最近遇到一个手机重启的问题,日志如下:
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]
然后兴高采烈的把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
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,然后推测原因。
本文纯属个人推测,希望有能力的同学遇到此种问题的时候能交流一下。