[转]Kitkat系列文章—OAT文件分析—Part2

本文由 ImportNew - Peter Pan 翻译自 google。欢迎加入翻译小组。转载请参见文章末尾的要求。

这是关于最新Android版本Kitkat系列文章中的第二部分,我把它们写在了+inovex GmbH上。在这一部分,我们将进一步查看这种新的文件格式:ART运行时的OAT,并且简要看一下它的垃圾回收机制(可以在这查看第一部分)。

3、进一步挖掘:OAT文件分析

目前为止我们发现系统在设备上执行了一些编译。不仅是应用,而且还有Android框架层的很大一部分被ART转化成了oat文件。在这篇文章中,我们会努力找出oat到底是什么东西以及oat文件是如何生成的。

正如前提到的,所有已安装应用通过dex2oat编译从而得已运行。现在让我们进一步查看一下这些生成的文件。那么,让我们通过adb分析dex2oat后的一个结果,例如这个由SystemUI.apk转化后的结果:

/data/dalvik-cache/system@priv-app@SystemUI.apk@classes.dex

便捷的“file”命令会返回:

 

system@priv-app@SystemUI.apk@classes.dex: ELF 32-bit LSB shared object, ARM, version 1 (GNU/Linux), dynamically linked, stripped

Wow.. 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-little

DYNAMIC SYMBOL TABLE:

00001000 g DO .rodata 0007d000 oatdata

0007e000 g DO .text 000a9f8f oatexec

00127f8b g DO .text 00000004 oatlastword

这里只确定了三个标识:元数据、执行的起始与终止地址。很明显的,新的运行时把应用当作共享对象来进行处理(!)。共享对象被动态加载到虚拟机的上下文(很可能是先前解释过的启动镜像)中。通过查看源可以知道:实际上在运行时调动dlopen()来加载这些库。

现在让我们使用新的oatdump来获取更多关于oat文件格式的知识。我的首次尝试是在启动镜像文件“/data/dalvik-cache /system@framework@boot.art@classes.dex”中使用oatdump。但是结果显示这个文件的整个dump文件有 1.6GB大小,这对于我将尝试的这种分析而言是十分不方便,
所以我写了一个小程序,虽然几乎谈不上有什么具体的功能,但是却简单到足以理解这个OAT是如何工作的。源代码如下:

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;

    }

}

安装之后,我们能够在主机上查看它的编译版本并在其上执行objdump:

 

data@app@de.inovex.arttest.apk@classes.dex: file format elf32-little

DYNAMIC SYMBOL TABLE:

00001000 g DO .rodata 00001000 oatdata

00002000 g DO .text 00000238 oatexec

00002234 g DO .text 00000004 oatlastword

目前而言没什么惊喜…它显然是一个几乎没有任何功能、有0×238字节的应用程序;-)

所以,让我在文件”oatdump –oat-file= data@app@de.inovex.arttest.apk@classes.dex”上使用oatdump:

 

MAGIC:

oat

007

CHECKSUM:

0x7fcf3941

INSTRUCTION SET:

Thumb2

DEX FILE COUNT:

1

EXECUTABLE OFFSET:

0x00001000

IMAGE FILE LOCATION OAT CHECKSUM:

0xd950003d

IMAGE FILE LOCATION OAT BEGIN:

0x60a95000

...

这个header向我们展示了一些信息,包括文件内容、体系结构、一些完整性检测值和一些想必是用来正确地移动该库的地址。有意思的部分在于这个dump输出体中的方法名、dex码和这个方法的ARM拆解码。例如:foo方法的oat-dump输出如下:

1: int de.inovex.arttest.MainActivity.foo(int) (dex_method_idx=5)

DEX CODE:

0x0000: add-int/lit16 v0, v2, #+4711

0x0002: return v0

OAT DATA:

frame_size_in_bytes: 32

core_spill_mask: 0x00008060 (r5, r6, r15)

fp_spill_mask: 0x00000000

vmap_table: 0xf73b00da (offset=0x000010da)

v0/r5, v2/r6, v65535/r15

mapping_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, #20

0xf73b00c2: 9000 str r0, [sp, #0]

0xf73b00c4: 9109 str r1, [sp, #36]

0xf73b00c6: 1c16 mov r6, r2

0xf73b00c8: f2412267 movw r2, #4711

0xf73b00cc: eb160502 adds.w r5, r6, r2

0xf73b00d0: 1c28 mov r0, r5

0xf73b00d2: b005 add sp, sp, #20

0xf73b00d4: e8bd8060 pop {r5, r6, pc}

DEXCODE部分体现的信息十分明显:Java源码中的整型a映射到虚拟寄存器v2中,加上常量4711,然后在v0上存储结果并返回。

OAT DATA还没完全被处理,但明显的是“core_spill_mask”描述了被用在那个ARM方法里面传递数据的寄存器,“vmap_table”显示出虚拟寄存器与真实寄存器的映射关系。

CODE区域显示处理器事实上将要执行的东西:起初,r2持有整型a;在新的栈桢创建之后,常量4711回到家整型a上;之后,结果被传回来。然见到这些虽然不是惊喜,但也令人印象深刻!

同时还提示:上述过程中几乎没有任何优化,更像是一个“gcc-00”。显然不需要新的栈桢,整个“计算”通过单独的一条指令完成:adds.w r0, r2, #4711。

最后,让我们来总结一下OAT文件是什么:OAT是类似APK的一种预编译文件,像共享库一样被正在运行的进程加载。OAT包含了APK中所有类的信息,比如方法、方法名、描述信息和偏移列表,可以在二进制中定位这些方法。

4、留意堆处理:ART中的GC

ATR中的垃圾回收机制跟Dalvik极其相似,两者都采用“标记—清除”的方式保持堆清洁。诈一看令人十分惊奇,但事实上却十分容易理解。从 Java源码,到类、dex,再到机器码都可以追踪。尽管代码执行的方式已经改变,但数据结构和对象引用却依然保持不变。因此,垃圾回收进程可以用 Dalvik相同的方式进行回收。

对源art/runtime/gc的简单了解可以发现ART使用了4种不同类型的GC。它们可以并行,也可以被列举出来释放堆空间:

 

// 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通过上面枚举的次序进行循环,直到有足够可用的空间来分配需要的内存:

 

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) {...

如果这个程序失败了,系统会通过增大堆空间等方式再次尝试分配。但这完全是一个标准程序,没有任何不同于Dalvik垃圾回收的地方,至少我没有发现。


原文链接: google 翻译: ImportNew.com - Peter Pan
译文链接: http://www.importnew.com/8825.html

posted @ 2014-12-26 10:58  小V爱科技  阅读(1113)  评论(0编辑  收藏  举报