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. ......
相關文章
相關標籤/搜索