1. 为什么要使用Google Breakpad?
我们在开发过程中,Android JNI层Crash问题或者我们引用的第三方.so库文件报错,都是一个比较头疼的问题。相对Java层来说,由于c/c++造成的crash没有输出如同Java的Exception Strace堆栈信息,所以定位问题也是个比较艰难的事情。
Google Breakpad是一套完整的工具集,从Crash的捕获到Crash的dump,都提供了相对应的工具。它记录了崩溃时的.dump文件,无论我们是在本地或者发送到服务器端,都可以用相对应的工具来解析.dump文件帮助我们查找C和C++堆栈踪迹。
但是,由于Google Breakpad是C/C++编写的,很多Android开发同学并没有这方面的经验,想用而却不能用。而且,Google Breakpad在Github上面的说明,估计很多人看了就懵逼了:
其实,它的意思是需要去编译Google Breakpad源码去生成适合不同平台下的minidump_stackwalk文件,比如Windows、Mac、Linux等等。当然我也是踩了坑的,参考这个说明,多次尝试后在Windows下确实没有成功过;后来,利用VMware Workstation Pro中安装Ubuntu 18.10下成功编出来了。
另外,我在踩坑过程中,查了各种资料,基本都是上面图中的翻版,没啥用。所以,当我无意中全局搜索发现Android自带有minidump_stackwalk执行文件,便怀着好奇心试了试,所以才有今天这篇文章。
2. 如何在Windows下无需编译Google Breakpad源码就可使用?
1. 利用模拟器测试
第一步:下载Google Breakpad源码
第二步:虽然代码中很多地方引用了#include "third_party/lss/linux_syscall_support.h"这个Linux调用库,但源码中并没有给出lss目录,所以我们需要自己翻墙去下载https://chromium.googlesource.com/linux-syscall-support/源码,并将lss目录拷贝到third_party下,否则编译不过。
第三步:利用Android Studio新建一个项目,具体参考我的实例项目。你只需要将Breakpad的源码src目录替换到我的项目中去就行(该项目中是采用的CMakeLists.txt来配置的,而Google Breakpad中的README.ANDROID给出的是Android.mk配置,这个大家自行取舍用哪种!
第四步:利用模拟器运行启动项目会是下面的界面,点击按钮应用闪退。
第五步:模拟器的文件管理器中会生成一个crashDump目录,里面有一个.dump文件。
第六步:有了.dump文件,我们就需要对其进行解析,工具的名称是minidump_stackwalk。其实,Android Studio的安装目录下bin\lldb\bin就存在这么一个执行文件(我用的是Windows 10)。好,那么来试试(将模拟器目录下的.dump文件拷贝到当前目录并重新命名了):
成功解析文件。大家看下
Operating system: Android
0.0.0 Linux 3.10.0+ #256 SMP PREEMPT Fri May 19 11:58:12 PDT 2017 i686
CPU: x86
GenuineIntel family 6 model 31 stepping 1
4 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x544
eip = 0x9e810544 esp = 0xbfe32c08 ebp = 0xbfe32c18 ebx = 0x9e811fe4
esi = 0xa447baa9 edi = 0xbfe32e08 eax = 0x00000000 ecx = 0x43e40746
edx = 0xbfe32c30 efl = 0x00010286
Found by: given as instruction pointer in context
1 libcrash-lib.so + 0x52c
eip = 0x9e81052c esp = 0xbfe32c20 ebp = 0xbfe32c28
Found by: previous frame's frame pointer
... // 后面内容直接省略掉,因为libcrash-lib.so相关的信息已经都展示出来了
好,从.dump文件解析出来的信息中,根据文章Android 平台 Native 代码的崩溃捕获机制及实现的介绍,我们可知Crash reason: SIGSEGV代表哪种类型的错误:
SIGSEGV 是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号。
Thread 0 (crashed) // crash 发生时候的线程
0 libcrash-lib.so + 0x544 // 发生 crash 的位置和寄存器信息
第七步:有了具体的寄存器信息,我们进行符号解析,可以使用Android NDK中提供的addr2line来根据地址进行一个符号反解的过程,该工具在Android SDK目录下可以找到。
工具链的选择要根据.so的类型来决定,看解析后的文件,有显示CPU信息。下面是NDK 18的工具链目录:
如果是arm-64位的so,解析是需要使用aarch64-linux-android-4.9下的工具链。
如果是arm-32位的so,解析是需要使用arm-linux-androideabi-4.9下的工具链。
如果是x86-64位的so,解析是需要使用x86_64-4.9下的工具链。
如果是x86-32位的so,解析是需要使用x86-4.9下的工具链。
这里,因为CPU信息是x86,所以选择x86-4.9下的工具链。我们将项目中build目录下的x86对应的libcrash-lib.so(app\build\intermediates\transforms\mergeJniLibs\debug\0\lib\x86\libcrash-lib.so)拷贝到x86-4.9下的工具链目录(x86-4.9\prebuilt\windows-x86_64\bin)中,然后执行如下命令:
是不是看到具体哪个类下的哪一行报错了!!!
2. 利用真机测试
前五步一样,请参考模拟器测试部分类容。
第六步:将真机中的.dump文件进行解析:
从上面的截图能看到,有一堆的ERROR错误信息,但是生成了解析文件。打开看下:
Operating system: Android
0.0.0 Linux 4.4.23+ #1 SMP PREEMPT Mon Sep 17 22:10:21 CST 2018 aarch64
CPU: arm64
8 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x5f8
x0 = 0x0000000000000000 x1 = 0x0000000000000001
x2 = 0x0000007fcd3ec4a0 x3 = 0x0000007ab06f0a58
x4 = 0x0000007fcd3ec0b0 x5 = 0x0000007ab07e4476
x6 = 0x0000007ab07e43f8 x7 = 0x0000000000000000
x8 = 0x918e7d903505e7f1 x9 = 0x918e7d903505e7f1
x10 = 0x0000000000430000 x11 = 0x0000000000000000
x12 = 0x0000007ab451de90 x13 = 0xd962dd2e22e77fe1
x14 = 0x0000007ab451d000 x15 = 0xffffffffffffffff
x16 = 0x0000007a932befe8 x17 = 0x0000007a932ae5e8
x18 = 0x0000000000000020 x19 = 0x0000007aafca3a00
x20 = 0x0000007ab06f0ab8 x21 = 0x0000007aafca3a00
x22 = 0x0000007fcd3ec4fc x23 = 0x0000007ab07eed97
x24 = 0x0000000000000004 x25 = 0x0000007aafca3aa0
x26 = 0x0000000000000000 x27 = 0x0000000000000000
x28 = 0x0000007fcd3ec240 fp = 0x0000007fcd3ec200
lr = 0x0000007a932ae5e0 sp = 0x0000007fcd3ec1f0
pc = 0x0000007a932ae5f8
Found by: given as instruction pointer in context
1 libcrash-lib.so + 0x5dc
fp = 0x0000007fcd3ec240 lr = 0x0000007aafaf5704
sp = 0x0000007fcd3ec210 pc = 0x0000007a932ae5e0
Found by: previous frame's frame pointer
的确与模拟解析出来的有点不一样哦。
第七步:我们利用aarch64-linux-android-4.9下的工具链对其进行符号反解。
虽然,第六步解析.dump文件的过程中提示ERROR信息,但是第七步执行完后,最终的结果是一样的,也是我们预测的结果。
3. 将真机的测试结果在Ubuntu中验证
文章开始就提到,Google Breakpad在Github上的说明是要自己编译minidump_stackwalk的,那么下面就用在Ubuntu上根据Breakpad源码编译出来的minidump_stackwalk进行对.dump解析:
由于解析过程中提示信息太多,所以我单独将其复制下来了(放在我的实例项目根目录下了),因为仔细看发现也有ERROR信息,而且同Windows下Android自带的minidump_stackwalk解析时ERROR信息是一样的:
从验证结果来看,我们可以直接利用Android自带的minidump_stackwalk直接进行解析。
对比真机测试和模拟器测试,在解析.dump文件时,前者会出现ERROR信息,而后者却正常呢?我们先来看下ERROR信息的错误,虽然能看懂英文,但不知其具体含义,不过我们可以在解析后的文件中看到的确有0x15cd207000000001和0x80238(其实是0x0000000000080238,省去了中间的0而已):
Thread 0 (crashed)
0 libcrash-lib.so + 0x5f8
......
1 libcrash-lib.so + 0x5dc
......
2 libart.so + 0x512700
fp = 0x15cd207000000001 lr = 0x0000000000000000
sp = 0x0000007fcd3ec250 pc = 0x0000007aafaf5704
Found by: previous frame's frame pointer
Thread 15
0 libc.so + 0x699e8
......
1 libc.so + 0x42c00
fp = 0x0000000000080238 lr = 0x0000007aa1e62e80
sp = 0x0000007a928823f0 pc = 0x0000007ab0d5bc04
Found by: previous frame's frame pointer
大家从上述两个截取的片段中应该能够看到对应的地址了吧,0x15cd207000000001指向的是libart.so的系统库(我用的华为荣耀8,华为自研的海思芯片,也就是麒麟);0x0000000000080238指向的是libc.so的系统库。所以,我猜测应该是华为手机kernel层做了什么事情吧,而模拟器针对的是Google原生系统,是标准库(大家也可以拿别的手机也试试);不管我的猜测是否正确,ERROR信息中涉及的寄存器信息均与自己写的、待分析的libcrash-lib.so没有关系,也就是说不影响我们的业务代码分析,所以可以忽略。
3. 总结一下
其实,回过头来看Google Breakpad在Github上的说明,是针对没有minidump_stackwalk执行文件的开发者而言的,打个比方(可能不恰当,假设做Java后台的没有minidump_stackwalk):做Java Web的,利用JNI实现了一个需求,想捕获异常并分析,那么他就必须在他开发的平台上(Windows、Mac或Linux下)利用Google Breakpad源码生成一个minidump_stackwalk文件。只是,Android已经提供这个文件,只是有些人不知道而已。
另外,我推测:在Linux、Mac下,只要安装了Android Studio应该都不需要编译Google Breakpad源码,对与不对,不喜忽喷,用Mac的同志可以帮忙测试下,欢迎留言!!!