首页
社区
课程
招聘
[原创]Android ART分析
2014-1-11 14:19 56063

[原创]Android ART分析

2014-1-11 14:19
56063
对Android ART的分析,主要包括ART Runtime启动过程以及dex2oat的分析。
由于代码量较多,忽略了很多细节,所以分析过程会存在错误;ART Runtime采用单例模式,启动过程中参数解析实例化会对后续dex2oat的分析有所影响,但是我在分析过程中没有去特别关注每个参数,有兴趣的可以自行分析!另外ART Runtime还可以继续深入分析!

BTW,附件中是分析中使用的javax.obex.jar以及转换后的OAT文件!

Android ART 分析
MindMac
2014/1/11

Google 在 Android4.4 中新增加了 ART(Android Run Time)用来替代之前的 Dalvik,关于 ART
的基本知识可以参考 Google 官方的 Introduction,XDA 的 Android 4.4 KitKat’s ART and App
Compatibility
以及 Android Police 对 ART 的相关介绍。在 Settings->Developer options 中可以选
择运行时环境,默认为 Dalvik,可以选择 ART,改变运行时环境后系统会重新启动,如图 1。

图 1 Android4.4 选择 ART 运行环境

与 Dalvik 不同的是,ART 不再使用 DEX 文件,而是 OAT 格式的文件,所以在重启过程中,系
统会完成从 DEX 到 OAT 格式的转换。本文主要分析 Android4.4 中 DEX 到 OAT 的转换过程。

ZIP 文件结构

在开始分析 Android ART 之前有必要先介绍下 ZIP 文件的格式。关于 ZIP 文件的结构可以
参考 The Great Android Security Hole Of ’08 ? – Part One: ZIP Files,更详细的解释可以参看.ZIP
File Format Specification


一个 ZIP 文件的基本结构如图 2 所示。No.1 到 No.n 分别代表了 ZIP 压缩文件中的文件,
n 表示所有的文件个数(包含文件目录)。新建一个 hello 文件夹,并在其下创建两个名为
helloone.txt 和 hello2.txt 的文件,其中内容分别为 contentone 和 contenttwo,将 hello 文件夹
压缩为 hello.zip 文件,以便后面的分析。

图 2 ZIP 文件基本结构

Local File Header

Local File Header 的结构如表 1 所示。其中 local file header signature 值固定为 0x04034b50。
使用 010Editor 打开,并应用 ZIP 模板,hellotwo.txt 文件的 Local File Header 如图 3 所示,由
于没有 extra 内容,因此 extra field length 为 0 且没有 extra field 字段。另外要注意的是,
hellotwo.txt 的 Local File Header 起始地址是 0x39,也就是 57,如图 4 所示。

表 1 Local File Header 各字段及大小
字段      字节数
local file header signature  4

version needed to extract  2 

general purpose bit flag  2

compression method  2

last mod file time  2

last mod file date  2

crc-32  4

compressed size  4

uncompressed size  4

file name length  2

extra filed length  2

file name  可变长度(size of file name)

extra filed  可变长度(size of file name)




图 3 hellotwo.txt 文件的 Local File Header


图 4 hellotwo.txt Local File Header 的起始地址

File Data

File Data 包含了文件的实际内容。helloone.txt 的 File Data 如图 5 所示,从图中可知其内
容为 contentone。

图 5 helloone.txt 的 File Data 内容

Central Directory Header

Central Directory Header 的结构如表 2 所示,其中 central file header signature 值固定为
0x02014b50。helloone.txt 的 Central Directory Header 如图 6 所示,其中 deHeaderOffset 即是对
应 relative offset of local header 字段,值为 57,与 hellotwo.txt 的 Local File Header 的起始地址
一致。

表 2 Central Directory Header 各字段和大小
字段             字节数   
central file header signature  4

version made by  2

version needed to extract  2

general purpose bit flag  2

compression method  2

last mod file time  2

last mod file date  2

crc-32  4

compressed size  4

uncompressed size  4

file name length  2

extra field length  2

file comment length  2

disk number start  2

internal file attribute  2

external file attributes  4

relative offset of local header  4

file name  可变长度(size of file name)

extra field   可变长度(size of file name)

file comment  可变长度(size of file name)




图 6 hellotwo.txt 的 Central Directory Header

End of Central Directory Record

End Of Central Directory Record 的结构如表 3 所示。end of central directory signature 值固
定为 0x06054b50,offset of start of central directory 值为第一个 Central Directory Header 的起始
地址。因此解析 ZIP 文件时,可以从文件尾开始读取数据,首先根据 0x06054b50 确定 End of
Central Directory Record 的位置,然后读取 offset of start of central directory 的值,拿到该值后
就可以定位到 Central Directory Header,而 Central Directory Header 中又包含了 Local File Header
的起始地址,这样从 Local File Header 中就能读取到 File Data 了。hello.zip 的 End of Central
Directory Record 如图 7 所示,第一个 Central Directory Header 的起始地址是 150。

表 3 End of Central Directory Record 各字段和大小
字段       字节数
end of central directory signature  4
number of this disk  2
number of the disk with the start of the central directory  2
total number of entries in the central directory on this disk  2
total number of entries in the central directory  2
size of the central directory  4
offset of start of central directory  4
ZIP file comment length  2
ZIP file comment  可变长度



图 7 hello.zip 的 End of Central Directory Record

ART Runtime

在未启用 ART 环境的情况下,Android 系统启动时会运行 Dalvik 虚拟机,ART 号称可以
取代 Dalvik,那么在启动 ART 后,虚拟机部分又会有何变化呢?

Android 系统 init 进程启动过程中会运行 app_process 进程,app_process 对应的源码位于
/frameworks/base/cmds/app_process/app_main.cpp,在 main 函数中会启动 Zygote(关于 Zygote
此文不会涉及太多,主要是分析和 ART 相关部分,Zygote 的分析可以参考
http://www.cnblogs.com/innost/archive/2011/01/26/1945769.html),代码如下。其中
1. if (zygote) {

2. runtime.start("com.android.internal.os.ZygoteInit",

3. startSystemServer ? "start-system-server" : "");

4. }


runtime 是 AppRuntime 的实例,AppRuntime 继承自 AndroidRuntime,进入 AndroidRuntime 类
的 start 函数(/frameworks/base/core/jni/AndroidRuntime.cpp),部分代码如下:
1. void AndroidRuntime::start(const char* className, const char* options)

2. {

3. ......

4. JniInvocation jni_invocation;

5. jni_invocation.Init(NULL);

6. JNIEnv* env;

7. if (startVm(&mJavaVM, &env) != 0) {

8. return;

9. }

10. ......

11. }


第 4 行声明 JniInvocation 类(/libnativehelper/JniInvocation.cpp)变量;第 5 行调用 JniInvocation
类的 Init 函数,该函数部分代码如下:
1. bool JniInvocation::Init(const char* library) {

2. #ifdef HAVE_ANDROID_OS

3. char default_library[PROPERTY_VALUE_MAX];

4. property_get("persist.sys.dalvik.vm.lib", default_library, "libdvm.so");

5. #else

6. const char* default_library = "libdvm.so";

7. #endif

8. if (library == NULL) {

9. library = default_library;

10. }

11. handle_ = dlopen(library, RTLD_NOW);

12. ......

13. if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),

14. "JNI_GetDefaultJavaVMInitArgs")) {

15. return false;

16. }

17. if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),

18. "JNI_CreateJavaVM")) {

19. return false;

20. }

21. if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),

22. "JNI_GetCreatedJavaVMs")) {

23. return false;

24. }

25. return true;

26. }


第 3 行定义 default_library 字符数组;第 4 行从属性系统中获得名为 persist.sys.dalvik.vm.lib
的属性值,默认值为 libdvm.so,对于 ART 环境,该值为 libart.so;8-10 行,由于参数 library
为 NULL,因此将 default_library 赋值给 library;11 行调用 dlopen 打开 libart.so 文件;13-24
行分别调用 FindSymbol 函数从打开的 libart.so 文件中搜索到对应的导出符号,例如 17 行
JNI_CreateJavaVM 对应的是/art/runtime/jni_internal.cc 中的 JNI_CreateJavaVM 函数,并非
/dalvik/vm/Jni.cpp 的 JNI_CreateJavaVM 函数,因为现在打开的是 libart.so,这是需要注意的
地方。

