Android ART 分析

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

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

https://bbs.pediy.com/thread-183668.htm

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 各字段及大小
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
字段      字节数
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 各字段和大小
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
字段             字节数  
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 各字段和大小
1
2
3
4
5
6
7
8
9
10
字段       字节数
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
2
3
4
5
6
7
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
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
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
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. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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. 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
2
3
4
5
6
7
8
9
10
11
12
13
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
2
3
4
5
6
7
8
9
10
11
12
13
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
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. 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
2
3
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
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
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
2
3
4
5
6
7
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
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
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
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. 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 命
令。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值