一个boot.oat crash问题的分析

最近遇到一个手机重启的问题,日志如下:

05-18 13:42:55.553 I/AEE/AED (10514): pid: 1734, tid: 1788, name: android.ui  >>> system_server <<<
05-18 13:42:55.554 I/AEE/AED (10514): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
05-18 13:42:55.636 I/AEE/AED (10514):     x0   0000000070e4c750  x1   0000007f90aa4200  x2   0000000000000000  x3   aba39cecf7cb9912
05-18 13:42:55.636 I/AEE/AED (10514):     x4   aba39cecf7cb9912  x5   aba39cecf7cb9912  x6   0000007f9b1ba000  x7   0000007f9ee79888
05-18 13:42:55.636 I/AEE/AED (10514):     x8   aba39cecf7cb9912  x9   aba39cecf7cb9912  x10  aba39cecf7cb9912  x11  0000007f90aa4200
05-18 13:42:55.636 I/AEE/AED (10514):     x12  0000007f84f7ee40  x13  0000000000000000  x14  0000000000000030  x15  0000000000000000
05-18 13:42:55.637 I/AEE/AED (10514):     x16  00000000018c54dd  x17  0000000000000003  x18  0000007f90aa4200  x19  0000000000000001
05-18 13:42:55.637 I/AEE/AED (10514):     x20  0000000012d4a070  x21  0000000000000000  x22  0000000012d4a070  x23  0000000070e48e00
05-18 13:42:55.637 I/AEE/AED (10514):     x24  0000000000000000  x25  0000000000000000  x26  0000007f8566a8d0  x27  00000000ffffffff
05-18 13:42:55.637 I/AEE/AED (10514):     x28  0000000000157e92  x29  0000007f84f7f118  x30  000000007492bcd0
05-18 13:42:55.637 I/AEE/AED (10514):     sp   0000007f84f7eee0  pc   0000000000000000  pstate 0000000060000000
05-18 13:42:55.638 E/AEE/LIBAEE(10514): aee_try_get_word: read:1788 addr:0x0000000000000000 ret:-1, 5
05-18 13:42:55.641 I/AEE/AED (10514): 
05-18 13:42:55.641 I/AEE/AED (10514): backtrace:
05-18 13:42:55.641 I/AEE/AED (10514):     #00 pc 0000000000000000  
05-18 13:42:55.642 I/AEE/AED (10514):     #01 pc 00000000039f9ccc  /data/dalvik-cache/arm64/system@[email protected] (offset 0x257c000)
堆栈十分的短,只有两行。

Android4.4之后引入了art虚拟机,会预先把system/framework中的一些jar包转换为oat格式,这个格式实际上是elf可执行文件格式的变种。这个转换的作用是把虚拟机的字节码直接转换成cpu的机器码,这样不用在java虚拟机运行的时候解释字节码,会提高程序运行速度。

但是其实本质上还是java代码,如果是java层次的出错会抛出Exception,分析起来十分容易,如果是调用到so库出错,那么就是native crash,网上分析的文章也很多。日志这个问题比较少出现,它既不是java层面的Exception,也没有调用到任何so库,但是看起来和native crash的日志十分相像。

dump oat文件信息的程序是oatdump,例如连接上手机。

adb shell oatdump --oat-file=/data/dalvik-cache/arm64/system@[email protected]

或者在pc本地运行oatdump,不用连接手机。但是直接用这个命令dump的文件会非常大,我本地看有1G的大小,而且大部分的信息对于crash来说完全是无用的,幸好oatdump有如下参数

  --addr2instr=
: output matching method disassembled code from relative address (e.g. PC from crash dump) Example: --addr2instr=0x00001a3b
指定地址即可只dump地址附近的信息,那么这个address是什么,如何确定,是00000000039f9ccc,还是0x257c000,亦或是其它

05-18 13:42:55.642 I/AEE/AED (10514):     #01 pc 00000000039f9ccc  /data/dalvik-cache/arm64/system@[email protected] (offset 0x257c000)

估计大多数android程序员例如我面对此种问题是懵逼的感觉,幸好有万能的搜索引擎。可是网上搜索分析oat crash的可真是少,我搜索到两个网页和这个问题相关。

oat backtrace 地址 定位?


还有oat文件的 backtrace 定位


可以看出这两个文章间都有差异,知乎回答和评论间也有差异(而且回答和评论里的地址计算个人认为都是错的,作者明显是懂这个问题,但是回答太随意了)。不过感谢这两篇文章给了我分析此问题的起点。

