《深入理解Android》一3.3 Assert与crash dump

简介:

本节书摘来自华章出版社《深入理解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反汇编调用栈上出现的共享库,找到对应的地址。使用调用栈信息很容易定位到问题。

相关文章
|
Java Android开发 安全
|
Java Android开发
Android Native Crash问题排查思路
Android Native Crash问题排查思路
1066 0
Android Native Crash问题排查思路
|
监控 程序员 Go
基于友盟U-APM解决客户小姐姐Android Native Crash问题,小姐姐说我真棒,要把她闺蜜介绍给我
客户小姐姐反馈一个Crash问题,但是概率很小,开发和测试都没遇到过。总不能让小姐姐帮忙抓取logcat日志。逼不得已,用上了杀手锏友盟+U-APM神器,重新给小姐姐更新了一版APK。然后,开瓶82年的冰阔落,坐等日志上来。
基于友盟U-APM解决客户小姐姐Android Native Crash问题,小姐姐说我真棒,要把她闺蜜介绍给我
|
Web App开发 运维 Java
mPaaS最佳实践之《Android Native crash处理》
目前 mPaas Android是使用的是Crash SDK对闪退进行的处理,CrashSDK 是 Android 平台上一款功能强大的崩溃日志收集 SDK,有着极高的崩溃收集率和完整、全面的崩溃日志信息,生成的日志内容非常利于问题的跟进和解决。在我们的日常运维中,经常遇到一些闪退,无法直接从闪退堆栈看到原因,尤其是一些非Java的Native的闪退,这里分享下在mPaas框架下怎么使用Crash SDK对闪退进行分析。
451 0
|
Android开发 开发者
Android Native crash 处理案例分享
Android Native crash 处理案例分享
Android Native crash 处理案例分享
|
监控 调度
04.Android崩溃Crash库之Loop拦截崩溃和ANR
04.Android崩溃Crash库之Loop拦截崩溃和ANR
814 0
|
Java API Android开发
03.Android崩溃Crash库之ExceptionHandler分析
03.Android崩溃Crash库之ExceptionHandler分析
465 0
|
消息中间件 机器学习/深度学习 监控
02.Android崩溃Crash库之App崩溃分析
02.Android崩溃Crash库之App崩溃分析
949 0
|
存储 数据采集 监控
01.Android崩溃Crash封装库
Android崩溃Crash封装库
1208 0
01.Android崩溃Crash封装库
|
Java Android开发
Android Native Crash 收集
在 Android 平台上,Native Crash 一直是比较麻烦的问题,因为捕获麻烦,获取到了内容又不全,内容全了信息又不对,信息对了又不好处理。
1246 0