Kitkat系列文章—OAT文件分析—Part2
|
1 | /data/dalvik-cache/system@priv-app@SystemUI.apk@classes.dex |
便捷的“file”命令會(huì)返回:
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 |
這里只確定了三個(gè)標(biāo)識(shí):元數(shù)據(jù)、執(zhí)行的起始與終止地址。很明顯的,新的運(yùn)行時(shí)把應(yīng)用當(dāng)作共享對(duì)象來(lái)進(jìn)行處理(!)。共享對(duì)象被動(dòng)態(tài)加載到虛擬機(jī)的上下文(很可能是先前解釋過(guò)的啟動(dòng)鏡像)中。通過(guò)查看源可以知道:實(shí)際上在運(yùn)行時(shí)調(diào)動(dòng)dlopen()來(lái)加載這些庫(kù)。
現(xiàn)在讓我們使用新的oatdump來(lái)獲取更多關(guān)于oat文件格式的知識(shí)。我的首次嘗試是在啟動(dòng)鏡像文件“/data/dalvik-cache/system@framework@boot.art@classes.dex”中使用oatdump。但是結(jié)果顯示這個(gè)文件的整個(gè)dump文件有1.6GB大小,這對(duì)于我將嘗試的這種分析而言是十分不方便,
所以我寫了一個(gè)小程序,雖然幾乎談不上有什么具體的功能,但是卻簡(jiǎn)單到足以理解這個(gè)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; }} |
安裝之后,我們能夠在主機(jī)上查看它的編譯版本并在其上執(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 |
目前而言沒(méi)什么驚喜…它顯然是一個(gè)幾乎沒(méi)有任何功能、有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... |
這個(gè)header向我們展示了一些信息,包括文件內(nèi)容、體系結(jié)構(gòu)、一些完整性檢測(cè)值和一些想必是用來(lái)正確地移動(dòng)該庫(kù)的地址。有意思的部分在于這個(gè)dump輸出體中的方法名、dex碼和這個(gè)方法的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上存儲(chǔ)結(jié)果并返回。
OAT DATA還沒(méi)完全被處理,但明顯的是“core_spill_mask”描述了被用在那個(gè)ARM方法里面?zhèn)鬟f數(shù)據(jù)的寄存器,“vmap_table”顯示出虛擬寄存器與真實(shí)寄存器的映射關(guān)系。
CODE區(qū)域顯示處理器事實(shí)上將要執(zhí)行的東西:起初,r2持有整型a;在新的棧楨創(chuàng)建之后,常量4711回到家整型a上;之后,結(jié)果被傳回來(lái)。然見(jiàn)到這些雖然不是驚喜,但也令人印象深刻!
同時(shí)還提示:上述過(guò)程中幾乎沒(méi)有任何優(yōu)化,更像是一個(gè)“gcc-00”。顯然不需要新的棧楨,整個(gè)“計(jì)算”通過(guò)單獨(dú)的一條指令完成:adds.w r0, r2, #4711。
最后,讓我們來(lái)總結(jié)一下OAT文件是什么:OAT是類似APK的一種預(yù)編譯文件,像共享庫(kù)一樣被正在運(yùn)行的進(jìn)程加載。OAT包含了APK中所有類的信息,比如方法、方法名、描述信息和偏移列表,可以在二進(jìn)制中定位這些方法。
4、留意堆處理:ART中的GC
ATR中的垃圾回收機(jī)制跟Dalvik極其相似,兩者都采用“標(biāo)記—清除”的方式保持堆清潔。詐一看令人十分驚奇,但事實(shí)上卻十分容易理解。從Java源碼,到類、dex,再到機(jī)器碼都可以追蹤。盡管代碼執(zhí)行的方式已經(jīng)改變,但數(shù)據(jù)結(jié)構(gòu)和對(duì)象引用卻依然保持不變。因此,垃圾回收進(jìn)程可以用Dalvik相同的方式進(jìn)行回收。
對(duì)源art/runtime/gc的簡(jiǎn)單了解可以發(fā)現(xiàn)ART使用了4種不同類型的GC。它們可以并行,也可以被列舉出來(lái)釋放堆空間:
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通過(guò)上面枚舉的次序進(jìn)行循環(huán),直到有足夠可用的空間來(lái)分配需要的內(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) {... |
如果這個(gè)程序失敗了,系統(tǒng)會(huì)通過(guò)增大堆空間等方式再次嘗試分配。但這完全是一個(gè)標(biāo)準(zhǔn)程序,沒(méi)有任何不同于Dalvik垃圾回收的地方,至少我沒(méi)有發(fā)現(xiàn)。
|
|