回到 AndroidRuntime::start 函数,第 7 行调用 startVm 函数启动虚拟机,该函数最终会调
用 JNI_CreateJavaVM 函数(AndroidRuntime.cpp 的 775 行),根据上面的分析,此处的 JNI_ Create
JavaVM 函数定义在/art/runtime/jni_internal.cc 中,部分代码如下:
1. extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {

2. const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);

3. ......

4. Runtime::Options options;

5. for (int i = 0; i < args->nOptions; ++i) {

6. JavaVMOption* option = &args->options[i];

7. options.push_back(std::make_pair(std::string(option->optionString),

 option->extraInfo));

8. }

9. bool ignore_unrecognized = args->ignoreUnrecognized;

10. if (!Runtime::Create(options, ignore_unrecognized)) {

11. return JNI_ERR;

12. }

13. Runtime* runtime = Runtime::Current();

14. bool started = runtime->Start();

15. .....

16. return JNI_OK;

17. }


5-7 行解析虚拟机启动参数并存入到 Runtime::Options 实例中;第 10 行根据解析的参数信息,
调用 Create 函数创建 Runtime 的实例,该函数代码如下:
1. bool Runtime::Create(const Options& options, bool ignore_unrecognized) {

2. ......

3. InitLogging(NULL);

4. instance_ = new Runtime;

5. if (!instance_->Init(options, ignore_unrecognized)) {

6. .....

7. return false;

8. }

9. return true;
 
10. }



第 3 行初始化 Log 系统;第 4 行创建 Runtime 实例;第 7 行初始化 Runtime,细节可以自行
分析。

回到 JNI_ Create JavaVM 函数中,13 行获得 Runtime 当前实例,Runtime 使用单例模式实
现,后文也会介绍到;14 行调用 Start 函数,该函数代码如下:
1. bool Runtime::Start() {

2. ......

3. Thread* self = Thread::Current();

4. self->TransitionFromRunnableToSuspended(kNative);

5. started_ = true;

6. InitNativeMethods();

7. ......

8. if (is_zygote_) {

9. if (!InitZygote()) {

10. return false;

11. }

12. } else {

13. DidForkFromZygote();

14. }

15. StartDaemonThreads();

16. ......

17. return true;
 
18. }


第 3 行获得当前运行线程;第 4 行将该线程状态从 Runnable 切换到 Suspend;第 6 行完成
Native 函数的初始化工作,InitNativeMethods 函数部分代码如下:

1. void Runtime::InitNativeMethods() {

2. .....

3. JNIEnv* env = self->GetJniEnv();

4. ......

5. RegisterRuntimeNativeMethods(env);

6. ......

7. }


第 3 行获取 JNI 环境;第 5 行调用 RegisterRuntimeNativeMethods 函数完成 Native 函数的注册,
至于注册了哪些 Native 函数,有兴趣的可以继续跟踪源码。

继续分析 Runtime:: Start 函数,第 9 行调用 InitZygote 完成一些文件文件系统的 mount;
第 15 行最终通过调用 java.lang.Daemons.start()函数启动守护进程。

以上分析了 ART Runtime 的启动过程,但是仍然忽略了很多细节,有兴趣的可以继续深
入分析。基本流程如下图。

图 8 ART Runtime Start 流程

PackageManagerService

PackageManagerService 在 Android 系统中负责 APK 包的管理以及应用程序的安装、卸载,
同时提供 APK 包的相关信息查询功能。DEX 到 OAT 的转换过程就是在 PackageManagerService
中完成的。PackageManagerService 由 SystemServer ( 源码路径为/frameworks/base/services
/java/com/android/server/SystemServer.java )创建,相关代码如下,关于 SystemServer 更加详

1. public void initAndLoop(){

2. ......

3. Slog.i(TAG, "Waiting for installd to be ready.");

4. installer = new Installer();

5. installer.ping();

6. ......

7. pm = PackageManagerService.main(context, installer,

8. actoryTest != SystemServer.FACTORY_TEST_OFF, onlyCore);

9. ......
 
10. }


细的分析可以参考邓凡平的《深入理解 Android 卷 II》第 3 章“深入理解 SystemServer”。第
4 行创建 Installer 实例,Installer 类主要负责和 installd 服务通过 socket 进行通信,installer 以
及 installd 会在后续分析。第 7 行调用 PackageManagerService.main 函数,其代码如下:

1. public static final IPackageManager main(Context context, Installer installer,

2. boolean factoryTest, boolean onlyCore) {

3. PackageManagerService m = new PackageManagerService(context, installer,

4. factoryTest, onlyCore);

5. ServiceManager.addService("package", m);

6. return m;

7. }



上述代码主要创建 PackageManagerService 的实例,并调用 ServiceManager.addService 函数将
其添加到 ServiceManager 的相关数据结构中,此处不分析 ServiceManager,有兴趣的可以自
行分析。

PackageManagerService 的构造函数部分代码如下:

1. public PackageManagerService(Context context, Installer installer,

2. boolean factoryTest, boolean onlyCore) {

3. ......

4. mInstaller = installer;

5. ......

6. if (mSharedLibraries.size() > 0) {

7. Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();

8. while (libs.hasNext()) {

9. String lib = libs.next().path;

10. ......

11. try {

12. if (dalvik.system.DexFile.isDexOptNeeded(lib)) {

13. alreadyDexOpted.add(lib);

14. mInstaller.dexopt(lib, Process.SYSTEM_UID, true);

15. didDexOpt = true;

16. }

18. }

19. }

20. }

21. ......

22. }


因为主要是分析 DEX 到 OAT 格式的转化过程,因此只截取了与此相关的部分代码,有关
PackageManagerService 更加详细的分析当然还是参考《深入理解 Android 卷 II》第 4 章“深
入理解 PackageManagerService”。上述代码第 4 行将参数 installer 赋值给 mInstaller,installer
在 SystemServer 中完成初始化;第 6 行的 mSharedLibraries 是一个 HashMap 实例,它保存了
/system/etc/permissions/platform.xml 文件中声明的系统库的信息,platform.xml 文件部分内容
如图 9,其中红框部分即为系统库信息,包括库名称以及库文件的具体路径。


图 9 platform.xml 文件内容

第 9 行获取库文件的路径;12 行调用 DexFile.isDexOptNeeded 函数判断 apk 或者 jar 文件是否
需要进行优化处理,该函数代码如下,源文件路径为/libcore/dalvik/src/main/java/dalvik
/system /DexFile.java

1. native public static boolean isDexOptNeeded(String fileName)
 
2. throws FileNotFoundException, IOException;


易知 isDexOptNeeded 函数是个 Native 函数,但是这个 Native 函数的定义则有两个,分别存在
于/dalvik/vm/native/dalvik_system_DexFile.cpp 和/art/runtime/native/dalvik_system_DexFile.cc 中。
从源码路径来看,前者是针对 Dalvik 运行环境的,而后者是 ART 运行环境的。在 ART 下,
/art/runtime/native/dalvik_system_DexFile.cc 中 Native 函数的注册过程可以参考 ART Runtime
分关于 Native 函数注册的分析。另外在启用 ART 后系统启动过程中的 Log 文件也给出了证
明,如图 10 中的红框所示文字即为/art/runtime/native/dalvik_system_DexFile.cc 中 262-263 行
Log 信息输出的一部分,因为分析是针对 ART 的,所以这里只研究/art/runtime/native/dalvik_


图 10 启用 ART 后系统启动的 Log 日志

system_DexFile.cc 中对应 isDexOptNeeded 的 Native 函数,该函数为 DexFile_isDexOptNeeded,
其部分代码如下,主要根据一些规则来判断是否需要进行 DEX 文件的优化操作,例如 5-11
行判断文件是否存在于

