原文地址:caikelun.io/post/2019-0…android
对于程序的崩溃问题,不管是面对 Linux 的 coredump 仍是 Android 的 tombstone,咱们最终都须要进行俗称为 “验尸” 的过程。有时候,咱们会遇到一些堪称完美的 “犯罪现场”。git
Signal: 7 (SIGBUS), Code: 2 (BUS_ADRERR), fault addr 0xcd596372
r0 0000006e r1 00000000 r2 00000000 r3 00000000
r4 f35bb740 r5 f35bb748 r6 cd3ff8a8 r7 00000064
r8 f7451e34 r9 f35bb708 r10 cd301000 r11 cd5a3ac5
ip 00000000 sp cd3ff890 lr 00000000 pc cd596372
#00 pc 0009a372 /data/data/com.package.name/files/download/libCube.so
#01 pc 000a4f69 /data/data/com.package.name/files/download/libCube.so
#02 pc 000a513f /data/data/com.package.name/files/download/libCube.so
#03 pc 000a7ab9 /data/data/com.package.name/files/download/libCube.so
#04 pc 000a7abf /data/data/com.package.name/files/download/libCube.so
#05 pc 000a7add /data/data/com.package.name/files/download/libCube.so
#06 pc 0004078b /system/lib/libc.so (_ZL15__pthread_startPv+30)
#07 pc 0001a031 /system/lib/libc.so (__start_thread+6)
#00 cd3ff890 cd3ff8dc
cd3ff894 5d419f43
cd3ff898 cd3ff8a0
cd3ff89c f35bb744 [anon:libc_malloc]
cd3ff8a0 5d419f43
cd3ff8a4 000a5952
cd3ff8a8 5d419f43
cd3ff8ac 2e62c950 /dev/ashmem/dalvik-main space (deleted)
cd3ff8b0 00000064
cd3ff8b4 f35bb700 [anon:libc_malloc]
cd3ff8b8 f35bb750 [anon:libc_malloc]
cd3ff8bc 00000064
cd3ff8c0 cd3ff8dc
cd3ff8c4 cd5a0f6d /data/data/com.package.name/files/download/libCube.so
#01 cd3ff8c8 cd5a3ac5 /data/data/com.package.name/files/download/libCube.so
cd3ff8cc 00000000
cd3ff8d0 00000001
cd3ff8d4 262237a1 /dev/ashmem/dalvik-main space (deleted)
cd3ff8d8 00000000
......
......
memory near pc:
cd596350 43591889 60714b11 dd054299 33019b01 ..YC.Kq`.B.....3
cd596360 4b0f9306 607118c9 1c299803 f0251c32 ...K..q`..).2.%.
cd596370 286ef8d5 7b23d003 d0f52b00 2601e002 ..n(..#{.+.....&
cd596380 e0004276 78232600 d1002b00 1c287323 vB...&#x.+..#s(.
cd596390 fd84f024 b0091c30 46c0bdf0 3b9ac9ff $...0......F...;
cd5963a0 c4653600 1c0cb510 f8c0f025 d1022800 .6e.....%....(..
cd5963b0 20016020 f024e004 6800fcd1 20006020 `. ..$....h `.
cd5963c0 b570bd10 26006803 3b0cb09a 1c0c681b ..p..h.&...;.h..
cd5963d0 d10342b3 600a2202 e0161c18 6800600e .B...".`.....`.h cd5963e0 f0244669 2800fef3 f024d005 6800fcb7 iF$....(..$....h cd5963f0 1c306020 9b04e009 021222f0 2380401a `0......"...@.#
cd596400 429a021b 6020d101 b01a2001 b538bd70 ...B.. `. ..p.8.
cd596410 1c0d6800 3b0c1c03 2c00681c 2302d102 .h.....;.h.,...#
cd596420 e00d600b feeaf024 d0051e04 602b2300 .`..$........#+`
cd596430 fef4f024 e0042001 fc90f024 60286800 $.... ..$....h(`
cd596440 bd381c20 6803b570 1c0d1c06 681c3b0c .8.p..h.....;.h
......
......
cd4fc000-cd5e9000 r-x 0 ed000 /data/data/com.package.name/files/download/libCube.so
cd5e9000-cd5ee000 r-- ec000 5000 /data/data/com.package.name/files/download/libCube.so
cd5ee000-cd5ef000 rw- f1000 1000 /data/data/com.package.name/files/download/libCube.so
......
复制代码
.text:0009A368 LDR R0, [SP,#0x38+var_2C]
.text:0009A36A MOVS R1, R5
.text:0009A36C MOVS R2, R6
.text:0009A36E BL j_pthread_cond_timedwait
.text:0009A372 CMP R0, #0x6E ;在这个位置发生了 SIGBUS
.text:0009A374 BEQ loc_9A37E
复制代码
崩溃位置在 libCube.so 的 offset 0009a372
处,根据 arm32 fastcall 调用约定,这里在 pthread_cond_timedwait()
函数返回后,检查 r0
中的返回值是否为 6e
(十进制:110
),查 pthread_cond_timedwait()
manpage 和 Linux errno list 得知:pthread_cond_timedwait()
返回 110(ETIMEDOUT) 表示超时。github
这是一个正常的调用,没有问题。那为何会 SIGBUS 呢?难道是偶发的 NAND Flash 硬件故障?或者是驱动的 bug ?bash
从 xCrash 捕获的崩溃信息中能够看到, 当前 pc 位置(0009a372
)的二进制指令码是 286e
,恰好对应 CMP R0, #0x6E
,这里的指令码数据是经过 ptrace()
获取的,xCrash 能获取到可是 CPU 执行这条指令时却获取不到?app
Signal: 11 (SIGSEGV), Code: 1 (SEGV_MAPERR), fault addr 0x11d868
r0 bf206648 r1 00000000 r2 00000000 r3 00000000
r4 bf206640 r5 bf206648 r6 ffffffff r7 000001b9
r8 d44bf980 r9 bf206608 r10 b1654000 r11 b21bd89d
ip b240ed4c sp b1752890 lr b21a66b9 pc 0011d868
#00 pc 0011d868 <unknown>
#01 pc 005586b5 /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN3psl5Event4WaitEi+160)
#02 pc 0056fe55 /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN8BaseHcdn6Thread9ThreadRunEv+122)
#03 pc 0057002b /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN8BaseHcdn10CThreadObj14ThreadWorkFuncEv+6)
#04 pc 0056f891 /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN3psl13CThreadObject14ThreadBaseFuncEv+26)
#05 pc 0056f897 /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN3psl13CThreadObject3RunEv+2)
#06 pc 0056f8b5 /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN3psl10ThreadBaseINS_13CThreadObjectEE10ThreadProcEPv+24)
#07 pc 00040aab /system/lib/libc.so (_ZL15__pthread_startPv+30)
#08 pc 0001a011 /system/lib/libc.so (__start_thread+6)
#00 b1752890 b17528dc
........ ........
#01 b1752890 b17528dc
b1752894 5d419f83
b1752898 b17528a0
b175289c bf206644 [anon:libc_malloc]
b17528a0 5d419f83
b17528a4 000cf94e
b17528a8 5d419f84
b17528ac 115c2ef0
b17528b0 000001b9
b17528b4 bf206600 [anon:libc_malloc]
b17528b8 bf206650 [anon:libc_malloc]
b17528bc 000001b9
b17528c0 b17528dc
b17528c4 b21bde59 /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN8BaseHcdn6Thread9ThreadRunEv+126)
#02 b17528c8 b21bd89d /data/data/com.package.name/files/download/libHCDNClientNet.so (_ZN3psl10ThreadBaseINS_13CThreadObjectEE10ThreadProcEPv)
b17528cc 00000000
b17528d0 00000001
b17528d4 0730c6a9
b17528d8 00000000
b17528dc bf20663c [anon:libc_malloc]
b17528e0 00000000
b17528e4 bf206600 [anon:libc_malloc]
b17528e8 b1752970
b17528ec b1752930
......
......
f73d0000-f7444000 r-x 0 74000 /system/lib/libc.so
f7444000-f7448000 r-- 73000 4000 /system/lib/libc.so
f7448000-f744b000 rw- 77000 3000 /system/lib/libc.so
......
b1c4e000-b23ef000 r-x 0 7a1000 /data/data/com.package.name/files/download/libHCDNClientNet.so
b23ef000-b23f0000 --- 0 1000
b23f0000-b240f000 r-- 7a1000 1f000 /data/data/com.package.name/files/download/libHCDNClientNet.so
b240f000-b241a000 rw- 7c0000 b000 /data/data/com.package.name/files/download/libHCDNClientNet.so
......
复制代码
这是一个段错误,pc 的值是 0011d868
,这个地址显然是异常的,咱们俗称 pc “跑飞了”。backtrace 第二行中,libHCDNClientNet.so 的 offset 005586b5
对应的指令以下:函数
.text:005586B2 MOVS R0, R5
.text:005586B4 BL j_pthread_mutex_unlock ;这里
.text:005586B8 MOVS R0, R6
复制代码
这是对 libc.so 中 pthread_mutex_unlock()
函数的调用,这里不该该有问题。难道是 linker 的 bug?pthread_mutex_unlock()
的绝对地址应该由 linker 写入 libHCDNClientNet.so 的 GOT 中。post
此类怪异的崩溃现象还有一些,这里不继续罗列了。ui
问题到底出在哪里?google
通过总结,发现上述的问题有如下几个特征:spa
files
目录中)不管是安卓系统的 debuggerd,仍是 xCrash,或者 Breakpad,在捕获 native 崩溃时,都会经历如下几个阶段:
clone()
子进程。ptrace()
attach 到崩溃进程的各个线程。在子进程 attach 到全部崩溃进程的线程以前,崩溃进程仍然还在执行。若是考虑安卓 app 的多进程状况,那么在整个崩溃捕获期间,app 的其余进程也均可能在执行中。
崩溃捕获的机制也是由代码实现的,它们须要被一步一步的执行。咱们能够想象:当案件(崩溃)发生时,警察(崩溃捕获机制)赶到案发现场是须要必定的时间的,这段时间就是 “视觉的盲区”。
对于咱们前面描述的崩溃问题,在进程崩溃以后,但在 xCrash 开始执行并记录崩溃信息以前,必定还发生了一些咱们所不知道的事情,案发现场的某些细节被篡改了!
回想上述的 “现象1”,指令码 286e
为何 xCrash 能获取到,可是 CPU 执行时却获取不到而发生了 SIGBUS?
也许缘由很简单:CPU 的执行流水线从内存加载这条指令时,so 库文件的这个 offset 位置确实不可读,因而发生了 SIGBUS;以后 xCrash 开始捕获崩溃信息,当 xCrash 用 ptrace()
从同一个内存位置读取数据时,so 库文件的这个 offset 位置又从新变的可读了。
最大的可能性是:so 库文件在已经被 linker 加载并开始执行的状况下,又再次被覆写了。也许是 so 库在动态下载、解压、校验的某些环节存在 bug,致使内部状态发生了异常。
通过仔细的分析,咱们确实发现了一些间接的证据:
咱们发了大量的 “崩溃 so 库文件的最后修改时间” 晚于 “崩溃时间点” 的状况。相似下面的状况:
process start time : 2019-08-01 01:54:51.519 +0800
process crash time : 2019-08-01 01:54:53.743 +0800
so last modify time : 2019-08-01 01:54:53.938 +0800
复制代码
因为动态下载的 so 库咱们是能够从服务端可靠的获取的,咱们比对了 xCrash 记录的崩溃 so 库的文件大小和 MD5 hash。发现有一部分确实是不对的,这部分状况应该就属于:“直到 xCrash 开始记录崩溃信息的时候,so 库被错误覆盖的过程尚未彻底结束”。
经过分析 open files 列表,咱们看到在有些状况下,崩溃进程仍然持有有效的 FD 可用于访问动态下载的 so 库所属的 zip 包:
Open files:
......
fd 57: /data/data/com.package.name/files/download/fullso.zip
......
复制代码
有些 FD 还指向 zip 包的 crc 文件:
Open files:
......
fd 58: /data/data/com.package.name/files/download/fullso.zip.crc
......
复制代码
结论已经比较明显了:so 库在动态下载、解压、校验的某些环节存在 bug,致使 so 库文件在已经被 linker 加载并开始执行的状况下,又再次被覆写了。
后续:咱们的 so 库动态下发流程作了改进,增长了更多防护性的设计。咱们从 APM 线上崩溃数据的统计中看到,相似前述 “现象1” 和 “现象2” 的崩溃几乎都没有了,APP 的总体 native 崩溃率也进一步的下降了。