本节书摘来自华章出版社《深入理解Android》一书中的第3章,第3.3节,作者孟德国 王耀龙 周金利 黎欢,更多章节内容可以访问云栖社区“华章计算机”公众号查看
3.3 Assert与crash dump
Assert(断言)在C/C++语言开发的程序中使用广泛,尤其是在一些单测框架(比如gtest)中,通过它C/C++宏的强大可窥一斑。断言通常用来检测程序的运行状态和程序运行的健康状况,并在错误发生时进行适当的处理。
由于WebKit被设计用于许多嵌入式平台,为提高其平台适应性,应该尽量减少对编译器高级特性的依赖,比如异常、dynamic_cast等。而WTF中提供的一系列Assert相关的宏,就起到了异常检查的作用,只不过发生异常时(或者说程序运行状态错误时)没有异常的层层上抛动作,也不像使用setjump和longjump那样跳转到特定的处理函数,而通常只是触发crash或debugger。这部分断言表达式,通过条件编译的控制,只会在Debug版中起作用,而在正式的Release版中不会存在,因此不会增加代码的体积。
3.3.1 Assert的实现及使用
下面是 WTF中Assert的定义:
【→Assertions.h】
#define ASSERT(assertion) do \
if (!(assertion)) { \
WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, #assertion); \
CRASH(); \
} \
while (0)
其中,WTFReportAssertionFailure输出日志不是主要的控制逻辑。下面我们看一下CRASH:
【→Assertions.h】
#define CRASH() do { \
WTFReportBacktrace(); \
WTFInvokeCrashHook(); \
*(int *)(uintptr_t)0xbbadbeef = 0; \
((void(*)())0)(); /* More reliable, but doesn't say BBADBEEF */ \
} while (false)
WTFReportBacktrace();在Android下是空函数,真正触发CRASH的是非法内存操作—(int )(uintptr_t)0xbbadbeef = 0; ((void(*)())0)()。3.3.2节会附上Assert失败时产生的crash信息,可以看到发生crash的地址就在0xbbadbeef。
其他Assert宏的实现也与此类似,差别仅仅在于断言的逻辑条件不同而已。
3.3.2 crash dump的实现及使用
crash dump 涉及每一个Android进程,它的通用实现机制如下:
1)Android的linker为每一个进程的fatal signals设置了处理函数,代码如下:
【→debugger.c】
void debugger_init()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = debugger_signal_handler;
act.sa_flags = SA_RESTART | SA_SIGINFO;
sigemptyset(&act.sa_mask);
sigaction(SIGILL, &act, NULL);
sigaction(SIGABRT, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGSEGV, &act, NULL);
#if defined(SIGSTKFLT)
sigaction(SIGSTKFLT, &act, NULL);
#endif
sigaction(SIGPIPE, &act, NULL);
}
2)进程或线程在运行过程中收到上述信号时,对应的信号处理函数将通过socket报告signal处理信息给debuggerd。
【→debugger.c】
tid = gettid();
s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM);
if (s >= 0) {
int ret;
debugger_msg_t msg;
msg.action = DEBUGGER_ACTION_CRASH;
msg.tid = tid;
RETRY_ON_EINTR(ret, write(s, &msg, sizeof(msg)));
……
}
3) debuggerd是root用户进程,它通过socket接收产生信号的进程(或者进程中的某个线程)发来的debugger_msg_t后,通过ptrace(PTRACE_ATTACH, request.tid,0,0)跟踪跟进程(或线程), 如果attach成功,则进一步根据信号的类型决定处理手段。最终产生crash dump信息的函数是tombstone.c中的dump_crash,它输出crash信息到系统log以及/data/tombstones/目录中,这就是我们平时说的native crash。
一旦发生crash我们可以得到如下形式的crash dump 信息:
//关键点(1)
Build fingerprint: 'samsung/m0zn/m0chn:4.1.2/JZO54K/I9300ZNEMD2:user/ release-keys'
//关键点(2)
pid: 23415, tid: 23559, name: WebViewCoreThre >>> xxxxxxx.testBrowser <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr bbadbeef //关键点(3)
r0 bbadbeef r1 5348ff68 r2 6257a214 r3 5f374421
r4 58f08140 r5 400b6a6c r6 00000000 r7 6257a22c
r8 5f372f85 r9 6257a214 sl 58ed6418 fp 5fd5bc94
ip 5fd5bc28 sp 5fd5bae0 lr 5f374441 pc 5f374442 cpsr 68000030
d0 0000016851ab0866 d1 4388cf697149f2ca
…中间部分省略…
d30 0003000000030000 d31 0003000000030000
scr 88000013
backtrace:
#00 pc 004e4442 /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
#01 pc 004e2fcb /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
#02 pc 004e2f8d /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
#03 pc 001a4371 /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
#04 pc 00341855 /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
#05 pc 0001deb0 /system/lib/libdvm.so (dvmPlatformInvoke+112)
#06 pc 0004d103 /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int
const*, JValue*, Method const*, Thread*)+394)
#07 pc 00000214 /dev/ashmem/dalvik-jit-code-cache (deleted)
stack:
5fd5baa0 5f62c98f /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
5fd5baa4 00000000
5fd5baa8 5f62c98f /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
5fd5baac 00000000
5fd5bab0 400b6a6c
5fd5bab4 00000000
5fd5bab8 0000006c
5fd5babc 00000057
5fd5bac0 00000000
5fd5bac4 58ed6910
5fd5bac8 000001a9
5fd5bacc 5348ff68 /dev/ashmem/dalvik-mark-stack (deleted)
5fd5bad0 000001a9
5fd5bad4 58ed6910
5fd5bad8 df0027ad
5fd5badc 00000000
……中间部分省略……
#02 5fd5bc30 00000000
5fd5bc34 5f034373 /data/data/xxxxxxx.testBrowser/libs/libwebcore.so
memory near r0:
bbadbecc ffffffff ffffffff ffffffff ffffffff ................
bbadbedc ffffffff ffffffff ffffffff ffffffff ................
bbadbeec ffffffff ffffffff ffffffff ffffffff ................
bbadbefc ffffffff ffffffff ffffffff ffffffff ................
bbadbf0c ffffffff ffffffff ffffffff ffffffff ................
……中间部分省略……
code around lr:
5f374420 43f0e92d 4d5ab0c9 91074691 447d4604 -..C..ZM.F...F}D
5f374430 2600682d 0718f109 91476829 fc96f4bf -h.&....)hG.....
5f374440 60064852 495347b0 20034a53 44799b07 RH.`.GSISJ. ..yD
5f374450 f4bd447a 4951e82e 4479a80a ff26f545 zD....QI..yDE.&.
5f374460 990f9b0e 46324638 0803ebc1 f7ff4643 ....8F2F....CF..
memory map around fault addr bbadbeef:
63ae5000-63be4000
(no map for address)
be90a000-be92b000 [stack]
!@dumpstate -k -t -z -d -o /data/log/dumpstate_app_native -m 23415
上面是笔者特意设计的Assert失败时产生的crash dump信息。利用在开发过程中产生的上述信息,我们可以定位并解决问题。
关键点(1):展示的是当前ROM的编译信息,此处使用的是三星galaxy SIII(I93000)的原生ROM。
关键点(2):展示的是线程信息,这些信息从/proc/tip和/proc/pid两个目录下读到,说明23415进程的 23559线程发生异常,线程的名字是WebViewCoreThre,进程(同时也是主线程)的名字是xxxxxxx.testBrowser。
关键点(3):展示的是信号的信息,通过ptrace(PTRACE_GETSIGINFO…)读到。通过信号的信息,我们可以大体知道产生了何种错误。SIGSEGV一般是由非法的内存访问产生的。
接下来是ARM寄存器的信息,通过ptrace(PTRACE_GETREGS…)得到。结合寄存器信息、栈信息及寄存器关联区域的内存信息,可以还原crash时的执行场景。
之后的调用栈信息是常用的部分,可以使用命令:
arm-eabi-addr2line -f –e ./out/target/product/xxxxx/symbols/system/libs/libxxxxxx.so 0Xxxxx
从对应的符号库中找到crash时执行的代码的位置,以及与之对应的调用栈。当然也可以使用arm-eabi-objdump反汇编调用栈上出现的共享库,找到对应的地址。使用调用栈信息很容易定位到问题。