1. static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename)
{

2. ......

3. ScopedUtfChars filename(env, javaFilename);

4. ......

5. Runtime* runtime = Runtime::Current();

6. ClassLinker* class_linker = runtime->GetClassLinker();

7. const std::vector<const DexFile*>& boot_class_path = class_linker ->

 GetBootClassPath();

8. for (size_t i = 0; i < boot_class_path.size(); i++) {

9. if (boot_class_path[i]->GetLocation() == filename.c_str()) {

10. return JNI_FALSE;

11. }

12. }

13. ......

14. std::string cache_location(GetDalvikCacheFilenameOrDie(filename.c_str()));

15. oat_file.reset(OatFile::Open(cache_location, filename.c_str(), NULL, false));

16. if (oat_file.get() == NULL) {

17. LOG(INFO) << "DexFile_isDexOptNeeded cache file " << cache_location

18. << " does not exist for " << filename.c_str();

19. return JNI_TRUE;

20. }
 
21. ......

22. }



bootclasspath 路径中,对于所有定义在 bootclasspath 中的文件默认都已经经过了优化处理,
因此返回 false;14-19 行则是判断在 cache 目录下(/data/dalvik-cache/目录)是否存在已经优
化过的 DEX 文件,若没有则打印 Log 信息同时返回 true,该 Log 信息也就是图 10 中显示的
日志输出信息。DexFile_isDexOptNeeded 函数还有一些其他的判断规则,如检查是否存在对应
的 odex 文件,classes.dex 文件的校验值等。

回到 PackageManagerService 的构造函数,在 12 行完成是否需要进行 DEX 优化的判断后,
若需要进行 DEX 优化,则首先将该文件路径加入到名为 alreadyDexOpted 的 HashSet 中,然
后 14 行调用 Installer 类的 dexopt 函数,该函数代码如下:

1. public int dexopt(String apkPath, int uid, boolean isPublic) {

2. StringBuilder builder = new StringBuilder("dexopt");

3. builder.append(' ');

4. builder.append(apkPath);

5. builder.append(' ');

6. builder.append(uid);

7. builder.append(isPublic ? " 1" : " 0");

8. return execute(builder.toString());
 
9. }



第 2-7 行构造一段字符串,以系统库/system/framework/javax.obex.jar 为例,则 apkPath 值为
/system/framework/javax.obex.jar;uid 值为 Process.SYSTEM_UID,即 1000;isPublic 的值为 true。
因此构造完的字符串为“dexopt /system/framework/javax.obex.jar 1000 1”。第 8 行调用 execute
执行传入的命令,execute 函数的代码如下:
1. private int execute(String cmd) {

2. String res = transaction(cmd);

3. try {

4. return Integer.parseInt(res);

5. } catch (NumberFormatException ex) {

6. return -1;

7. }

8. }


代码第 2 行调用 transaction 函数,函数核心代码如下:

1. private synchronized String transaction(String cmd) {

2.if (!connect()) {

3. Slog.e(TAG, "connection failed");

4. return "-1";

5. }

6. if (!writeCommand(cmd)) {

7. Slog.e(TAG, "write command failed? reconnect!");

8. if (!connect() || !writeCommand(cmd)) {

9. return "-1";

10. }

11. }

12. ......

13. }


Installer 类主要是负责和 installd 服务通过 socket 通信,因此第 2 行首先调用 connect 函数与
socket 建立连接,第 6 行调用 writeCommand 向 socket 发送数据。接着分析 installd 服务,源
代码位于/frameworks/native/cmds/installd/installd.c,main 函数的部分代码如下:
1. int main(const int argc, const char *argv[]) {

2. ......

3. for (;;) {

4. alen = sizeof(addr);

5. s = accept(lsocket, &addr, &alen);

6. ......

7. ALOGI("new connection\n");

8. for (;;) {

9. unsigned short count;

10. if (readx(s, &count, sizeof(count))) {

11. ALOGE("failed to read size\n");

12. break;

13. }

14. if ((count < 1) || (count >= BUFFER_MAX)) {

15. ALOGE("invalid size %d\n", count);

16. break;

17. }

18. if (readx(s, buf, count)) {

19. ALOGE("failed to read command\n");

20. break;

21. }

22. buf[count] = 0;

23. if (execute(s, buf)) break;

24.  }

25. ALOGI("closing connection\n");

26. close(s);

27. }

28.

29. return 0;

30. }



installd 在接受到一个 socket 请求后,首先会判断传入的消息字符串的长度(10-17 行);接着
会将消息字符串读入到 buf 数组中(18-22 行);最后调用 execute 函数执行命令。execute 函数
会解析命令字符串,并调用相应的函数执行,对于“dexopt /system/framework/javax.obex.jar
1000 1”命令,最终会调用 installd.c 源代码中的 do_dexopt 函数,而该函数只是去调用 dexopt
函数,如下代码所示:

1. static int do_dexopt(char **arg, char reply[REPLY_MAX])

2. {

3.return dexopt(arg[0], atoi(arg[1]), atoi(arg[2])); 

4. }



dexopt 传入的三个参数值分别为/system/framework/javax.obex.jar,1000,1,该函数位于
/frameworks/native/cmds/installd/commands.c 源文件中,函数代码如下:

1. int dexopt(const char *apk_path, uid_t uid, int is_public)

2. {

3. struct utimbuf ut;

4. struct stat apk_stat, dex_stat;

5. char out_path[PKG_PATH_MAX];

6. char dexopt_flags[PROPERTY_VALUE_MAX];

7. char persist_sys_dalvik_vm_lib[PROPERTY_VALUE_MAX];

8. char *end;

9. int res, zip_fd=-1, out_fd=-1;

10. if (strlen(apk_path) >= (PKG_PATH_MAX - 8)) {

11. return -1;

12. }

13. property_get("persist.sys.dalvik.vm.lib", persist_sys_dalvik_vm_lib,

"libdvm.so");

14. sprintf(out_path, "%s%s", apk_path, ".odex");

15. if (stat(out_path, &dex_stat) == 0) {

16. return 0;

17. }

18. if (create_cache_path(out_path, apk_path)) {

19. return -1;

20. }

21. ......

22. pid_t pid;

23. pid = fork();

24. if (pid == 0) {

25. ......

26. if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {

27. run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);

28. } else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {

29. run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);

30. } else {

31. exit(69); /* Unexpected persist.sys.dalvik.vm.lib value */

32. }

33. exit(68); /* only get here on exec failure */

34. }

35. ......
 
36. }



10-12 行首先对需要进行优化的文件路径长度进行判断;13 行获取名为
persist.sys.dalvik.vm.lib 的属性值,默认值为 libdvm.so,当然对于 ART 运行环境,该值为 libart.so;
14-17 行判断是否已经存在对应的.odex 文件,若有说明已经经过预处理,可以直接忽略,
out_path 路径为/system/framework/javax.obex.jar.odex;18 行调用 create_cache_path 对 out_path
处理后其值最终为/data/dalvik-cache/system@framerok@javax.obex.jar.odex;23 行 fork 一个子
进程来完成 DEX 文件的优化,26-29 行根据运行环境调用不同的函数来行 DEX 文件的处理,
若是 Dalvik,则调用 run_dexopt;若是 ART,则调用 run_dex2oat,因此接着分析 run_dex2oat
函数。

run_dex2oat 函数代码如下:

1. static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name,
 
2. const char* output_file_name, const char* dexopt_flags)

3. {

4. static const char* DEX2OAT_BIN = "/system/bin/dex2oat";

5. static const int MAX_INT_LEN = 12;

6. char zip_fd_arg[strlen("--zip-fd=") + MAX_INT_LEN];

7. char zip_location_arg[strlen("--zip-location=") + PKG_PATH_MAX];

8. char oat_fd_arg[strlen("--oat-fd=") + MAX_INT_LEN];

9. char oat_location_arg[strlen("--oat-name=") + PKG_PATH_MAX];

10. 

11. sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd);

12. sprintf(zip_location_arg, "--zip-location=%s", input_file_name);

13. sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd);

14. sprintf(oat_location_arg, "--oat-location=%s", output_file_name);

15. execl(DEX2OAT_BIN, DEX2OAT_BIN,

16. zip_fd_arg, zip_location_arg,

17. oat_fd_arg, oat_location_arg,

18. (char*) NULL);
 
19. }