文章中提到的maps是,/proc/(出错进程pid)/maps,这个可以看内存布局,例如在手机(烧录出问题的rom版本)我查看如下

6f653000-70520000 rw-p 00000000 fd:00 32776                              /data/dalvik-cache/arm64/system@[email protected]

70520000-72a9c000 r--p 00000000 fd:00 32774                              /data/dalvik-cache/arm64/system@[email protected]

72a9c000-75321000 r-xp 0257c000 fd:00 32774                              /data/dalvik-cache/arm64/system@[email protected]

75321000-75322000 rw-p 04e01000 fd:00 32774                              /data/dalvik-cache/arm64/system@[email protected]

这里看出日志中的0x257c000是指代码段对于整个oat文件的偏移,那么oat在内存中的偏移就是72a9c000。日志中的pc是00000000039f9ccc,按搜索文章所说要减去72a9c000的话明显感觉不对,这个个人推测应该是google或者mtk的优化,pc地址已经减去了文件基址,因为只有拿到手机才能看到maps,日志中大概率是看不到maps相关信息的,而且maps地址对于查看堆栈其实没啥意义,获取代码相对于文件的偏移才是关键。

然后兴高采烈的把0x039f9ccc作为参数去oatdump,然后又懵逼了,输出结果不对,一个汇编代码都没有。想起来搜索文章中提到过日志偏移和dump偏移有差距0x1000,查看dump文件,真的和文章中一样,dump的比日志中的0x257c000小了0x1000;

EXECUTABLE OFFSET:

0x0257b000
虽然不明白具体原理,还是把参数减少0x1000,继续尝试,但是还是同样的结果,懵逼了。oatdump整个文件看代码的地址到不了0x039f9ccc,所以当然没啥输出了。仔细查看dump文件发现了问题:

SEARCH ADDRESS (executable offset + input):

0x04af7000

addr2instr参数传入的address是会自动加上executable offset,这个估计还是优化,那么addr2instr参数传入的值应该是:代码相对于代码段的offset,不用考虑文件在内存中的偏移和代码段相对于文件的偏移,即把日志中的pc减去offset传入即可,即00000000039f9ccc -  0x257c000就是参数。获取的汇编代码片段如下,出问题的就是0x039f8ccc,即blr

      suspend point dex PC: 0x0018

      GC map objects:  v0 (r22), v18 (r20)

      0x039f8cbc: d5033bbf	dmb ish

      0x039f8cc0: d0fdc3a0	adrp x0, #-0x478a000 (addr -0xd92000)

      0x039f8cc4: f9446c00	ldr x0, [x0, #2264]

      0x039f8cc8: f940181e	ldr lr, [x0, #48]

      0x039f8ccc: d63f03c0	blr lr

      suspend point dex PC: 0x0019

继续百度arm blr指令,发现blr是直接可以控制pc的少数指令之一(好像只有两个),那么和日志中正好有某种对应:

05-18 13:42:55.641 I/AEE/AED (10514):     #00 pc 0000000000000000  
05-18 13:42:55.642 I/AEE/AED (10514):     #01 pc 00000000039f9ccc  /data/dalvik-cache/arm64/system@[email protected] (offset 0x257c000)

日志中出错点pc就是突然变为了0,到此问题算是有了一定的进展。可是汇编代码看起来还是十分头疼的,能看对应的Java代码就开心了,oatdump的文件中的对应方法是

  24: android.os.Message android.os.MessageQueue.next() (dex_method_idx=45283)
虽然不能直观的确定行数,不过对照suspend point dex PC大概可以确认在0x0018和0x0019之间,对比oatdump对应的dex代码

      0x0015: 7040 e0b0 c06d           	| invoke-direct {v0, v12, v13, v6}, void android.os.MessageQueue.nativePollOnce(long, int) // method@45280

      0x0018: 1d12                     	| monitor-enter v18

      0x0019: 7100 69b4 0000           	| invoke-static {}, long android.os.SystemClock.uptimeMillis() // method@46185
对应的Java代码

 Message next() {
        ...
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                ...
            }

            ...
        }
    }
是从synchronized开始到final long now = SystemClock.uptimeMillis();

问题到此为止没有继续分析了,原因一:真正要分析要对arm汇编和操作系统等有深厚功底的,现学现卖搞不定,对一个写app的未免有点难。原因二:这个问题只出现过一次,就算功底深厚现场也很难复现了,依据网上信息是一步步用gdb查看赋给pc的内存数据为啥会是0,然后推测原因。

本文纯属个人推测,希望有能力的同学遇到此种问题的时候能交流一下。


你可能感兴趣的:(android,boot.oat,crash,oatdump)