崩溃是Android开发经常会碰到的问题,我们都知道,Android崩溃分为Java崩溃和Native崩溃。简单来说Java崩溃就是在Java代码中出现了未捕获异常,导致程序异常退出。那Native崩溃又是如何产生的?一般是因为Native代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动abort,这些都会产生相应的signal信号,导致程序异常退出。
相比于Java崩溃,Native崩溃更难捕获和定位。事实上针对我们目前的项目,几乎大部分的崩溃都是Native 崩溃。分析项目的崩溃日志我们可以发现,有些崩溃是可以从backtrace中获得一些有用的信息。但是有些崩溃甚至连backtrace都不会出现,开发人员只能单步调试,无法快速定位。所以有没有一个系统的框架来收集和处理这些Native崩溃呢?当然有,Google开源的BreakPad就是其中之一。
1 BreakPad
1.1常见信号类型
在介绍BreakPad之前,我们首先要对信号机制有一个大概的了解。这里只是希望能对常见的信号类型有一个认识,因为一旦发生崩溃,信号类型能让我们对崩溃有一个初步的判断。
信号量 | value | 描述 | 例子 |
---|---|---|---|
SIGABRT | 6 | 程序发生错误或者调用了abort | 很多C的库函数,如果发现异常会调用abort,如strlen |
SIGBUS | 10,7,10 | 不存在的物理地址硬件错误 | 更多的是因为硬件或者系统引起的 |
SIGFPE | 8 | 浮点数运算错误 | 如除0,余0,整型溢出 |
SIGILL | 4 | 非法指令 | 损坏的可执行文件或者代码区损坏 |
SIGSEGV | 11 | 段地址错误 | 空指针,访问不存在的地址空间,访问内核区,写只读空间,栈溢出,数组越界,野指针 |
SIGSTKFLT | 16 | //未使用 | |
SIGPIPE | 13 | 管道错误,往没有reader的管道中写 | Linux中的socket,如果断掉了继续写,signal(SIGPIPE,SIG_IGN) |
1.2BreakPad简介
Google BreakPad是一个跨平台的崩溃转储和分析框架,它是一个工具集合。
BreakPad由三个主要组件组成:
- client,以library的形式内置在你的应用中,当崩溃发生时写 minidump文件
- symbol dumper, 读取由编译器生成的调试信息(debugging information),并生成 symbol file
- processor, 读取 minidump文件 和 symbol file ,生成可读的c/c++ Stack trace.
简单来说就是一个生成 minidump,一个生成symbol file,然后将其合并处理成可读的Stack trace。
2 BreakPad Android集成
2.1BreakPad源码下载
既然是BreakPad在Android平台上的应用,就把具体的应用过程写的更详细一点。BreakPad的源码下载其实是一件挺让人头大的事情,你想直接去下载国外的一些优秀开源项目的源码基本上都需要翻墙,而且翻墙了你也不一定能下载得到。参考了网上获取BreakPad源码的几种方法,虽然我已经确定自己翻墙了,无一例外全部以失败告终。后来摸索中发现,其实要获取源码,可以直接通过谷歌浏览器下载。
进入这个网页https://chromium.googlesource.com/breakpad/breakpad
然后选择master分支
然后点击tgz,源码算是下载下来了。不过还没完,源码下载到本地解压以后,你会发现是这么一个文件目录
其实我们关心的主要是src这个文件夹,因为BreakPad的源码是在这个目录下面。这里提醒一下踩过的坑。在src/third_party这个路径下面,其实是少了一个文件夹的。文件夹名是lss,文件夹内部包含了一个文件linux_syscall_support.h,少了他BreakPad就编译不通过了,好在这个文件比较好下载,具体流程就不细说了。
2.2 BreakPad动态库编译
源码下载完以后,我们就可以开始把BreakPad集成到App中了。这个过程主要分三步。编写JNI层mapping文件;编译Android各个平台的.so文件;将.so文件导入到实际的开发项目中。
动态库的编译需要使用ndk,本文没有采用传统的Android.mk脚本进行编译,而是使用了cmake,直接把BreakPad源码编译成了.so文件。实际上,在下载完BreakPad源码以后,进入src目录,执行./configure 和 make 命令,就能得到libbreakpad.a静态库,通过libbreakpad.a也能编译得到对应的动态库。对应的JNI层目录如下:
include文件夹下面的文件是BreakPad源码中src目录下的文件,包含Breakpad的实现源码以及所有的头文件。breakpad.cpp对应JNI mapping文件,用来注册信号处理接口,监听崩溃时系统抛出的信号。对应的代码如下:
对应的native接口为
public static native void BreakPadInit(String path)
其中参数path是发生崩溃时,dump文件的生成路径。通过编写CMakeLists.txt来生成对应的libbreakpad.so。关于Cmake的语法规则可以查阅相关资料。CMakeLists.txt文件对应如下:
至此已经可以得到我们想要的libbreakpad.so,把生成的动态库文件导入到项目中,就算完成任务了。
3 Native Crash分析
通过得到的libbreakpad.so我们已经可以进行native crash的监测了。我们试着利用Breakpad进行native crash的定位和分析。
3.1 捕获native崩溃
首先我们在JNI层的Native代码中人为的制造一个崩溃。这里制造了一个空指针的崩溃
我们把libbreakpad.so集成到案例项目中,运行程序,调用BreakPadTest接口,果然程序崩溃了。而且崩的很彻底,Logcat上并没有发现有用的日志信息,提示信息如下:
Error只是上报了一个SIGSEGV的信号,我们通过这个信号,只能知道是native层发生了段地址错误,有可能是数组越界或者空指针之类的常见问题,除此之外具体是哪里出现了错误,我们无从得知。
3.2 Dmp文件分析
分析Dmp文件是我们通过Breakpad定位崩溃问题的重要手段。我们在进行Breakpad回调函数注册的时候会设置一个路径,这个路径就是发生崩溃时,dmp文件的生成路径。4.1节中,dmp文件的生成路径为/storage/emulated/0/,对应的dmp文件名为72a7ac3f-8cbe-482a-28339c98-ef9d8eb2.dmp。
进入Breakpad源码目录,在/breakpad-master/目录下执行configure命令,执行完该命令以后,会在/breakpad-master/src/processor目录下生成minidump_stackwalk工具,用于导出崩溃日志。同时在/breakpad-master/src/tools/linux/dump_syms目录下生成dump_syms工具,用于导出符号文件。把dmp文件和minidump_stackwalk,dump_syms以及debug版本的对应so文件拷贝到同一个目录下。首先执行生成符号文件命令:
这部分的实现需要在linux环境下执行。得到libbreakpad.so.sym文件以后,查看文件,我们能够得到文件头部的一个字符串:
继续执行命令
导出崩溃堆栈信息文件(确保dmp文件和symbols目录是同级的):
这里面的native_dmp.dmp文件对应的就是上面的72a7ac3f-8cbe-482a-28339c98-ef9d8eb2.dmp,文件名太长,我在导出的时候进行了重命名。可以看到此时我们的test目录下生成了我们所需要的crashed日志,如下图:
我们打开日志可以发现崩溃位置:
定位到我们的breakpad.cpp的第46行,从左到右第二个字符。我们可以回顾一下之前制造崩溃的位置:
4 总结
在Android平台上使用Breakpad进行native崩溃定位的整个流程就结束了,显然Breakpad优点很明显,首先它具有跨平台的特性,其次它也是google颇为得意的一款开源工具集,权威性不言而喻。缺点也很明显,从集成目标项目,到最后的崩溃日志分析,整个过程的步骤比较复杂,而且Breakpad的代码体量也比较大。如果把Breakpad集成到我们自己的项目中又会出现一些新的问题,比如目前我们软终端Android项目中的so库大大小小将近二十个,发生崩溃时,如果我们无法定位究竟是哪一个so引发的问题,那就比较头大了。最坏情况下,要对所有的so进行一番分析,所以对待native崩溃还是要结合其他手段一起定位,分析native crash并不是一件容易的事情。作者水平有限,难免会有疏漏和错误,欢迎大家批评指正。
引用
https://blog.csdn.net/elsdnwn/article/details/48651815
https://blog.csdn.net/Tencent_Bugly/article/details/75006423
对应Demo的github https://github.com/zhoutianjie/BreakPadTest