DEX2OAT_BIN 指向 dex2oat 可执行文件的路径,即/system/bin/dex2oat,DEX 到 OAT 文件格式
的转换最终需要通过该可执行文件完成;6-14 行完成参数的构造,--zip-fd 表示待转换文件
的文件描述符,--zip-location 表示待转换文件的路径,值为/system/framework/javax.obex.jar,
--oat-fd 表示转换结果的文件描述符,--oat-location 表示转换结果的输出文件路径,值为
/data/dalvik-cache/system@framerok@javax.obex.jar.odex;第 15 行调用 execl 执行 dex2oat 命
令。

dex2oat

/system/bin/dex2oat 对应的源码文件位于/art/dex2oat/dex2oat.cc。main 函数代码如下:

1. int main(int argc, char** argv) {
 
2. return art::dex2oat(argc, argv);

3. }



直接调用 art 命名空间下的 dex2oat 函数,该函数有点长,去除注释差不多有 500 行,但是
有很大以部分代码都是在解析参数,我们只关注前面提到的几个参数,所以下面贴的代码有
所省略,而且鉴于代码略长,会分成几部分来分析。

参数解析

与参数解析相关的代码如下,大家可以参考下源码,因为函数开头声明了很多变量,为
了避免篇幅过长,此处略去不表。基本后面所有遇到的变量都是在函数开头声明的,而且这
些变量的值最终都是从传递过来的参数中获取的。

1. static int dex2oat(int argc, char** argv) {

2. ......

3. #if defined(ART_USE_PORTABLE_COMPILER)

4. CompilerBackend compiler_backend = kPortable;

5. #else

6. CompilerBackend compiler_backend = kQuick;
 
7. #endif

8. #if defined(__arm__)

9. InstructionSet instruction_set = kThumb2;
 
10. #elif defined(__i386__)

11. InstructionSet instruction_set = kX86;

12. #elif defined(__mips__)

13. InstructionSet instruction_set = kMips;

14. #else

15. #error "Unsupported architecture"

16. #endif

17. ......

18. for (int i = 0; i < argc; i++) {

19. const StringPiece option(argv[i]);

20. ......

21. else if (option.starts_with("--zip-fd=")) {

22. const char* zip_fd_str = option.substr(strlen("--zip-fd=")).data();

23. if (!ParseInt(zip_fd_str, &zip_fd)) {

24. Usage("Failed to parse --zip-fd argument '%s' as an integer", zip_fd_str);

25. }

26. } else if (option.starts_with("--zip-location=")) {

27. zip_location = option.substr(strlen("--zip-location=")).data();

28. }

29. ......

30. } else if (option.starts_with("--oat-fd=")) {

31. const char* oat_fd_str = option.substr(strlen("--oat-fd=")).data();

32. if (!ParseInt(oat_fd_str, &oat_fd)) {

33. Usage("Failed to parse --oat-fd argument '%s' as an integer", oat_fd_str);

34. }

35. }

36. ......

37. } else if (option.starts_with("--oat-location=")) {

38. oat_location = option.substr(strlen("--oat-location=")).data();

39. }

40. ......

41. }

42. if (oat_filename.empty() && oat_fd == -1) {

43. Usage("Output must be supplied with either --oat-file or --oat-fd");

44. }

45. if (!oat_filename.empty() && oat_fd != -1) {

46. Usage("--oat-file should not be used with --oat-fd");

47. }

48. ......

49. if (oat_fd != -1 && !image_filename.empty()) {

50. Usage("--oat-fd should not be used with --image");

51. }

52. if (host_prefix.get() == NULL) {

53. const char* android_product_out = getenv("ANDROID_PRODUCT_OUT");

54. if (android_product_out != NULL) {

55. host_prefix.reset(new std::string(android_product_out));

56. }

57. }

58. if (android_root.empty()) {

59. const char* android_root_env_var = getenv("ANDROID_ROOT");

60. if (android_root_env_var == NULL) {

61. Usage("--android-root unspecified and ANDROID_ROOT not set");

62. }

63. android_root += android_root_env_var;

64. }

65. bool image = (!image_filename.empty());

66. if (!image && boot_image_filename.empty()) {

67. if (host_prefix.get() == NULL) {

68. boot_image_filename += GetAndroidRoot();

69. } else {

70. boot_image_filename += *host_prefix.get();

71. boot_image_filename += "/system";

72. }

73. boot_image_filename += "/framework/boot.art";

74. }

75. std::string boot_image_option;

76. if (!boot_image_filename.empty()) {

77. boot_image_option += "-Ximage:";

78. boot_image_option += boot_image_filename;

79. }

80. ......

81. if (dex_locations.empty()) {

82. for (size_t i = 0; i < dex_filenames.size(); i++) {

83. dex_locations.push_back(dex_filenames[i]);

84. }

85. } else if (dex_locations.size() != dex_filenames.size()) {

86. Usage("--dex-location arguments do not match --dex-file arguments");

87. }

88. ......
 
89. }



3-7 行根据 ART_USE_PORTABLE_COMPILER 宏的值来确定 ART Compiler Driver 的工作方式,关
于 ART Compiler Driver 后续会涉及到;8-16 行根据设备的平台架构来确定指令集,包括 ARM,
i386 以及 Mips;21-25 行解析--zip-fd 参数并转换为整型值赋值给 zip_fd;26-39 行分别解析
获得 zip_location,oat_fd 以及 oat_location。获得参数信息后,会对一些参数进行判断,例如
45-46 行会判断--oat-file 以及--oat-fd 参数是否同时被赋值,对于一些不正确的参数组合,会
调用 Usage 函数打印 dex2oat 的使用方法并退出。

之后会根据一些参数情况进行其他参数的赋值操作,52 行判断 host_prefix 是否为空,
由于没有使用--host-prefix 参数,因此进入 if 分支语句,获取 ANDROID_PRODUCT_OUT 环境变
量并重新赋值给 host_prefix 变量,ANDROID_PRODUCT_OUT 环境变量应该是在 PC 机上进行
Android 源码编译时设置的,一般为<ANDROID BASEDIR>/out/target/product/generic/,所以在
Android 手机设备上不存在此环境变量,host_prefix 值还是为 NULL,这个没有编译调试,分
析结论不一定正确。不过可以编写 Android 程序,在 Java 层通过 System.getenv (“ANDROID
_PRODUCT_OUT”)来测试,会发现没有这个环境变量(System.getenv()可以获取所有的环境变
量)!至于 System. getenv(String)函数是否与上述的 getenv 函数最终调用的是同一个函数,大
家可以跟踪源码,System.getenv 调用过程是:System.getenv -> Libcore.os.getenv-> Posix.getenv,
Posix.getenv(libcore/luni/src/main/java/libcore/io/Posix.java)调用的是 Native 函数,而这个 Native
函数的注册暂时没有找到(囧)。

58-64 行获取 ANDROID_ROOT 环境变量,该值为/system,也就是 Android 系统下的 system
目录路径;65 行由于 image_filename 为空,因此布尔值 image 为 false,则会进入 66 行的 if
分支,66 行由于 host_prefix 为 NULL, boot_image_filename 值为“/system”(GetAndroidRoot
函数源码在/art/runtime/utils.cc 文件中,仍然调用 getenv(“ANDROID_ROOT”),不过会增加
/system 目录是否存在的判断),执行 73 行,最终 boot_iamge_filename 值为“/system/framework
/boot.art”,但是在 Nexus4 上/system/framework 目录下并不存在 boot.art 文件,反而在/data
/dalvik-cache 目录下存在 [email]system@framework@boot.art[/email] 以及 [email]system@framework@boot.oat[/email] 文件
(Android4.4 系统应该都是这样);76 行,经过上述操作,boot_image_filename 不为空,因此进
入 if 分支语句,boot_image_option 最终值为“-Ximage:/system/framework/boot.art”;81 行
dex_locations 为空,进入 if 分支,由于 dex_filenames 仍然为空,所以不会执行 for 循环。

