android中有3种crash情况:未捕获的异常、ANR和闪退。未捕获的异常一般用crash文件就可以记录异常信息,而ANR无响应表现就是界面卡着无法响应用户操作,而闪退则是整个app瞬间退出。闪退一般是由于调用so库出错导致,像类似非法地址访问等,此时会生成/data/tombstones/tombstone_x文件,通过此文件分析崩溃后的错误信息 这种方法则主要是分析程序崩溃之后产生的错误信息相关文件来确定产生 Bug 的原因和位置。文件内容如下例:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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 文件它主要由下面几部分组成:
- 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。
信号名 值 默认动作 发出原因 SIGQUIT 3 终止进程 键盘的退出键被按下,虚拟机下用来打印堆栈 SIGILL 4 终止进程 非法指令 SIGABRT 6 终止进程 由abort(3)发出的终止指令,用户无法终止 SIGSEGV 11 终止进程 无效的内存引用 SIGBUS 7 终止进程 总线错误(错误的内存访问)
- SIGBUS与SIGSEGV的区别
SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。比如int型要4字节对齐,short型的2字节对齐。例如:short array[16];int * p = (int *)&array[1];*p = 1;SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对应该地址。例如:int pi= (int)0x00001111;*pi = 17
Call Stack 信息
调用栈信息是分析程序崩溃的非常重要的一个信息,它主要记录了程序在 Crash 前的函数调用关系以及当前正在执行函数的信息,它对应的是我们 tombstone 文件中 backtrace 符号开始的信息,上面例子中的 backtrace 的信息如下所示:
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
.....
在上面的输出信息中, ##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 开发包中已经为我们提供了非常便利好用的解析工具了https://developer.android.google.cn/ndk/downloads/index.html
我们主要使用了下面两种工具来解析 tombstone:
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'
addr2line基本用法如下:
#:addr2line -f -e libui.so 00006639
_ZN7android5Fence11waitForeverEPKc
/xx/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 部分的源码)。
objdump
objdump可以将so库进行反汇编,反汇编后得到重定向文件,然后根据偏移地址得到更详细的函数调用上下文信息。
我实际使用ndk 20版本发现addr2line 运行可以,objdump总有一些错误,所以最后使用ndk 10命令执行成功。
objdump -S -D D:\xx\xx.so > D:\xx\xx.log
打开xx.log文件,搜索偏移地址“6639 ”,即可看到此地址的汇编指令操作。
ndk-stack
ndk 的使用说明如下所示:
Usage:
ndk-stack -sym[-dump ]
-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目录下的文件。
参考:
https://www.jianshu.com/p/dd0b0a09aa78
https://blog.csdn.net/zerokkqq/article/details/73392172
https://cloud.tencent.com/developer/article/1969660