前言
作为程序员,调试 Bug 永远是最痛苦的一件事情,而在所有的 Bug当中访问非法内存又是最最让人崩溃和最难发现的一个问题。
通常我们调试程序 Bug 的方法主要有下面 3 种:
- 打印日志信息 这种方法通过在代码中可能出现 Bug 的位置添加一些日志输出语句来跟踪整个程序的执行过程或者查看关键变量的值等等。
- 单步调试 这个方法则主要使用到了调试工具,例如 gdb 和 Visual Studio 中内置的调试器。这些高级的工具使得我们可以在程序中任何位置设置断点,或者查看相关变量的值,或者更进一步直接查看 CPU 中寄存器的值。
- 分析崩溃后的错误信息 这种方法则主要是分析程序崩溃之后产生的错误信息相关文件来确定产生 Bug 的原因和位置。
Android 开发中常见 Crash 的情况
在 Android 开发中,程序 Crash 分三种情况:
- 未捕获的异常
- ANR(Application Not Responding)
- 闪退(NDK 程序引发错误)
其中未捕获的异常根据 logcat 打印的堆栈信息很容易定位错误。
ANR错误也好查,Android规定,应用与用户进行交互时,如果5秒内没有响应用户的操作,则会引发ANR错误,并弹出一个系统提示框,让用户选择继续等待或立即关闭程序。并会在/data/anr目录下生成一个traces.txt文件,记录系统产生anr异常的堆栈和线程信息。
如果是闪退,这问题比较难查,通常是项目中用到了 NDK 引发某类致命的错误导致闪退。因为 NDK 是使用 C/C++ 来进行开发,熟悉 C/C++ 的程序员都知道,指针和内存管理是最重要也是最容易出问题的地方,稍有不慎就会遇到诸如内存地址访问错误、使用野指针、内存泄露、堆栈溢出、初始化错误、类型转换错误、数字除0等常见的问题,导致最后都是同一个结果:程序崩溃。它不会像在 Java 层产生的异常时弹出“xxx程序无响应,是否立即关闭”之类的提示框。当发生 NDK 错误后,logcat 打印出来的那堆日志根据看不懂,更别想从日志当中定位错误的根源。
那么我们该怎么调试引发 Crash 的 NDK 程序呢?
哈哈,好在 Google 早就料到了我们写的 NDK 代码肯定会漏洞百出。首先,当 NDK 程序在发生 Crash 时,它会在路径 /data/tombstones/ 下产生导致程序 Crash 的文件 tombstone_xx。并且 Google 还在 NDK 包中为我们提供了一系列的调试工具,例如 addr2line、objdump、ndk-stack。
Tombstone
Linux 信号机制
在介绍 Tombstone 之前,我们首先补充一个 Linux 信号机制的知识。
信号机制是 Linux 进程间通信的一种重要方式,Linux 信号一方面用于正常的进程间通信和同步,如任务控制(SIGINT, SIGTSTP,SIGKILL, SIGCONT,……);另一方面,它还负责监控系统异常及中断。 当应用程序运行异常时, Linux 内核将产生错误信号并通知当前进程。 当前进程在接收到该错误信号后,可以有三种不同的处理方式。
- 忽略该信号。
- 捕捉该信号并执行对应的信号处理函数(signal handler)。
- 执行该信号的缺省操作(如 SIGSEGV, 其缺省操作是终止进程)。
当 Linux 应用程序在执行时发生严重错误,一般会导致程序 crash。其中,Linux 专门提供了一类 crash 信号,在程序接收到此类信号时,缺省操作是将 crash 的现场信息记录到 core 文件,然后终止进程。
Crash 信号列表:
Signal | Description |
---|---|
SIGSEGV | Invalid memory reference. |
SIGBUS | Access to an undefined portion of a memory object. |
SIGFPE | Arithmetic operation error, like divide by zero. |
SIGILL | Illegal instruction, like execute garbage or a privileged instruction |
SIGSYS | Bad system call. |
SIGXCPU | CPU time limit exceeded. |
SIGXFSZ | File size limit exceeded. |
什么 Tombstone?
Android Native 程序本质上就是一个 Linux 程序,因此当它在执行时发生严重错误,也会导致程序 crash,然后产生一个记录 crash 的现场信息的文件,而这个文件在 Android 系统中就是 tombstone 文件。
Tombstone 英文的本意是墓碑,我觉得用这个单词来表示程序 Crash 之后产生的现场死亡信息真的再恰当不过了,tombstone 文件的确就像墓碑一样记录了死亡了的进程的基本信息(例如进程的进程号,线程号),死亡的地址(在哪个地址上发生了 Crash),死亡时的现场是什么样的(记录了一系列的堆栈调用信息)等等。
因此,分析出现 Crash 的原因和代码位置最重要的就是分析这个 tombstone 文件。
tombstone 文件位于路径 /data/tombstones/ 下,
root@x86:/data/tombstones
tombstone_00
tombstone_01
它的主要内容如下所示:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android-x86/android_x86/x86:5.1.1/LMY48W/woshijpf04211939:eng/test-keys'
Revision: '0'
ABI: 'x86'
pid: 1019, tid: 1019, name: surfaceflinger >>> /system/bin/surfaceflinger <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4
eax a6265c06 ebx b7467d88 ecx b7631a22 edx a6265c06
esi 00000000 edi b6867140
xcs 00000073 xds 0000007b xes 0000007b xfs 00000000 xss 0000007b
eip b745a639 ebp bfcfc1e8 esp bfcfc150 flags 00010282
backtrace:
#00 pc 00006639 /system/lib/libui.so (android::Fence::waitForever(char const*)+41)
#01 pc 00034b86 /system/lib/libsurfaceflinger.so
#02 pc 0003229e /system/lib/libsurfaceflinger.so
#03 pc 0002cb9c /system/lib/libgui.so (android::BufferQueue::ProxyConsumerListener::onFrameAvailable(android::BufferItem const&)+652)
#04 pc 000342f4 /system/lib/libgui.so (android::BufferQueueProducer::queueBuffer(int, android::IGraphicBufferProducer::QueueBufferInput const&, android::IGraphicBufferProducer::QueueBufferOutput*)+2580)
#05 pc 0004eafb /system/lib/libgui.so (android::Surface::queueBuffer(ANativeWindowBuffer*, int)+411)
#06 pc 0004ce06 /system/lib/libgui.so (android::Surface::hook_queueBuffer(ANativeWindow*, ANativeWindowBuffer*, int)+38)
#07 pc 00014bc6 /system/lib/egl/libGLES_android.so
#08 pc 00017f73 /system/lib/egl/libGLES_android.so (eglSwapBuffers+163)
#09 pc 00015fdb /system/lib/libEGL.so (eglSwapBuffers+203)
#10 pc 000013ea /system/lib/hw/hwcomposer.x86.so
#11 pc 00034730 /system/lib/libsurfaceflinger.so
#12 pc 000256d4 /system/lib/libsurfaceflinger.so
#13 pc 00024bf4 /system/lib/libsurfaceflinger.so
#14 pc 000236fb /system/lib/libsurfaceflinger.so
#15 pc 0002338a /system/lib/libsurfaceflinger.so
#16 pc 0001e0ff /system/lib/libsurfaceflinger.so
#17 pc 0001d9ce /system/lib/libutils.so (android::Looper::pollInner(int)+926)
#18 pc 0001db73 /system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+67)
#19 pc 0001e561 /system/lib/libsurfaceflinger.so
#20 pc 00022ce7 /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::run()+39)
#21 pc 00000ca3 /system/bin/surfaceflinger
#22 pc 0001365a /system/lib/libc.so (__libc_init+106)
#23 pc 00000da8 /system/bin/surfaceflinger
stack:
bfcfc110 00000000
bfcfc114 b6839270
bfcfc118 00000000
bfcfc11c 00000000
bfcfc120 b68394e0
bfcfc124 00000002
bfcfc128 00000002
bfcfc12c b75d8185 /system/lib/libutils.so (android::RefBase::incStrong(void const*) const+53)
bfcfc130 b6839270
bfcfc134 bfcfc1e8 [stack]
bfcfc138 00000002
bfcfc13c a6265c06
bfcfc140 b7467d88 /system/lib/libui.so
bfcfc144 00000000
bfcfc148 b6867140
bfcfc14c b745a639 /system/lib/libui.so (android::Fence::waitForever(char const*)+41)
#00 bfcfc150 b683af18
bfcfc154 bfcfc1e8 [stack]
bfcfc158 00000000
bfcfc15c 00000000
bfcfc160 00000000
bfcfc164 b683af18
bfcfc168 b75ec9c4 /system/lib/libutils.so
bfcfc16c b75d8285 /system/lib/libutils.so (android::RefBase::weakref_type::decWeak(void const*)+37)
bfcfc170 00000000
bfcfc174 00000000
bfcfc178 00000000
bfcfc17c 00000000
bfcfc180 b7642968 /system/lib/libsurfaceflinger.so
bfcfc184 bfcfc1e8 [stack]
bfcfc188 b6867140
bfcfc18c b7622b87 /system/lib/libsurfaceflinger.so
tombstone 文件解析
第一次看到 tombstone 文件时,我也是一头雾水,一脸懵逼,加上调 Bug 时本来就烦躁的心情,简直不想多看它一眼。但是没办法,导致 Crash 的原因只有在这里才能找到,所以我们硬着头皮也要去分析啊(最好还是在心情比较平静时去调 Bug 吧,这样效率可能会更好一点)。
tombstone 文件它主要由下面几部分组成:
- Build fingerprint
- Crashed process and PIDs
- Terminated signal and fault address
- CPU registers
- Call stack
- Stack content of each call
但是也不是 tombstone 中的信息我们都需要分析,我们最主要的就是分析 Crashed process and PIDs、Terminated signal and fault address和Call stack 部分。
Crashed process and PIDs 信息
从上面 tombstone 文件中的第 5 行中我们可以看到 Crash 掉进程的基本信息,如下所示:
pid: 1019, tid: 1019, name: surfaceflinger >>> /system/bin/surfaceflinger <<<
如果 pid 等于 tid ,那么就说明这个程序是在主线程中 Crash 掉的,name 的属性则表示 Crash 进程的名称以及在文件系统中位置。
Terminated signal and fault address 信息
在上面 tombstone 文件中的第 6 行中我们可以看到程序是因为什么信号导致了 Crash 以及出现错误的地址,如下所示:
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4
这里的信息说明出现进程 Crash 的原因是因为程序产生了段错误的信号,访问了非法的内存空间,而访问的非法地址是 0x4。
Call Stack 信息
调用栈信息是分析程序崩溃的非常重要的一个信息,它主要记录了程序在 Crash 前的函数调用关系以及当前正在执行函数的信息,它对应的是我们 tombstone 文件中 backtrace 符号开始的信息,上面例子中的 backtrace 的信息如下所示:
在上面的输出信息中, ##00,#01,#02……等表示的都是函数调用栈中栈帧的编号,其中编号越小的栈帧表示着当前最近调用的函数信息,所以栈帧标号 #00 表示的就是当前正在执行并导致程序 Crash 函数的信息。
在栈帧的每一行中,pc 后面的 16 进制数值表示的是当前函数正在执行语句的在共享链接库或者可执行文件中的位置,然后 /system/lib/libui.so 则表示的是当前执行指令是在哪个文件当中,后面的小括号则是注明对应的是哪个函数。
例如,在上面的例子中,我们就可以定位到是程序是在 Fence::waitForever(char const* )中出现了错误,但是具体在那一行呢,我们还不是特别清楚,所以就需要我们进一步地使用更加高级的工具来帮助我们解析 tombstone 中有关调用栈的信息。
定位 Crash 源码位置的工具
前面我们简要地介绍了 tombstone 文件的结构以及每个部分的相关含义,我们可以得到导致程序 Crash 掉的主要原因是什么(根据 Signal 的类型),也知道了是在主线程还是在子线程中挂掉了,但是我们对程序具体在代码中的哪个位置挂掉了,还不是特别清楚,最多还只是通过 backtrace 中的栈帧的信息大概定位到位于哪个函数中,但具体是哪个文件哪个函数那一行还是不清楚的。所以,我们接下来就需要借助一些更加高级的工具来定位 Bug 在代码中的具体位置。
说明
Google 提供的 Android NDK 开发包中已经为我们提供了非常便利好用的解析工具了,我们主要使用了下面两种工具来解析 tombstone:
所以,在使用这两种工具之前,你首先得把 Android NDK 工具包在你的电脑上安装好。
addr2line
addr2line 是 NDK 中用来获得指定动态链接库文件或者可执行文件中指定地址对应的源代码信息,它位于 NDK 包中的如下位置中:
$NDK_HOME/toolchains/x86-4.6/prebuilt/linux-x86/bin/i686-linux-android-addr2line
其中 NDK_HOME 表示你 NDK 的安装路径。 虽然在 Linux 中同样有 addr2line 命令了,但是它与 NDK 中提供的 addr2line 指令还是略有差别的,所以我们可以使用 alias 来将 shell 中默认的 addr2line 指令链接的 NDK 的上述路径中,命令如下所示:
alias addr2line='$NDK_HOME/toolchains/x86-4.6/prebuilt/linux-x86/bin/i686-linux-android-addr2line'
这样之后,我们就可以在 Linux 中的 shell 环境中来直接使用 addr2line 命令了。 addr2line 命令的各个参数的含义如下所示:
woshijpf@woshijpf-OptiPlex-9020:~/newspace/android-x86/out/target/product/x86/symbols/system/lib$ addr2line -h
Usage: addr2line [option(s)] [addr(s)]
Convert addresses into line number/file name pairs.
If no addresses are specified on the command line, they will be read from stdin
The options are:
@<file> Read options from <file></file></file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format</bfdname>
-e --exe=<executable> Set the input file name (default is a.out)</executable>
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses</name>
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
addr2line: supported targets: elf64-x86-64 elf32-i386 elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big pe-x86-64 pe-i386 plugin srec symbolsrec verilog tekhex binary ihex
Report bugs to <http: www.sourceware.org="" bugzilla=""></http:>
addr2line 的基本用法如下所示:
aaa@qq.com9020:~/newspace/android-x86/out/target/product/x86/symbols/system/lib$ addr2line -f -e libui.so 00006639
_ZN7android5Fence11waitForeverEPKc
/home/woshijpf/newspace/android-x86/frameworks/native/libs/ui/Fence.cpp:59
哈哈,使用了 addr2line 工具之后,我们终于看到 libui.so 文件中地址 00006639 对应的源码是什么了,它对应的是 Android 系统源码中 /home/woshijpf/newspace/android-x86/frameworks/native/libs/ui/Fence.cpp:59 处代码(因为上面的 tombstone 文件是由于 Android 系统中的 surfaceFlinger 进程崩溃而产生的,所以它对应的源码也是 Android 系统中有关 SurfaceFlinger 部分的源码)。
ndk-stack
Android NDK 自从版本 r6开始, 提供了一个工具 ndk-stack。这个工具能自动分析 tombstone 文件, 能将崩溃时的调用内存地址和 c++ 代码一行一行对应起来.
ndk-stack 工具同样也位于 NDK 包中,它的路径如下所示:
ndk 的使用说明如下所示:
Usage:
ndk-stack -sym <path></path> [-dump <path></path>]
-sym Contains full path to the root directory for symbols.
-dump Contains full path to the file containing the crash dump.
This is an optional parameter. If ommited, ndk-stack will
read input data from stdin
其中,
- dump 参数很容易理解, 即 dump 下来的 log 文本文件. ndk-stack会分析此文件,这里我们就是指定我们从 Android 系统中获取到的 tombstone(这个需要通过 adb pull 的方式拉取到本地的宿主机中) 文件存放的位置。
- sym 参数就是你android项目下,编译成功之后,obj目录下的文件。
由于 ndk-stack 主要是用来分析用 C++ 编写的单独的 NDK 程序,所以下面就以解析一个 NDK 程序运行时 Crash 后产生的 tombstone 为例,tombstone 文件的内容如下所示:
// tombstone_01 文件内容
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android-x86/android_x86/x86:5.1.1/LMY48W/woshijpf04211939:eng/test-keys'
Revision: '0'
ABI: 'x86'
pid: 2125, tid: 2125, name: androidvncserve >>> androidvncserver <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
eax 00000000 ebx b76ffff4 ecx b6a37000 edx 00000000
esi 00000000 edi 00000000
xcs 00000073 xds 0000007b xes 0000007b xfs 00000000 xss 0000007b
eip b75a4ec5 ebp bfa9bd08 esp bfa9bbf0 flags 00010246
backtrace:
#00 pc 0004bec5 /system/bin/androidvncserver
#01 pc 0004e3e4 /system/bin/androidvncserver
#02 pc 0001365a /system/lib/libc.so (__libc_init+106)
#03 pc 0001060c /system/bin/androidvncserver
stack:
bfa9bbb0 bfa9bbc8 [stack]
bfa9bbb4 b72c6fb0 /system/lib/libdvnc_flinger_sdk22.so
bfa9bbb8 b72c7004 /system/lib/libdvnc_flinger_sdk22.so
bfa9bbbc b72c5b26 /system/lib/libdvnc_flinger_sdk22.so (readfb_flinger+38)
bfa9bbc0 b68ae080
bfa9bbc4 00000000
bfa9bbc8 00000000
bfa9bbcc 00000000
bfa9bbd0 00000000
bfa9bbd4 b76ffff4 /system/bin/androidvncserver
bfa9bbd8 b76a5e20 /system/bin/androidvncserver
bfa9bbdc b75ac181 /system/bin/androidvncserver
bfa9bbe0 b75ac16b /system/bin/androidvncserver
bfa9bbe4 b76ffff4 /system/bin/androidvncserver
bfa9bbe8 bfa9bd08 [stack]
bfa9bbec b75a579b /system/bin/androidvncserver
#00 bfa9bbf0 00000012
bfa9bbf4 bfa9bc1c [stack]
bfa9bbf8 00000000
bfa9bbfc 00000000
bfa9bc00 bfa9bc14 [stack]
bfa9bc04 00000000
bfa9bc08 00000000
bfa9bc0c b7498825 /system/lib/libc.so (je_free+453)
bfa9bc10 0000000e
bfa9bc14 00000400
bfa9bc18 00000000
bfa9bc1c b749538a /system/lib/libc.so (je_malloc+778)
bfa9bc20 0000000c
bfa9bc24 00000065
bfa9bc28 00000000
bfa9bc2c b6a37000
........ ........
#01 bfa9bd10 b6bb7300
bfa9bd14 00001b58
bfa9bd18 b76aa954 /system/bin/androidvncserver
bfa9bd1c 0000000b
bfa9bd20 00000005
bfa9bd24 00000000
bfa9bd28 00000000
bfa9bd2c 00000005
bfa9bd30 00000006
bfa9bd34 00000005
bfa9bd38 00000000
bfa9bd3c 00000000
bfa9bd40 00000000
bfa9bd44 bfa9bd24 [stack]
bfa9bd48 b754f9c8 /system/bin/linker
bfa9bd4c b7557bd8 /system/bin/linker
........ ........
#02 bfa9bed0 00000001
bfa9bed4 bfa9bf14 [stack]
bfa9bed8 bfa9bf1c [stack]
bfa9bedc 00000000
bfa9bee0 b7556fec /system/bin/linker
bfa9bee4 bfa9bf10 [stack]
bfa9bee8 00000000
bfa9beec b7556fec /system/bin/linker
bfa9bef0 bfa9bf10 [stack]
bfa9bef4 00000000
bfa9bef8 bfa9bf0c [stack]
bfa9befc b756960d /system/bin/androidvncserver
#03 bfa9bf00 bfa9bf10 [stack]
bfa9bf04 00000000
bfa9bf08 b756960d /system/bin/androidvncserver
bfa9bf0c b7569612 /system/bin/androidvncserver
bfa9bf10 00000001
bfa9bf14 bfa9cb05 [stack]
bfa9bf18 00000000
bfa9bf1c bfa9cb16 [stack]
bfa9bf20 bfa9cb35 [stack]
bfa9bf24 bfa9cb48 [stack]
bfa9bf28 bfa9cba3 [stack]
bfa9bf2c bfa9cbae [stack]
bfa9bf30 bfa9cbc1 [stack]
bfa9bf34 bfa9cbdc [stack]
bfa9bf38 bfa9cbe7 [stack]
bfa9bf3c bfa9cbfd [stack]
使用 ndk-stack 处理之后的结果:
woshijpf@woshijpf-OptiPlex-9020:~/android_workspace/droidVNCserver-real$ ndk-stack -sym obj/local/x86/ -dump ~/android-x86-debug-log/tombstone_01
********** Crash dump: **********
Build fingerprint: 'Android-x86/android_x86/x86:5.1.1/LMY48W/woshijpf04211939:eng/test-keys'
pid: 2125, tid: 2125, name: androidvncserve >>> androidvncserver <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Stack frame #00 pc 0004bec5 /system/bin/androidvncserver: Routine update_screen_16 in /home/woshijpf/android_workspace/droidVNCserver-real/jni/vnc/updateScreen.c:68
Stack frame #01 pc 0004e3e4 /system/bin/androidvncserver: Routine main in /home/woshijpf/android_workspace/droidVNCserver-real/jni/vnc/droidvncserver.c:805
Stack frame #02 pc 0001365a /system/lib/libc.so (__libc_init+106)
Stack frame #03 pc 0001060c /system/bin/androidvncserver: Unable to locate routine information for address 1060c in module obj/local/x86//androidvncserver
Stack frame #00 pc 0007bf71 /system/lib/libc.so (nanosleep+17)
Stack frame #01 pc 00047ed6 /system/lib/libc.so (usleep+70)
Stack frame #02 pc 0004fa6b /system/bin/androidvncserver: Routine camera_io in /home/woshijpf/android_workspace/droidVNCserver-real/jni/vnc/camera_io.c:557
Stack frame #03 pc 0004af6a /system/bin/androidvncserver: Routine receive_camera in /home/woshijpf/android_workspace/droidVNCserver-real/jni/vnc/droidvncserver.c:273
Stack frame #04 pc 00022168 /system/lib/libc.so (__pthread_start(void*)+56)
Stack frame #05 pc 0001cc69 /system/lib/libc.so (__start_thread+25)
Stack frame #06 pc 000137c6 /system/lib/libc.so (__bionic_clone+70)
Stack frame #00 pc 0007cc33 /system/lib/libc.so (recvfrom+19)
Stack frame #01 pc 00050cdb /system/bin/androidvncserver: Routine handle_connections in /home/woshijpf/android_workspace/droidVNCserver-real/jni/vnc/gui.c:105
Stack frame #02 pc 00022168 /system/lib/libc.so (__pthread_start(void*)+56)
Stack frame #03 pc 0001cc69 /system/lib/libc.so (__start_thread+25)
Stack frame #04 pc 000137c6 /system/lib/libc.so (__bionic_clone+70)
经过 ndk-stack地这么处理之后,我们就非常直接明了地看到了每个栈帧中调用函数的内存地址和程序源码的关系。是不是感觉很爽啊!
总结
Android NDK 程序的系统调试其实也没那么复杂,虽然它长着一副恐怖的外表,但是只要掌握了正确的方法,了解 Tombstone 文件中关键信息的含义,学会使用 addr2line 和 ndk_stack 这两个超级方便的工具,那么一步一步找出导致 NDK 程序 Crash 的 Bug 就非常 Easy 了。 但上面的这些工作也还只是帮助你快速地定位到你的代码出现问题的位置,具体的 Bug 还是需要你进一步地根据业务逻辑来分析代码。
参考链接
[cocos2dx]利用NDK崩溃日志查找BUG
Debugging tombstones with ndk-stack and addr2line
Debugging Native Android
Platform Code
Android Tombstone/Crash的log分析和定位
Android重启原因分析
android crash的类型及原因分析(上)
Android Debug
Android平台抓取native
crash log
Android
Debuggerd 简要介绍和源码分析
Android NDK tombstonelfn分析工具
转自 https://juejin.im/entry/5764ed962e958a00581a5c54