经过上述分析,总结一些变量的值:

  • host_prefix: NULL
  • android_root: /system
  • boot_image_filename: /system/framework/boot.art
  • boot_image_option: -Ximage:/system/framework/boot.art
  • dex_locations: 空


  • 创建 OAT 文件指针

    在完成参数解析判断后,会创建一个指向 oat_location 的文件指针,暂时没有真正的写
    入 OAT 格式的文件数据。代码如下:
    1. UniquePtr<File> oat_file;
    
    2. bool create_file = !oat_unstripped.empty();
    
    3. if (create_file) {
    
    4. oat_file.reset(OS::CreateEmptyFile(oat_unstripped.c_str()));
    
    5. if (oat_location.empty()) {
    
    6. oat_location = oat_filename;
    
    7. }
    
    8. } else {
    
    9. oat_file.reset(new File(oat_fd, oat_location));
    
    10. oat_file->DisableAutoClose();
    
    11. }
    
    12. if (oat_file.get() == NULL) {
    
    13. PLOG(ERROR) << "Failed to create oat file: " << oat_location;
    
    14. return EXIT_FAILURE;
    
    15. }
    
    16. if (create_file && fchmod(oat_file->Fd(), 0644) != 0) {
    
    17. PLOG(ERROR) << "Failed to make oat file world readable: " << oat_location;
    
    18. return EXIT_FAILURE;
    
    19. }
    
    


    第 1 行声明 File 指针变量 oat_file;第 2 行因为 oat_unstripped 为空,故 create_file 布尔值为 flase;
    第 3 行判断 create_file 值,进入 else 分支;9 行根据 oat_fd 以及 oat_location 创建 File 实例并
    赋值给 oat_file;第 10 行调用 DisableAutoClose 函数禁止文件自动关闭;12-18 行会进行一些
    判断操作。上述代码仅仅是创建了一个 File 实例,还没有真正写入任何数据。

    dex2oat 准备工作

    接着会完成一些 DEX 到 OAT 文件格式的转换工作,代码如下,对于部分分支语句由于
    在本次分析中不会执行已忽略。

    1. ......
    
    2. Runtime::Options options;
    
    3. options.push_back(std::make_pair("compiler", reinterpret_cast<void*>(NULL)));
    
    4. std::vector<const DexFile*> boot_class_path;
    
    5. if (boot_image_option.empty()) {
     
    6. ......
    
    7. } else {
    
    8. options.push_back(std::make_pair(boot_image_option.c_str(),
     
     reinterpret_cast<void*>(NULL)));
    
    9. }
    
    10. ......
    
    


    第 2 行声明 Runtime::Options 类型的变量 options,而 Runtime::Options 实际上是一个包含
    pair(http://www.cplusplus.com/reference/utility/pair/)的 vector,定义在/art/runtime/runtime.h 头
    文件中,如下,pair 中的两个数据一个是字符串,另一个为指针,类似于 HashMap 之类的结
    构:

    1. class Runtime {
    
    2. public:
    
    3. typedef std::vector<std::pair<std::string, const void*> > Options;
    
    4. ......
    
    5. }
    
    


    第 3 行在 options 变量中添加一个 pair 数据;第 4 行声明包含 DexFile 的 vector 变量 boot_class_
    path,类 DexFile 的定义在/art/runtime/dex_file.h 头文件中,其实就是 Dalvik 上关于 dex 文件
    结构的定义;第 5 行由于 boot_image_option 不为空,因此执行 else 分支语句,跳到第 8 行,
    继续在 options 中增加一个 pair 数据,boot_image_option 值为-Ximage:/system/framework
    /boot.art。所以最终 options 中包含“compiler”和“-Ximage:/system/framework/boot.art”两
    项。

    提取 classes.dex 文件

    在完成一系列的准备工作后,就要开始进入比较繁重的 OAT 文件创建和转换工作了。
    部分代码如下:

    1. Dex2Oat* p_dex2oat;
    
    2. if (!Dex2Oat::Create(&p_dex2oat, options, compiler_backend, instruction_set,
    
    thread_count)) {
    
    3. LOG(ERROR) << "Failed to create dex2oat";
    
    4. return EXIT_FAILURE;
    
    5. }
    
    6. UniquePtr<Dex2Oat> dex2oat(p_dex2oat);
    
    7. Thread* self = Thread::Current();
    
    8. self->TransitionFromRunnableToSuspended(kNative);
    
    9. WellKnownClasses::Init(self->GetJniEnv());
    
    10. ......
    
    11. std::vector<const DexFile*> dex_files;
    
    12. if (boot_image_option.empty()) {
    
    13. dex_files = Runtime::Current()->GetClassLinker()->GetBootClassPath();
    
    14. } else {
    
    15. if (dex_filenames.empty()) {
    
    16. UniquePtr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(zip_fd));
    
    17. if (zip_archive.get() == NULL) {
    
    18. LOG(ERROR) << "Failed to open zip from file descriptor for " << zip_location;
    
    19. return EXIT_FAILURE;
    
    20. }
    
    21. const DexFile* dex_file = DexFile::Open(*zip_archive.get(), zip_location);
    
    22. if (dex_file == NULL) {
    
    23. ......
    
    24. return EXIT_FAILURE;
    
    25. }
    
    26. dex_files.push_back(dex_file);
    
    27. } else {
    
    28. ......
    
    29. }
    
    30. for (const auto& dex_file : dex_files) {
    
    31. if (!dex_file->EnableWrite()) {
    
    32. ......
    
    33. }
    
    34. }
    
    35. }
    


    第 1 行声明指向 Dex2Oat 的指针,类 Dex2Oat 定义在/art/dex2oat/dex2oat.cc 文件中;第 2 行
    调用 Dex2Oat 的静态方法 Create,其中 options 参数已经在“dex2oat 准备工作”部分分析了,
    compiler_backend 在没有指定--compiler-backend 参数的情况下,会根据 ART_USE_PORTABLE
    _COMPILER 宏定义的情况取值,若该宏定义了则为 kPortable,否则为 kQuick;instruction_set
    根据源码编译指定的目标平台会使用相应的指令集,目前大部分 Android 设备使用的 ARM,
    因此 instruction_set 值为 kThumb2。thread_count 值在默认情况下通过 sysconf
    (_SC_NPROCESSORS_CONF)获取,也就是 CPU 的个数。Dex2Oat::Create 函数代码如下:

    1. static bool Create(Dex2Oat** p_dex2oat,Runtime::Options& options,
    
    2. CompilerBackend compiler_backend, InstructionSet instruction_set,
    
    3. size_t thread_count)
    
    4. SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_) {
    
    5. if (!CreateRuntime(options, instruction_set)) {
    
    6. *p_dex2oat = NULL;
    
    7. return false;
    
    8. }
    
    9. *p_dex2oat = new Dex2Oat(Runtime::Current(), compiler_backend, instruction_set,
    
    thread_count);
    
    10. return true;
     
    11. }
    


    第 5 行调用 CreateRuntime 函数获取 Runtime(/art/runtime/runtime.cc)类的实例,并进行相关的
    设置,Runtime 使用单例模式,所以获取 Runtime 实例之前会先获取锁,如第 4 行所示,关
    于 Runtime 类实例的获取比较简单,不再详述,是典型的单例设计模式;第 9 行创建 Dex2Oat
    类的实例,Dex2Oat 的构造函数比较简单,只是一些类成员的赋值操作,如下代码所示,除
    了传入的一些参数外,还记录了实例化的时间保存在 start_ns_成员变量中。

    1. explicit Dex2Oat(Runtime* runtime,
    
    2. CompilerBackend compiler_backend,
    
    3. InstructionSet instruction_set,
    
    4. size_t thread_count)
    
    5. : compiler_backend_(compiler_backend),
    
    6. instruction_set_(instruction_set),
    
    7. runtime_(runtime),
    
    8. thread_count_(thread_count),
    
    9. start_ns_(NanoTime()) {
     
    10. }
    


    继续回到 dex2oat 函数,第 6 行将 p_dex2oat 重新赋值给 dex2oat 变量;第 7 行调用
    Thread::Current 得到当前线程,Thread 类定义在/art/runtime/thread.h 头文件中;第 8 行将线程
    状态从 Runnable 切换到 Suspend,释放掉之前在调用 Dex2Oat::Create 中获取的锁,
    TransitionFromRunnableToSuspend 函数定义在/art/runtime/thread-inl.h 头文件中,是个内联函
    数;第 9 行调用 WellKnownClasses::Init 函数完成一些 JNI 类、函数以及字段的初始化操作,
    主要是从 JNI 运行环境中查找一些类、函数等信息并返回,self->GetJniEnv()可以获得线程关
    联的 JNI 环境,Init 函数代码位于/art/runtime/well_known_classes.cc,可自行进行分析;11 行
    声明名为 dex_files 的 vector 变量;12 行由于 boot_image_option 非空,因此进入 else 分支;
    15 行 dex_filenames 为空,进入 if 分支,16 行调用 ZipArchive::OpenFromFd 函数创建 ZipArchive
    类实例,主要完成 zip 文件到内存结构的映射。个人觉得此处 zip 文件的操作对后续的分析
    有一定的影响,因此会在此展开分析 ZipArchive 类(/art/runtime/zip_archive.h 和/art/runtime/
    zip_archive.cc)。OpenFromFd 的函数代码如下:

    1. ZipArchive* ZipArchive::OpenFromFd(int fd) {
    
    2. ......
    
    3. UniquePtr<ZipArchive> zip_archive(new ZipArchive(fd));
    
    4. ......
    
    5. if (!zip_archive->MapCentralDirectory()) {
    
    6. zip_archive->Close();
    
    7. return NULL;
    
    8. }
    
    9. if (!zip_archive->Parse()) {
    
    10. zip_archive->Close();
    
    11. return NULL;
    
    12. }
    
    13. return zip_archive.release();
     
    14. }
    


    第 3 行初始化 ZipArchive 实例并赋值给 zip_archive 变量,ZipArchive 类的构造函数比较简单,
    如下所示,只是简单的成员变量初始化:

    1. explicit ZipArchive(int fd) : fd_(fd), num_entries_(0), dir_offset_(0) {}
    


    第 5 行调用 MapCentralDirectory 完成 Central Directory Header 的查找以及将所有 Central
    Directory Header 内容映射到内存(关于 ZIP 文件的格式可以参考本文开头关于 ZIP 文件结构
    介绍),该函数部分代码如下(为避免篇幅过长,已删除各种与文件合法性检查相关的代码):

    1. bool ZipArchive::MapCentralDirectory() {
    
    2. off64_t file_length = lseek64(fd_, 0, SEEK_END);
    
    3. ......
    
    4. size_t read_amount = kMaxEOCDSearch;
    
    5. if (file_length < off64_t(read_amount)) {
    
    6. read_amount = file_length;
    
    7. }
    
    8. UniquePtr<uint8_t[]> scan_buf(new uint8_t[read_amount]);
    
    9. ......
    
    10. if (lseek64(fd_, 0, SEEK_SET) != 0) {
    
    11. return false;
    
    12. }
    
    13. ssize_t actual = TEMP_FAILURE_RETRY(read(fd_, scan_buf.get(), sizeof(int32_t)));
    
    14. ......
    
    15. unsigned int header = Le32ToHost(scan_buf.get());
    
    16. if (header != kLFHSignature) {
    
    17. return false;
    
    18. }
    
    19. off64_t search_start = file_length - read_amount;
    
    20. if (lseek64(fd_, search_start, SEEK_SET) != search_start) {
    
    21. return false;
    
    22. }
    
    23. actual = TEMP_FAILURE_RETRY(read(fd_, scan_buf.get(), read_amount));
    
    24. ......
    
    25. int i;
    
    26. for (i = read_amount - kEOCDLen; i >= 0; i--) {
    
    27. if (scan_buf.get()[i] == 0x50 && Le32ToHost(&(scan_buf.get())[i]) ==
    
     kEOCDSignature) {
    
    28. break;
    
    29. }
    
    30. }
    
    31. ......
    
    32. off64_t eocd_offset = search_start + i;
    
    33. const byte* eocd_ptr = scan_buf.get() + i;
    
    34. DCHECK(eocd_offset < file_length);
    
    35. uint16_t disk_number = Le16ToHost(eocd_ptr + kEOCDDiskNumber);
    
    36. uint16_t disk_with_central_dir = Le16ToHost(eocd_ptr + kEOCDDiskNumberForCD);
    
    37. uint16_t num_entries = Le16ToHost(eocd_ptr + kEOCDNumEntries);
    
    38. uint16_t total_num_entries = Le16ToHost(eocd_ptr + kEOCDTotalNumEntries);
    
    39. uint32_t dir_size = Le32ToHost(eocd_ptr + kEOCDSize);
    
    40. uint32_t dir_offset = Le32ToHost(eocd_ptr + kEOCDFileOffset);
    
    41. uint16_t comment_size = Le16ToHost(eocd_ptr + kEOCDCommentSize);
    
    42. ......
    
    43. dir_map_.reset(MemMap::MapFile(dir_size, PROT_READ, MAP_SHARED, fd_,
    
    dir_offset));
    
    44. ......
    
    45. num_entries_ = num_entries;
    
    46. dir_offset_ = dir_offset;
    
    47. return true;
     
    48. }
    


    第 1 行通过 lseek64 函数将文件描述符定位到 ZIP 文件结尾处得到文件的长度;第 3 行将
    kMaxEOCDSearch 赋值给 read_amount,kMaxEOCDSearch = (kMaxCommentLen + kEOCDLen) =
    65535 + 22,也就是 End of Central Directory 的最大长度,因为 Zip file comment 最大长度为 64KB,
    故 kMaxEOCDSearch 最大长度为 65535+22;第 5 行判断若整个 ZIP 文件的长度小于
    read_amount,则将 read_amount 重新设置为 file_length 的值;第 8 行声明 scan_buf 指针用来
    存储读取的文件内容;第 10 行将文件描述符定位到 ZIP 文件的开头,因为在获取文件长度
    时将其定位到了文件结尾处,现在要重新进行定位;13 行从文件开始处连续读取
    sizeof(int32_t)字节的内容到 scan_buf 中;15-18 行判断文件开头 4 个字节是否为 0x04034b50,
    也就是 Local File Header 的 Signature,若不是说明该 ZIP 文件格式不正确;19 行计算
    search_start 值,也就是后续 End of Central Directory 搜索的起始地址(相对于文件开头);20 行
    将文件描述符定位到 search_start 处;23 行读取 search_start 之后的所有内容到 scan_buf;26-30
    行开始搜索 End of Central Directory 的位置,搜索结果可以参考图 11,图中 End of Central
    Directory Record 没有包含 Zip file comment。注意为了方便描述,Zip file comment 长度设定为
    10Bytes,另外一点要注意的是在 for 循环中比较 Signature 时是从 scan_buf 的末尾往前扫的;
    32 行给 eocd_offset 赋值,从图 11 中可知 End of Central Directory Record 的偏移量为 search_start
    + 65535 - 10,也就是 search_start + i 的值;33 行获得指向 EOCD 起始地址的指针;35-41 行
    完成 EOCD 中字段值的获取,可以参考 ZIP 文件结构中的介绍,其中 dir_size 表示所有 Central

    图 11 End of Central Directory 搜索示意图

    Directory Header 的大小,dir_offset 表示第一个 Central Directory Header 相对文件开始处的偏移
    地址;43 行将所有 Central Directory Header 的内容映射到内存;45-46 行给相应的成员变量
    赋值,保存解析到的 Central Directory Header 的个数及偏移量。

    继续回到 OpenFromFd 函数,第 9 行调用 Parse 函数将 Central Directory Header 保存到名
    为 dir_entries_的 SafeMap 类(/art/runtime/safe_map.h)中,其中 Key 为 StringPiece 实例(StringPiece
    类定义在/art/runtime /base/stringpiece.h 头文件中),Value 为对应 Central Directory Header 映射
    到内存中的起始地址。Parse 函数比较简单,不再赘述。

    分析完 ZipArchive::OpenFromFd 后,继续回到 dex2oat 中,21 行调用 DexFile::Open 函数,
    该函数定义在/art/runtime/dex_file.cc 源文件中,注意有两个 DexFile::Open 函数,一个是 const
    DexFile* DexFile::Open(const std::string& filename, const std::string& location),另一个是 const
    DexFile* DexFile::Open(const ZipArchive& zip_archive, const std::string& location),这里显然调用的
    是后者,部分代码如下:

    1. const DexFile* DexFile::Open(const ZipArchive& zip_archive, const std::string&
    
    location) {
    
    2. CHECK(!location.empty());
    
    3. UniquePtr<ZipEntry> zip_entry(zip_archive.Find(kClassesDex));
    
    4. ......
    
    5. UniquePtr<MemMap> map(zip_entry->ExtractToMemMap(kClassesDex));
    
    6. ......
    
    7. UniquePtr<const DexFile> dex_file(OpenMemory(location, zip_entry->GetCrc32(),
    
    map.release()));
    
    8. ......
    
    9. if (!DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(),
    
    dex_file->Size())) {
    
    10. LOG(ERROR) << "Failed to verify dex file '" << location << "'";
    
    11. return NULL;
    
    12. }
    
    13. if (!dex_file->DisableWrite()) {
    
    14. LOG(ERROR) << "Failed to make dex file read only '" << location << "'";
    
    15. return NULL;
    
    16. }
    
    17. CHECK(dex_file->IsReadOnly()) << location;
    
    18. return dex_file.release();
    
    19. }
    


    第 3 行调用 ZipArchive 类的 Find 函数,以文件名为 Key 找到对应 ZIP 文件中的文件并返回
    ZipEntry 实例,该 ZipEntry 实例对应的内容为 Central Directory Header,其中 kClasssesDex 定义
    在 dex_file.cc 源码文件的第 208 行,值为”classes.dex”,也就是从/system/framework/javax.
    obex.jar(JAR 文件实际上也是一个 ZIP 文件)文件中找到 classes.dex 的文件。可以简单看下从
    Nexus4 上 pull 下来的 javax.obex.jar,将该文件解压缩,得到的文件列表如图 12 所示,确实
    存在一个 classes.dex 的文件。第 5 行调用 ZipEntry 的 ExtractToMemMap 函数,该函数部分代

    图 12 javax.obex.jar 解压后文件

    码如下:

    1. MemMap* ZipEntry::ExtractToMemMap(const char* entry_filename) {
    
    2. std::string name(entry_filename);
    
    3. name += " extracted in memory from ";
    
    4. name += entry_filename;
    
    5. UniquePtr<MemMap> map(MemMap::MapAnonymous(name.c_str(),NULL,
    
    6. GetUncompressedLength(), PROT_READ | PROT_WRITE));
    
    7. ......
    
    8. bool success = ExtractToMemory(map->Begin(), map->Size());
    
    9. ......
    
    10. return map.release();
     
    11. }
    


    2-4 行做字符串拼接,最终字符串值为“classes.dex extracted in memory from classes.dex”;
    第 5 行调用 MemMap 类的 MapAnonymous(/art/runtime/mem_map.cc)创建 MemMap 的实例,其
    中 GetUncompressedLength 的函数代码如下,很简单,就是从 Central Directory Header 中获得
    
    1. uint32_t ZipEntry::GetUncompressedLength() {
     
    2. return Le32ToHost(ptr_ + ZipArchive::kCDEUncompLen);
    
    3. }
    


    uncompressed size 的值。回到 ExtractToMemMap 函数,第 8 行调用 ExtractToMemory,该函数
    代码如下:

    1. bool ZipEntry::ExtractToMemory(uint8_t* begin, size_t size) {
    
    2. ......
    
    3. off64_t data_offset = GetDataOffset();
    
    4. ......
    
    5. if (lseek64(zip_archive_->fd_, data_offset, SEEK_SET) != data_offset) {
    
    6. PLOG(WARNING) << "Zip: lseek to data at " << data_offset << " failed";
    
    7. return false;
    
    8. }
    
    9. switch (GetCompressionMethod()) {
    
    10. case kCompressStored:
    
    11. return CopyFdToMemory(begin, size, zip_archive_->fd_,
    
    GetUncompressedLength());
    
    12. case kCompressDeflated:
    
    13. return InflateToMemory(begin, size, zip_archive_->fd_,
    
    14. GetUncompressedLength(), GetCompressedLength());
    
    15. default:
    
    16. LOG(WARNING) << "Zip: unknown compression method " << std::hex <<
    
    GetCompressionMethod();
    
    17. return false;
    
    18. }
     
    19. }
    


    第 3 行调用 GetDataOffset 函数,该函数的作用就是从 Central Directory Header 中找到 Local File
    Header 的偏移量,然后从 Local File Header 中定位到 File Data 的位置,该函数部分代码如下(已
    忽略所有文件格式合法性检查):

    1. off64_t ZipEntry::GetDataOffset() {
    
    2. ......
    
    3. int64_t lfh_offset = Le32ToHost(ptr_ + ZipArchive::kCDELocalOffset);
    
    4. ......
    
    5. if (lseek64(zip_archive_->fd_, lfh_offset, SEEK_SET) != lfh_offset) {
    
    6. PLOG(WARNING) << "Zip: failed seeking to LFH at offset " << lfh_offset;
    
    7. return -1;
    
    8. }
    
    9. uint8_t lfh_buf[ZipArchive::kLFHLen];
    
    10. ssize_t actual = TEMP_FAILURE_RETRY(read(zip_archive_->fd_, lfh_buf,
    
    sizeof(lfh_buf)));
    
    11. ......
    
    12. off64_t data_offset = (lfh_offset + ZipArchive::kLFHLen
    
    13. + Le16ToHost(lfh_buf + ZipArchive::kLFHNameLen)
    
    14. + Le16ToHost(lfh_buf + ZipArchive::kLFHExtraLen));
    
    15. ......
    
    16. return data_offset;
    
    17. }
    


    第 3 行获得该 Central Directory Header 对应的 File Local Header 的偏移量;第 5 行将 zip_archive_
    的 fd_定位到该偏移处;第 10 行从偏移处开始读取内容到 lfh_buf 中;第 12 行从 File Local
    Header 中获得 File Data 的偏移,可以参考 Local File Header 的结构来看第 12 行的代码;最后
    返回 data_offset 值。

    回到 ExtractToMemory,第 5 行根据 File Data 的偏移量,将 ZIP 文件的文件描述符定位到
    File Data 起始位置;第 9 行进入 switch 语句,GetCompressionMethod 从 Central Directory Header
    中获得 compression method 字段值,那么 javax.obex.jar 中 classes.dex 文件该值是多少呢?使
    用 010Editor 可以发现该值为 0x0008,如图 13 所示。而 kCompressStored 表示 File Data 没有
    压缩,仅仅是存储,其值为 0,kCompressDeflated 表示 File Data 经过压缩,其值为 8(两者都
    定义在/art /runtime/zip_archive.h 中),因此 13 行的 InflateToMemory 函数会被调用,该函数主

    图 13 javax.obex.jar 中 classes.dex 的 compression method 值

    要完成 File Data 的解压工作,大家可以自行分析。

    回到 DexFile::Open 函数中,至此已经完成了 classes.dex 文件的解压并映射到了内存中。
    第 7 行调用 OpenMemory 函数,该函数代码如下:

    1. const DexFile* DexFile::OpenMemory(const byte* base,size_t size,
    
    2. const std::string& location,uint32_t location_checksum, MemMap* mem_map) {
    
    3. CHECK_ALIGNED(base, 4);
    
    4. UniquePtr<DexFile> dex_file(new DexFile(base, size, location, location_checksum,
    
    mem_map));
    
    5. if (!dex_file->Init()) {
    
    6. return NULL;
    
    7. } else {
    
    8. return dex_file.release();
    
    9. }
     
    10. }
    
    


    第 4 行创建 DexFile 类的实例,完成部分成员变量的初始化,DexFile 的构造函数位于/art
    /runtime/dex_file.h 头文件中;第 5 行调用 Init 函数完成初始化,如 DEX 文件的 StringId,TypeId
    的初始化,以及 Dex 文件的一些合法性检查工作。

    DexFile::Open 函数第 9 行调用 DexFileVerifier::Verify 函数对文件进行 DEX 合法性验证;13
    行设置文件禁止写;17 行检查文件是否只读,也就是进一步判断设置禁止写是否成功。

    回到 dex2oat 中,在 26 行调用 dex_files.push_back(dex_file)将创建的 DexFile 实例存入 vector
    中;第 30-31 行将 DEX 文件设置为可写。

    总结下该部分的工作,主要完成了从 javax.obex.jar 文件中提取 classes.dex 并映射到内存,
    同时完成了 DEX 文件合法性的验证工作。

    创建 OAT 文件

    继续分析 dex2oat 的代码,接着会创建 OAT 文件,代码如下:

    1. if (!image && (Runtime::Current()->GetCompilerFilter() !=
    
    Runtime::kInterpretOnly)) {
    
    2. size_t num_methods = 0;
    
    3. for (size_t i = 0; i != dex_files.size(); ++i) {
    
    4. const DexFile* dex_file = dex_files[i];
    
    5. CHECK(dex_file != NULL);
    
    6. num_methods += dex_file->NumMethodIds();
    
    7. }
    
    8. if (num_methods <= Runtime::Current()->GetNumDexMethodsThreshold()) {
    
    9. Runtime::Current()->SetCompilerFilter(Runtime::kSpeed);
    
    10. VLOG(compiler) << "Below method threshold, compiling anyways";
    
    11. }
    
    12. }
    
    13. UniquePtr<const CompilerDriver>
    
    compiler(dex2oat->CreateOatFile(boot_image_option,host_prefix.get(),android_roo
    
    t,is_host, dex_files,oat_file.get(), bitcode_filename, image, image_classes,
    
    dump_stats,timings));
    
    


    第 2-10 行主要从 DEX 文件中获得函数个数并判断是否达到阈值;13 行调用 CreateOatFile 函
    数创建 OAT 文件,创建 OAT 文件的过程过于繁琐,就不再在此一一罗列分析(主要是我不想
    再往坑里跳了~~),其实 OAT 部分的内容是嵌在 ELF 格式的文件中的,Linux 下使用 file 命令
    来检验下/system/framework/javax.obex.jar 转换后的文件/data/dalvik-cache/system@framework
    @javax.objex.jar@classes.dex,如图 14 所示。如果顺着代码逻辑走下去的话,在/art /compiler
    /elf_writer_quick.cc 文件中(当未定义 ART_USE_PORTABLE_COMPILER 宏时会进入 ElfWriterQuick::
    Create 函数)的 ElfWriteQuick::Write 函数中有一个文件格式的说明,参考
    http://androidxref.com/4.4_r1/xref/art/compiler/elf_writer_quick.cc#46。而关于 OAT 部分的内容
    格式可以从 http://androidxref.com/4.4_r1/xref/art/runtime/oat.h 中推测出来,大致结构如图 15
    所示。

    图 14 经过转换后文件的格式

    至此完成了 DEX 文件到 OAT 文件格式的转换,当然在 dex2oat 中还有一些其他的扫尾工
    作需要完成,可以参考 http://androidxref.com/4.4_r1/xref/art/dex2oat/dex2oat.cc#561 968 行开
    始处的注释及后续代码。


    图 15 OAT 部分的文件结构(从上之下,从左至右)

    APK 文件的转换

    前文分析 dex2oat 时是针对系统库文件的转换操作,那么对于一般的 APK 文件,dex2oat
    执行的流程又是怎样的呢?这里只给出基本流程,不会去详细分析,有兴趣的可以参考相关
    PackageManagerService 的分析。

    以/system/app 下的 APK 的转换为例,在 PackageManagerService.java 存在如下代码(1285
    行-1290):

    1. File systemAppDir = new File(Environment.getRootDirectory(), "app");
    
    2. mSystemInstallObserver = new AppDirObserver(
     
    3. systemAppDir.getPath(), OBSERVER_EVENTS, true, false);
    
    4. mSystemInstallObserver.startWatching();
    
    5. scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
     
    6. | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
    


    第 1 行创建系统 APP 的目录路径,即/system/app;第 2-4 行实现监控文件夹目录;第 5 行
    调用 scanDirLI 函数,该函数会对目录下的每个 APK 文件调用 scanPackageLI 函数完成 APK
    的扫描工作。scanPackageLI 会调用 performDexOptLI 函数(4611 行)。在 performDexOptLI 中则
    会调用 Installer.dexopt 函数,后面则和前文分析/system/framework/javax.obex.jar 的转换过程
    是一样的了。基本流程如图 16 所示。

    图 16 APK 文件 OAT 格式转换过程

    总结

    本文主要分析了 Android ART Runtime 的启动以及 dex2oat 的过程,OAT 文件最终其实是
    嵌入在 ELF 文件中。由于 ART 部分代码量比较大,很多细节都忽略了,在 ART Runtime 部分
    仅仅分析到了 Runtime 的启动,由于 Runtime 使用单例模式实现,开始过程的参数解析和实
    例化对后文 dex2oat 有一定的影响,但是并没有花时间去对参数进行一一分析,所以 dex2oat
    部分可能会存在错误;另外在 dex2oat 部分没有详细分析 ELF 以及 OAT 部分内容的写入过程,
    有兴趣的可以自行深入分析!

    注:本帖由看雪论坛志愿者PEstone 重新将PDF整理排版,若和原文有出入,以原作者附件为准

    [培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

    上传的附件:
    收藏
    点赞2
    打赏
    分享
    最新回复 (13)
    雪    币: 12152
    活跃值: (3235)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    xJJuno 2014-1-11 16:02
    2
    0
    我的NEXUS5开了ART感觉也没多大变化
    而且兼容也还有问题。。。
    楼主是用IDA调戏的?
    雪    币: 287
    活跃值: (225)
    能力值: (RANK:250 )
    在线值:
    发帖
    回帖
    粉丝
    MindMac 5 2014-1-11 17:20
    3
    0
    网上有相关的测评报告,确实有兼容性问题,可以看下这个http://www.xda-developers.com/android/android-4-4-kitkats-art-and-app-compatibility/,另外有一个兼容性列表http://www.androidruntime.com/list

    分析的源代码,不需要IDA!
    雪    币: 12152
    活跃值: (3235)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    xJJuno 2014-1-11 18:42
    4
    0

    但列表毕竟是极少数
    需要个人提交的
    还以为楼主真机调试了
    雪    币: 28
    活跃值: (75)
    能力值: ( LV4,RANK:50 )
    在线值:
    发帖
    回帖
    粉丝
    往来一气 2014-1-11 19:41
    5
    0
    第一次看见art的分析,nexus7fhd开ART变化确实不大,不过4.4更新到4.4.2之后,确实感觉流畅度高了一些,兼容性差是肯定的
    雪    币: 6
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    唐金玲 2014-1-12 22:54
    6
    0
    android4.4.2 还是有些不稳定~
    雪    币: 53
    活跃值: (245)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    netsniffer 2014-1-14 10:59
    7
    0
    Posix.getenv native实现在:
    libcore\luni\src\main\native\libcore_io_Posix.cpp
    static jstring Posix_getenv(JNIEnv* env, jobject, jstring javaName) {
        ScopedUtfChars name(env, javaName);
        if (name.c_str() == NULL) {
            return NULL;
        }
        return env->NewStringUTF(getenv(name.c_str()));
    }
    注册在该文件底部:
    void register_libcore_io_Posix(JNIEnv* env) {
        jniRegisterNativeMethods(env, "libcore/io/Posix", gMethods, NELEM(gMethods));
    }
    雪    币: 287
    活跃值: (225)
    能力值: (RANK:250 )
    在线值:
    发帖
    回帖
    粉丝
    MindMac 5 2014-1-14 21:37
    8
    0
    Good! 多谢!
    雪    币: 105
    活跃值: (171)
    能力值: ( LV5,RANK:60 )
    在线值:
    发帖
    回帖
    粉丝
    lcweik 1 2015-2-27 17:43
    9
    0
    很好很强大
    雪    币: 4
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    無銘 2015-3-2 20:20
    10
    0
    哈哈 ,我也是正式会员了、。。
    雪    币: 244
    活跃值: (25)
    能力值: ( LV2,RANK:15 )
    在线值:
    发帖
    回帖
    粉丝
    bitian 2015-3-3 11:40
    11
    0
    正在学习这部分,谢谢楼主分享
    雪    币: 188
    活跃值: (12)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    米利托 2015-3-12 17:02
    12
    0
    很棒的帖子,谢谢楼主
    雪    币: 57
    活跃值: (25)
    能力值: ( LV4,RANK:50 )
    在线值:
    发帖
    回帖
    粉丝
    阿圈 1 2015-11-18 15:58
    13
    0
    不明觉厉
    雪    币: 2700
    活跃值: (63)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    wyufei 2020-7-9 07:22
    15
    0
    nice
    游客
    登录 | 注册 方可回帖
    返回