Kitkat系列文章—OAT文件分析—Part2
|
1 | /data/dalvik-cache/system@priv-app@SystemUI.apk@classes.dex |
便捷的“file”命令會返回:
1 2 3 4 5 6 7 8 9 10 11 12 | system@priv-app@SystemUI.apk@classes.dex: ELF 32-bit LSB shared object, ARM, version 1 (GNU/Linux), dynamically linked, strippedWow.. that escalated quickly! With ART we go from java -> class -> dex -> oat, which is a shared object!Further analysis with objdump shows the following:system@priv-app@SystemUI.apk@classes.dex:file format elf32-littleDYNAMIC SYMBOL TABLE:00001000 g DO .rodata 0007d000 oatdata0007e000 g DO .text 000a9f8f oatexec00127f8b g DO .text 00000004 oatlastword |
這里只確定了三個標(biāo)識:元數(shù)據(jù)、執(zhí)行的起始與終止地址。很明顯的,新的運行時把應(yīng)用當(dāng)作共享對象來進行處理(!)。共享對象被動態(tài)加載到虛擬機的上下文(很可能是先前解釋過的啟動鏡像)中。通過查看源可以知道:實際上在運行時調(diào)動dlopen()來加載這些庫。
現(xiàn)在讓我們使用新的oatdump來獲取更多關(guān)于oat文件格式的知識。我的首次嘗試是在啟動鏡像文件“/data/dalvik-cache/system@framework@boot.art@classes.dex”中使用oatdump。但是結(jié)果顯示這個文件的整個dump文件有1.6GB大小,這對于我將嘗試的這種分析而言是十分不方便,
所以我寫了一個小程序,雖然幾乎談不上有什么具體的功能,但是卻簡單到足以理解這個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 | package de.inovex.arttest;import android.os.Bundle;import android.app.Activity;import android.view.Menu;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); int a = 100; a = foo(a); } private int foo(int a) { return a + 4711; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; }} |
安裝之后,我們能夠在主機上查看它的編譯版本并在其上執(zhí)行objdump:
1 2 3 4 5 6 | data@app@de.inovex.arttest.apk@classes.dex: file format elf32-littleDYNAMIC SYMBOL TABLE:00001000 g DO .rodata 00001000 oatdata00002000 g DO .text 00000238 oatexec00002234 g DO .text 00000004 oatlastword |
目前而言沒什么驚喜…它顯然是一個幾乎沒有任何功能、有0×238字節(jié)的應(yīng)用程序;-)
所以,讓我在文件”oatdump –oat-file= data@app@de.inovex.arttest.apk@classes.dex”上使用oatdump:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | MAGIC:oat007CHECKSUM:0x7fcf3941INSTRUCTION SET:Thumb2DEX FILE COUNT:1EXECUTABLE OFFSET:0x00001000IMAGE FILE LOCATION OAT CHECKSUM:0xd950003dIMAGE FILE LOCATION OAT BEGIN:0x60a95000... |
這個header向我們展示了一些信息,包括文件內(nèi)容、體系結(jié)構(gòu)、一些完整性檢測值和一些想必是用來正確地移動該庫的地址。有意思的部分在于這個dump輸出體中的方法名、dex碼和這個方法的ARM拆解碼。例如:foo方法的oat-dump輸出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 1: int de.inovex.arttest.MainActivity.foo(int) (dex_method_idx=5)DEX CODE:0x0000: add-int/lit16 v0, v2, #+47110x0002: return v0OAT DATA:frame_size_in_bytes: 32core_spill_mask: 0x00008060 (r5, r6, r15)fp_spill_mask: 0x00000000vmap_table: 0xf73b00da (offset=0x000010da)v0/r5, v2/r6, v65535/r15mapping_table: 0xf73b00d8 (offset=0x000010d8)gc_map: 0xf73b00e0 (offset=0x000010e0)CODE: 0xf73b00bd (offset=0x000010bd size=28)...0xf73b00bc: e92d4060 push {r5, r6, lr}0xf73b00c0: b085 sub sp, sp, #200xf73b00c2: 9000 str r0, [sp, #0]0xf73b00c4: 9109 str r1, [sp, #36]0xf73b00c6: 1c16 mov r6, r20xf73b00c8: f2412267 movw r2, #47110xf73b00cc: eb160502 adds.w r5, r6, r20xf73b00d0: 1c28 mov r0, r50xf73b00d2: b005 add sp, sp, #200xf73b00d4: e8bd8060 pop {r5, r6, pc} |
DEXCODE部分體現(xiàn)的信息十分明顯:Java源碼中的整型a映射到虛擬寄存器v2中,加上常量4711,然后在v0上存儲結(jié)果并返回。
OAT DATA還沒完全被處理,但明顯的是“core_spill_mask”描述了被用在那個ARM方法里面?zhèn)鬟f數(shù)據(jù)的寄存器,“vmap_table”顯示出虛擬寄存器與真實寄存器的映射關(guān)系。
CODE區(qū)域顯示處理器事實上將要執(zhí)行的東西:起初,r2持有整型a;在新的棧楨創(chuàng)建之后,常量4711回到家整型a上;之后,結(jié)果被傳回來。然見到這些雖然不是驚喜,但也令人印象深刻!
同時還提示:上述過程中幾乎沒有任何優(yōu)化,更像是一個“gcc-00”。顯然不需要新的棧楨,整個“計算”通過單獨的一條指令完成:adds.w r0, r2, #4711。
最后,讓我們來總結(jié)一下OAT文件是什么:OAT是類似APK的一種預(yù)編譯文件,像共享庫一樣被正在運行的進程加載。OAT包含了APK中所有類的信息,比如方法、方法名、描述信息和偏移列表,可以在二進制中定位這些方法。
4、留意堆處理:ART中的GC
ATR中的垃圾回收機制跟Dalvik極其相似,兩者都采用“標(biāo)記—清除”的方式保持堆清潔。詐一看令人十分驚奇,但事實上卻十分容易理解。從Java源碼,到類、dex,再到機器碼都可以追蹤。盡管代碼執(zhí)行的方式已經(jīng)改變,但數(shù)據(jù)結(jié)構(gòu)和對象引用卻依然保持不變。因此,垃圾回收進程可以用Dalvik相同的方式進行回收。
對源art/runtime/gc的簡單了解可以發(fā)現(xiàn)ART使用了4種不同類型的GC。它們可以并行,也可以被列舉出來釋放堆空間:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // The type of collection to be performed. The ordering of the enum matters, it// is used to// determine which GCs are run first.enum GcType { // Placeholder for when no GC has been performed. kGcTypeNone, // Sticky mark bits GC that attempts to only free objects allocated since // the last GC. kGcTypeSticky, // Partial GC that marks the application heap but not the Zygote. kGcTypePartial, // Full GC that marks and frees in both the application and Zygote heap. kGcTypeFull, // Number of different GC types. kGcTypeMax,}; |
GC通過上面枚舉的次序進行循環(huán),直到有足夠可用的空間來分配需要的內(nèi)存:
1 2 3 4 | art/runtime/gc/heap.cc// Loop through our different Gc types and try to Gc until we get enough free memory.for (size_t i = static_cast<size_t>(last_gc) + 1;i < static_cast<size_t>(collector::kGcTypeMax); ++i) {... |
如果這個程序失敗了,系統(tǒng)會通過增大堆空間等方式再次嘗試分配。但這完全是一個標(biāo)準(zhǔn)程序,沒有任何不同于Dalvik垃圾回收的地方,至少我沒有發(fā)現(xiàn)。
|
|