Dalvik class加载的处理
Java 源代碼經過編譯后會生成后綴為 class 的文件,也即字節碼文件。然后在 Android 中使用 dx 工具將其轉換為后綴為 jar 的 dex 類型文件。 Dalvik 虛擬機負責解釋并執行編譯后的字節碼。在解釋執行字節碼之前,當然要讀取 文件,分析文件的內容,得到字節碼,然后才能解釋執行之。在整個的加載過程中,最為重要的就是對 Class 的加載 – Class 包含 Method , Method 又包含 code 。通過對 Class 的加載,我們即可獲得所需執行的字節碼。
?
本文 從 dexfile 文件分析及 Class 加載中的數據結構入手,結合主要流程,對整個加載過程進行分析,期望對大 家有所幫助。
?
?
1. DexFile 在內存中的映射
?
在 Android 系統中, java 源文件會被編譯為后綴為 jar 的 dex 類 型文件,在代碼中稱為 dexfile 。在加載 Class 之前,必先讀取相應的 jar 文件。通常我們使用 read 函數來讀取文件中的內容。但在 Dalvik 中使用 mmap 函數,和 read 不同, mmap 函數會將 dex 文件映射到內存中,這樣通過普通的內存讀取操作即可訪問 dex file 中的內容。
?
Dexfile 的文件格式如下圖所示,主要有三部分組成: 頭部,索引,數據。通過頭部可知索引的位置和數目,可知數據區的起始位置。其中 classDefsOff 指定了 ClassDef 在文件的起始位置, dataOff 指定了數據在文件的起始位置, ClassDef 即可理解為 Class 的索引。通過讀取 ClassDef 可獲知 Class 的基本信息,其中 classDataOff 指定了 Class 數據在數據區的位置。
?
在將 dexfile 文件映射到內存后,即會調用 dexFileParse 函數對其分析,分析的結果存放于名為 DexFile 的數據結構中。 DexFile 中的 baseAddr 指向映射區的起始位置, pClassDefs 指向 ClassDefs (即 class 索引)的起始位置。由于在查找 class 時,都是使用 class 的名字進行查找,為了加快查找速度,創建了一個 hash 表。在 hash 表中對 class 名字進行 hash ,并生成 index 。這些操作都是在對文件解析時所完成的,這樣雖然在加載過程中比較耗時, 但是在運行過程中確可節省大量查找時間。
?
?
?
?
?
?
2. ClassObject -? Class 在加載后的表現形式
?
在對文件解析完成后就要加載 Class 的具體內容了!在 Dalvik 中,由 ClassObject 這個數據結構負責存放加載的信息。如下圖所示,加載過程會在內存中 alloc 幾個區域,分別存放 directMethods, virtualMethods, sfields, ifields 。這些信息 正是從 dex 文件的數據區中讀取。首先會讀取 Class 的詳細信息,從中獲知 directMethod, virtualMethod, sfield, ifield 等的信息,然后 再讀取。下圖為加載完成后的示意。 這里并未介紹加載的每個細節,感興趣的同學可通過此二圖自行分析。
還請大家注意的是在 ClassObject 結構中有個名為 super 的成員。通過 super 成員來指向它的超類。
?
?
?
3. findClassNoInit – 負責 加載 Class 并生成相應 ClassObject 的函數。
?
第二 節介紹了加載后的數據結構,本節會分析負責加載工作的函數 - findClassNoInit 。 請 注意在獲取 Class 索引時,會分為基本類庫文件和用戶類文件兩種情況。在 Dalvik 分析之準備篇中, grund.sh 中有一語句 ?? “ export BOOTCLASSPATH=$bootpath/core.jar:$bootpath/ext.jar:$bootpath/framework.jar:$bootpath/android.police.jar” 。 這條語句指定了 Dalvik 所需的基本庫文件,如果沒有此語句, Dalvik 在啟動過程中就會報錯退出。
?
LoadClassFromDex 函數先會讀取 Class 的具體數據(從 ClassDataoff 處),然后按圖索驥,分別加載 directMethod , virtualMethod , ifield , sfield 。
?
作為 業界 TOP 公司出品的東東,當然要關注執行效率了 ^_^ 。首先,在加載后我們要將其緩存起來,以便以后使用方便。其次,在查找過 程中,如果我們順序查找的話,當然是很慢的拉。這當然是俺們 senior engineer 所不能容忍的,所以 gDvm.loadedClasses 這個 Hash 表就隆重出場了。什么,這位同學說沒有幾個 Class ,至于這么大動干戈么。讓我們看看,通過在準備篇中介紹的方法,我們在 main.c 249 行設置斷點,這個時候基本庫已加載完畢。當程序停下時,我們看看 gDvm 的值,可以看到 numLoadedClasses 這個成員的值為 212 !也即意味著這個時候我們什么也沒做,用戶類也未加載, Dalvik 已加載的 Class 數目已達到 212 。
?
dvmLinkClass , 好長,但是其最終好像會再次調用 findClassNoInit 。嗯,也是可以理解的嘛。如果一個子類需要調用超類的函數,那它當然要先 加載超類了,可能的話,甚至會加載超類的超類 ^_^ 。 ?
?
?
?
?
眼見 為虛,實踐出真知,拿 gdb 調試下。
在 findClassNoInit 函數處設置斷點 ( 在 gdb 提示符后輸入 ”b findClassNoInit”) ,在 gdb 提 示符后連續幾次執行 ”c” 和 ”bt” 。可出現如下信息,可以看到在函數調用棧上可以多次看到 findClassNoInit 的身影。
?
(gdb) bt
#0? findClassNoInit (descriptor=0xfef4c7f4 "??????%", loader=0x0, pDvmDex=0x0)
?? ?at dalvik/vm/oo/Class.c:1373
#1? 0xf6fc4d53 in dvmFindClassNoInit (descriptor=0xf5046a63 "Ljava/lang/Object;", loader=0x0)
??? at dalvik/vm/oo/Class.c:1194
#2? 0xf6fc6c0a in dvmResolveClass (referrer=0xf5837400, classIdx=290,
??? fromUnverifiedConstant=false) at dalvik/vm/oo/Resolve.c:94
#3? 0xf6fc3476 in dvmLinkClass (clazz=0xf5837400, classesResolved=false)
??? at dalvik/vm/oo/Class.c:2537
#4? 0xf6fc1b67 in findClassNoInit (descriptor=0xf6ff0df6 "Ljava/lang/Class;", loader=0x0,
??? pDvmDex=0xa04c720) at dalvik/vm/oo/Class.c:1489
?
?
現在 從另一個角度去觀察。在 class.c 2575 行設置斷點,然后等待程序停下。看下 clazz 的內容。
(gdb) p clazz->super->descriptor
$6 = 0xf5046a63 "Ljava/lang/Object;"
(gdb) p clazz->descriptor
$7 = 0xf5046121 "Ljava/lang/Class;"
?
?
4. 基本類庫文件的加載
?
先在 findClassNoInit 函數處設置斷點,然后運行程序,等待程序的停下。
?
(gdb) b findClassNoInit
Breakpoint 2 at 0xf6fc13e0: file dalvik/vm/oo/Class.c, line 1373.
(gdb) c
Continuing.
?
看看 誰是第一個加載的 Class ,及其調用關系。
?
(gdb) bt
#0? findClassNoInit (descriptor=0x0, loader=0x0, pDvmDex=0x0) at dalvik/vm/oo/Class.c:1373
#1? 0xf6fc32a1 in dvmLinkClass (clazz=0xf5837350, classesResolved=false)
??? at dalvik/vm/oo/Class.c:2491
#2? 0xf6fc1b67 in findClassNoInit (descriptor=0xf6ff1ded "Ljava/lang/Thread;", loader=0x0,
??? pDvmDex=0xa04c720) at dalvik/vm/oo/Class.c:1489
#3? 0xf6f92692 in dvmThreadObjStartup () at dalvik/vm/Thread.c:328
#4? 0xf6f800e6 in dvmStartup (argc=2, argv=0xa041190, ignoreUnrecognized=false, pEnv=0xa0411a0)
??? at dalvik/vm/Init.c:1155
#5? 0xf6f8b8e3 in JNI_CreateJavaVM (p_vm=0xf6ff0df6, p_env=0xf6ff0df6, vm_args=0xfef4d0b0)
??? at dalvik/vm/Jni.c:4198
#6? 0x08048893 in main (argc=3, argv=0xfef4d168) at dalvik/dalvikvm/Main.c:212
?
函數 調用順序清晰可見: main -> JNI_CreateJavaVM-> dvmStartup-> dvmThreadObjStartup-> dvmFindSystemClassNoInit-> findClassNoInit 觀察仔細的同學可能會問在調用棧中沒有看到 dvmFindSystemClassNoInit 啊,為何你這樣寫啊?我估計編譯器將其作為 inline 優化了,導致 gdb 看不到有 dvmFindSystemClassNoInit 的棧。從回溯棧中也可清晰的看到
?
?
5. 用戶類文件的加載
用戶 類文件的加載頗為曲折,它會先加載一個 Class 。然后這個 Class 去負責用戶類文件的加載,當然了,這個 Class 又會通過 JNI 的方式去曲折調用到 findClassNoInit 。這個就留給大家自己分析了, 其實樓主自己也不是很明白為什么要這么大費周折 ^_^ 。
總結
以上是生活随笔為你收集整理的Dalvik class加载的处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iphone SHSH证书
- 下一篇: Dalvik分析(一)