dex2oat

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

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


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

参数解析

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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][email protected]@boot.art[/email] 以及 [email][email protected]@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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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
    2
    3
    4
    5
    6
    7
    8
    9
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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
    1. explicit ZipArchive(int fd) : fd_(fd), num_entries_(0), dir_offset_(0) {}


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    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
    Android Art分析2
    图 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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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 函数,该函数部分代
    Android Art分析2
    图 12 javax.obex.jar 解压后文件

    码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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
    2
    3
    4
    5
    1. uint32_t ZipEntry::GetUncompressedLength() {
      
    2. return Le32ToHost(ptr_ + ZipArchive::kCDEUncompLen);
     
    3. }


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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 函数会被调用,该函数主
    Android Art分析2
    图 13 javax.obex.jar 中 classes.dex 的 compression method 值

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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/[email protected]
    @[email protected],如图 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
    所示。
    Android Art分析2
    图 14 经过转换后文件的格式

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

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

    APK 文件的转换

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1. File systemAppDir = new File(Environment.getRootDirectory(), "app");
     
    2. mSystemInstallObserver = new AppDirObserver(
      
    3. systemAppDir.getPath(), OBSERVER_EVENTS, truefalse);
     
    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 所示。
    Android Art分析2
    图 16 APK 文件 OAT 格式转换过程

    总结

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

    • 2021-08-01
    • 2021-10-26
    • 2022-02-11
    • 2021-08-23
    • 2021-10-30
    • 2021-06-06
    • 2021-11-02
    猜你喜欢
    • 2021-08-14
    • 2021-07-22
    • 2021-06-19
    • 2021-09-26
    • 2022-12-23
    • 2022-12-23
    相关资源
    相似解决方案