Kitkat系列文章—OAT文件分析—Part2

这是关于最新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
[ 转载请保留原文出处、译者和译文链接。]

关于作者: Peter Pan

(新浪微博:@whuLittlePan

查看Peter Pan的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部