在各种场合遇到其他产品的开发人员时,大家总忍不住想在技术上切磋两招。第一句问的通常都是“你们产品的崩溃率是多少?”
程序员A自豪地说: “百分之一。”
旁边的程序员B鄙视地看了一眼,然后喊到: “千分之一!”
“万分之一” ,程序员C说完之后全场变得安静起来。
崩溃率是衡量一个应用质量高低的基本指标,这一点是你我都比较认可的。不过你说的“万分之一”就一定要比我说的“百分之一” 更好吗?我觉得,这个问题其实并不仅仅是比较两个数值这么简单。
今天我们就来聊一聊有关“崩溃”的那些事,我会从Android的两种崩溃类型谈起,再和你进一步讨论到底该怎样客观地衡量崩溃这个指标,以及又该如何看待和崩溃相关的稳定性。
Android 的两种崩溃
我们都知道,Android崩溃分为Java崩溃和Native崩溃。
简单来说,Java崩溃就是在Java代码中,出现了未捕获异常,导致程序异常退出。那Native崩溃又是怎么产生的呢?一般都是因为在Native代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动abort,这些都会产生相应的signal信号,导致程序异常退出。
所以,“崩溃”就是程序出现异常,而一个产品的崩溃率,跟我们如何捕获、处理这些异常有比较大的关系。Java崩溃的捕获比较简单,但是很多同学对于如何捕获Native崩溃还是一知半解,下面我就重点介绍Native崩溃的捕获流程和难点。
1.Native崩溃的捕获流程
如果你对Native崩溃机制的一些基本知识还不是很熟悉,建议你阅读一下《Android平台Native代码的崩溃捕获机制及实现》。这里我着重给你讲讲一个完整的Native崩溃从捕获到解析要经历哪些流程。
-
编译端。编译C/C++代码时,需要将带符号信息的文件保留下来。
-
客户端。捕获到崩溃时候,将收集到尽可能多的有用信息写入日志文件,然后选择合适的时机上传到服务器。
-
服务端。读取客户端上报的日志文件,寻找适合的符号文件,生成可读的C/C++调用栈。

2.Native崩溃捕获的难点
Chromium的Breakpad是目前Native崩溃捕获中最成熟的方案,但很多人都觉得Breakpad过于复杂。其实我认为Native崩溃捕获这个事情本来就不容易,跟当初设计Tinker的时候一样,如果只想在90%的情况可靠,那大部分的代码的确可以砍掉;但如果想达到99%,在各种恶劣条件下依然可靠,后面付出的努力会远远高于前期。
所以在上面的三个流程中,最核心的是怎么样保证客户端在各种极端情况下依然可以生成崩溃日志。因为在崩溃时,程序会处于一个不安全的状态,如果处理不当,非常容易发生二次崩溃。
那么,生成崩溃日志时会有哪些比较棘手的情况呢?
情况一:文件句柄泄漏,导致创建日志文件失败,怎么办?
应对方式:我们需要提前申请文件句柄fd预留,防止出现这种情况。
情况二:因为栈溢出了,导致日志生成失败,怎么办?
应对方式:为了防止栈溢出导致进程没有空间创建调用栈执行处理函数,我们通常会使用常见的signalstack。在一些特殊情况,我们可能还需要直接替换当前栈,所以这里也需要在堆中预留部分空间。
情况三:整个堆的内存都耗尽了,导致日志生成失败,怎么办?
应对方式:这个时候我们无法安全地分配内存,也不敢使用stl或者libc的函数,因为它们内部实现会分配堆内存。这个时候如果继续分配内存,会导致出现堆破坏或者二次崩溃的情况。Breakpad做的比较彻底,重新封装了Linux Syscall Support,来避免直接调用libc。
情况四:堆破坏或二次崩溃导致日志生成失败,怎么办?
应对方式:Breakpad会从原进程fork出子进程去收集崩溃现场,此外涉及与Java相关的,一般也会用子进程去操作。这样即使出现二次崩溃,只是这部分的信息丢失,我们的父进程后面还可以继续获取其他的信息。在一些特殊的情况,我们还可能需要从子进程fork出孙进程。
当然Breakpad也存在着一些问题,例如生成的minidump文件是二进制格式的,包含了太多不重要的信息,导致文件很容易达到几MB。但是minidump也不是毫无用处,它有一些比较高级的特性,比如使用gdb调试、可以看到传入参数等。Chromium未来计划使用Crashpad全面替代Breakpad,但目前来说还是 “too early to mobile”。
我们有时候想遵循Android的文本格式,并且添加更多我们认为重要的信息,这个时候就要去改造Breakpad的实现。比较常见的例如增加Logcat信息、Java调用栈信息以及崩溃时的其他一些有用信息,在下一节我们会有更加详细的介绍。
如果想彻底弄清楚Native崩溃捕获,需要我们对虚拟机运行、汇编这些内功有一定造诣。做一个高可用的崩溃收集SDK真的不是那么容易,它需要经过多年的技术积累,要考虑的细节也非常多,每一个失败路径或者二次崩溃场景都要有应对措施或备用方案。
3.选择合适的崩溃服务
对于很多中小型公司来说,我并不建议自己去实现一套如此复杂的系统,可以选择一些第三方的服务。目前各种平台也是百花齐放,包括腾讯的Bugly、阿里的啄木鸟平台、网易云捕、Google的Firebase等等。
当然,在平台的选择方面,我认为,从产品化跟社区维护来说,Bugly在国内做的最好;从技术深度跟捕获能力来说,阿里UC浏览器内核团队打造的啄木鸟平台最佳。
如何客观地衡量崩溃
对崩溃有了更多了解以后,我们怎样才能客观地衡量崩溃呢?
要衡量一个指标,首先要统一计算口径。如果想评估崩溃造成的用户影响范围,我们会先去看UV崩溃率。
UV 崩溃率 = 发生崩溃的 UV / 登录 UV
只要用户出现过一次崩溃就会被计算到,所以UV崩溃率的高低会跟应用的使用时长有比较大的关系,这也是微信UV崩溃率在业界不算低的原因(强行甩锅)。当然这个时候,我们还可以去看应用PV崩溃率、启动崩溃率、重复崩溃率这些指标,计算方法都大同小异。
这里为什么要单独统计启动崩溃率呢?因为启动崩溃对用户带来的伤害最大,应用无法启动往往通过热修复也无法拯救。闪屏广告、运营活动,很多应用启动过程异常复杂,又涉及各种资源、配置下发,极其容易出现问题。微信读书、蘑菇街、淘宝、天猫这些“重运营”的应用都有使用一种叫作“安全模式”的技术来保障客户端的启动流程,在监控到客户端启动失败后,给用户自救的机会。
现在回到文章开头程序员“华山论剑”的小故事,我来揭秘他们解决崩溃率的“独家秘笈”。
程序员B对所有线程、任务都封装了一层try catch,“消化”掉了所有Java崩溃。至于程序是否会出现其他异常表现,这是上帝要管的事情,反正我是实现了“千分之一”的目标。
程序员C认为Native崩溃太难解决,所以他想了一个“好方法”,就是不采集所有的Native崩溃,美滋滋地跟老板汇报“万分之一”的工作成果。
了解了美好数字产生的“秘笈”后,不知道你有何感想?其实程序员B和C都是真实的案例,而且他们的用户体量都还不算小。技术指标过于KPI化,是国内比较明显的一个现象。崩溃率只是一个数字,我们的出发点应该是让用户有更好的体验。
如何客观地衡量稳定性
到此,我们讨论了崩溃是怎么回事儿,以及怎么客观地衡量崩溃。那崩溃率是不是就能完全等价于应用的稳定性呢?答案是肯定不行。处理了崩溃,我们还会经常遇到ANR(Application Not Responding,程序没有响应)这个问题。
出现ANR的时候,系统还会弹出对话框打断用户的操作,这是用户非常不能忍受的。这又带来另外一个问题,我们怎么去发现应用中的ANR异常呢?总结一下,通常有两种做法。
1. 使用FileObserver监听/data/anr/traces.txt 的变化。非常不幸的是,很多高版本的ROM,已经没有读取这个文件的权限了。这个时候你可能只能思考其他路径,海外可以使用Google Play服务,而国内微信利用Hardcoder框架(HC框架是一套独立于安卓系统实现的通信框架,它让App和厂商ROM能够实时“对话”了,目标就是充分调度系统资源来提升App的运行速度和画质,切实提高大家的手机使用体验)向厂商获取了更大的权限。
2. 监控消息队列的运行时间。这个方案无法准确地判断是否真正出现了ANR异常,也无法得到完整的ANR日志。在我看来,更应该放到卡顿的性能范畴。
回想我当时在设计Tinker的时候,为了保证热修复不会影响应用的启动,Tinker在补丁的加载流程也设计了简单的“安全模式”,在启动时会检查上次应用的退出类型,如果检查连续三次异常退出,将会自动清除补丁。所以除了常见的崩溃,还有一些会导致应用异常退出的情况。
在讨论什么是异常退出之前,我们先看看都有哪些应用退出的情形。
-
主动自杀。
Process.killProcess()、exit()等。 -
崩溃。出现了Java或Native崩溃。
-
系统重启;系统出现异常、断电、用户主动重启等,我们可以通过比较应用开机运行时间是否比之前记录的值更小。
-
被系统杀死。被low memory killer杀掉、从系统的任务管理器中划掉等。
-
ANR。
我们可以在应用启动的时候设定一个标志,在主动自杀或崩溃后更新标志,这样下次启动时通过检测这个标志就能确认运行期间是否发生过异常退出。对应上面的五种退出场景,我们排除掉主动自杀和崩溃(崩溃会单独的统计)这两种场景,希望可以监控到剩下三种的异常退出,理论上这个异常捕获机制是可以达到100%覆盖的。
通过这个异常退出的检测,可以反映如ANR、low memory killer、系统强杀、死机、断电等其他无法正常捕获到的问题。当然异常率会存在一些误报,比如用户从系统的任务管理器中划掉应用。对于线上的大数据来说,还是可以帮助我们发现代码中的一些隐藏问题。
所以就得到了一个新的指标来衡量应用的稳定性,即异常率。
UV 异常率 = 发生异常退出或崩溃的 UV / 登录 UV
前不久我们的一个应用灰度版本发现异常退出的比例增长不少,最后排查发现由于视频播放存在一个巨大bug,会导致可能有用户手机卡死甚至重启,这是传统崩溃收集很难发现的问题。
根据应用的前后台状态,我们可以把异常退出分为前台异常退出和后台异常退出。“被系统杀死”是后台异常退出的主要原因,当然我们会更关注前台的异常退出的情况,这会跟ANR、OOM等异常情况有更大的关联。
通过异常率我们可以比较全面的评估应用的稳定性,对于线上监控还需要完善崩溃的报警机制。在微信我们可以做到5分钟级别的崩溃预警,确保能在第一时间发现线上重大问题,尽快决定是通过发版还是动态热修复解决问题。
总结
今天,我讲了Android的两种崩溃,重点介绍了Native崩溃的捕获流程和一些难点。做一个高可用的崩溃收集SDK并不容易,它背后涉及Linux信号处理以及内存分配、汇编等知识,当你内功修炼得越深厚,学习这些底层知识就越得心应手。
接着,我们讨论了崩溃率应该如何去计算,崩溃率的高低跟应用时长、复杂度、收集SDK有关。不仅仅是崩溃率,我们还学习了目前ANR采集的方式以及遇到的问题,最后提出了异常率这一个新的稳定性监控指标。
作为技术人员,我们不应该盲目追求崩溃率这一个数字,应该以用户体验为先,如果强行去掩盖一些问题往往更加适得其反。我们不应该随意使用try catch去隐藏真正的问题,要从源头入手,了解崩溃的本质原因,保证后面的运行流程。在解决崩溃的过程,也要做到由点到面,不能只针对这个崩溃去解决,而应该要考虑这一类崩溃怎么解决和预防。
崩溃的治理是一个长期的过程,在专栏下一期我会重点讲一些分析应用崩溃的方法论。另外,你如果细心的话,可以发现,在这篇文章里,我放了很多的超链接,后面的文章里也会有类似的情况。所以,这就要求你在读完文章之后,或者读的过程中,如果对相关的背景信息或者概念不理解,就需要花些时间阅读周边文章。当然,如果看完还是没有明白,你也可以在留言区给我留言。
课后作业
Breakpad是一个跨平台的开源项目,今天的课后作业是使用Breakpad来捕获一个Native崩溃,并在留言区写下你今天学习和练习后的总结与思考。
当然我在专栏GitHub的Group里也为你提供了一个Sample方便你练习,如果你没使用过Breakpad的话,只需要直接编译即可。希望你可以通过一个简单的Native崩溃捕获过程,完成minidump文件的生成和解析,在实践中加深对Breakpad工作机制的认识。
我要再次敲黑板划重点了,请你一定要坚持参与我们的课后练习,从最开始就养成学完后立马动手操作的好习惯,这样才能让学习效率最大化,一步步接近“成为高手”的目标。当然了,认真提交作业的同学还有机会获得学习加油礼包。接下来,就看你的了!
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。


精选留言
2018-12-01 22:05:35
2018-12-24 14:54:19
2018-12-06 21:26:45
PC操作系统:Ubuntu18.04 X64
AS版本:3.1
手机:华为V8 8.0系统
遇到的问题:
1. 导入项目编译不过:
Build command failed.
Error while executing process /home/jayden/Android/Sdk/cmake/3.6.4111459/bin/cmake with arguments {-H/mnt/D.....
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
解决方法:
下载cmake 3.6对应的ndk版本,下载ndk12版本( https://dl.google.com/android/repository/android-ndk-r12b-linux-x86_64.zip)解压到sdk路径下,并在local.properties配置上对应的ndk.dir路径。
2. minidump_stackwalk dump对应的 crash文件时候,报错,如上老师说的,需要自己编译对应环境的源码,所以我选择下载https://github.com/google/breakpad里的depot_tools,然后make install出来自己用。也可以借鉴其他人下载缺失的头文件来编译github上的源码。
3. addr2line的时候,报错 File format not recognized, Linux的话需要用aarch64-linux-android-4.9,而不是arm-linux-androideabi-4.9 。
~/Android/Sdk/android-ndk-r12b/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line -f -C -e sample/build/intermediates/transforms/mergeJniLibs/debug/0/lib/arm64-v8a/libcrash-lib.so 0x5a0
Crash()
/mnt/Data/github/Geek_Android_Improve/Chapter01/sample/src/main/cpp/crash.cpp:10
2018-12-04 09:48:04
1.最好自备梯子,导入工程会很顺利
2.Smaple 里「补充内容」那几行代码是加到 build.gradle -> andoroid -> defaultConfig 里的
3.装在 ARM 设备上时提示找不到 "libbreakpad-core.so" , build 一个 apk 就好了
4.macOS 10.14 删除了 libstdc++.6.dylib 和 libstdc++.dylib 两个动态库,/usr/lib 里的同名文件都是 libstdc++.6.0.9.dylib 的替身,用 minidump_stackwalk 时会提示 「__ZTTNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE」这个方法找不到,通过 google 搜索都是 Xcode 开发相关的解决方案
ps 试着下 depot_tools 自己编译,要下载好几个 G 的数据,放弃了。
2019-01-15 22:34:45
2018-12-01 09:52:12
想问一下张老师有什么好的建议?
2018-12-06 13:12:52
git clone代码,下载安装NDK和CMAKE,在此期间碰到一个问题,就是NDK下载版本太高,导致项目编译不过去,进一步分析,发现ndk-bundle\toolchains文件夹中少了mips64el-linux-android-4.9和mipsel-linux-android-4.9文件,解决方法是下载一个旧的NDK版本,把这两个文件夹复制过去即可。
第二步:
接着就run,在手机上跑起来了,点击CRASH,程序崩溃,我进去sdcard中看到了crashDump目录,生成崩溃文件。
第三步:
接着运行: ./tools/mac/minidump_stackwalk crashDump/***.dmp >crashLog.txt ,结果又报错了,由于我的系统是Windows,不是Mac,故需要重新编译minidump_stackwalk 工具,解决方法是下载个VMWare虚拟机,装个Unbuntu 系统,然后重新编译,在编译过程中,由于git clone了breakpad源码,会缺少一个thrid_party里的一个less文件夹,名称为linux_syscall_support.h头文件,需要补上即可。
第四步:
利用./configure && make
对源码完成了编译,minidump_stackwalk这个文件在src/processor/目录中,然后运行:
./minidump_stackwalk crashDump/***.dmp >crashLog.txt
大功告成:
Operating system: Android
0.0.0 Linux 3.18.22+ #1 SMP PREEMPT Mon Jun 11 17:42:41 CST 2018 armv8l
CPU: arm
ARMv1 ARM part(0x4100d0b0) features: half,thumb,fastmult,vfpv2,edsp,neon,vfpv3,tls,vfpv4,idiva,idivt
10 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x77e
r0 = 0x00000000 r1 = 0x00000001 r2 = 0xffdb9bdc r3 = 0xf4a57fc0
r4 = 0x702af968 r5 = 0x6fb3bc10 r6 = 0x12e21970 r7 = 0xffdb9bc8
r8 = 0x12de5330 r9 = 0xf4a76a00 r10 = 0x12dd28b0 r12 = 0xf36a6fd8
fp = 0x70536430 sp = 0xffdb9bb4 lr = 0xf36a379b pc = 0xf36a377e
Found by: given as instruction pointer in context
1 dalvik-main space (deleted) + 0x5fffe
sp = 0xffdb9bcc pc = 0x12c60000
Found by: stack scanning
2 base.odex + 0x44121f
sp = 0xffdb9bd0 pc = 0xdf254221
Found by: stack scanning
3 dalvik-LinearAlloc (deleted) + 0xf016
sp = 0xffdb9bd4 pc = 0xeea9a018
Found by: stack scanning
4 dalvik-main space (deleted) + 0xd539e
sp = 0xffdb9be0 pc = 0x12cd53a0
Found by: stack scanning
2018-12-01 01:17:54
2018-12-05 17:34:17
接着就run,在手机上跑起来了,点击CRASH,程序崩溃,我进去sdcard中看到了crashDump目录,但没有生成崩溃文件,看到评论说的,将sample的build.gradle中注释给去掉:
abiFilters "armeabi-v7a", // "arm64-v8a", "x86" --> abiFilters "armeabi-v7a", "arm64-v8a", "x86"
我测试了下,成功生成了crash文件(不知道是不是张老师故意制造的障碍~捂手偷笑);
接着运行: ./tools/mac/minidump_stackwalk crashDump/***.dmp >crashLog.txt ,结果又报错了:
dyld: Symbol not found: __ZTTNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE
Referenced from: /Users/james/Documents/projec/breakpad/Chapter01/./tools/mac/minidump_stackwalk
Expected in: /usr/lib/libstdc++.6.dylib
in /Users/james/Documents/projec/breakpad/Chapter01/./tools/mac/minidump_stackwalk
Abort trap: 6
评论中老师说到缺少必要的动态链接库导致的,建议我们去编译breakpad源码;
然后,我去git clone了breakpad源码,利用
./configure
make
对源码完成了编译,minidump_stackwalk这个文件在src/processor/目录中,然后运行:
./minidump_stackwalk crashDump/***.dmp >crashLog.txt
大功告成,分享下我得成果:
Operating system: Android
0.0.0 Linux 3.18.31-perf-g4fd2040 #1 SMP PREEMPT Tue Dec 4 03:15:19 WIB 2018 aarch64
CPU: arm64
8 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x600
x0 = 0x0000007f7583e300 x1 = 0x0000007ff1b88ad4
x2 = 0x0000007f792e3000 x3 = 0x0000000001e5a140
2018-12-03 10:53:38
2018-12-01 21:38:08
2018-12-03 18:20:21
dyld: Symbol not found: __ZTTNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE
Referenced from: /Users/Downloads/Chapter01-master/tools/mac/./minidump_stackwalk (which was built for Mac OS X 10.13)
Expected in: /usr/lib/libstdc++.6.0.9.dylib
in /Users/Downloads/Chapter01-master/tools/mac/./minidump_stackwalk
Abort trap: 6
不知道什么原因?
2018-12-03 09:25:22
1.请问像微信这种大项目灰度测试一般是怎么做的? 是开发了专门的sdk么,我所知的一般是给某些渠道包作为灰度包,或者客户端根据服务器的配置来决定启用新功能,但感觉都挺麻烦的。
2.另外想问张老师个与本节无关的,就是以前做跑步App,发现进程保活很痛苦,想了解下微信的保活手段有哪些,是否真的有白名单这东西?
2018-12-03 00:30:56
2018-12-03 16:04:17
1.googlesource上breakpad源码无法下载
2.sample中的minidump_stackwalk无法运行
解决方案:
1.下载GitHub的镜像源码
2.自己编译breakpad
结果:
成功捕获native异常,并从dump文件中定位到异常代码
总结:
看似简单的例子在做的过程中也可能遇到各种问题,动手才能在做的过程中遇到问题,解决问题,提升自己的能力。
2018-12-07 11:12:21
然后今天更新代码后可以了,看到提交记录加了arm64-v8a。
代码里的minidump_stackwalk的版本是mac的,但是在Android Studio\bin\lldb\bin找到了minidump_stackwalk.exe,也可以解析出crashLog,格式和内容看起来都非常正常,但是再使用arm-linux-androideabi-addr2line来定位代码就始终显示?,无法成功。 怀疑是minidump_stackwalk的问题。看了评论区发现windows的用户都是用了虚拟机的linux才成功的。是minidump_stackwalk无法在windows平台使用么? 我已经在网上找了一圈,都没有找到如何在windows下编译minidump_stackwalk。望大佬帮忙解答下,谢谢!
2018-12-03 21:35:47
1. breakpad 下载问题,官网提供的工具可能是网络环境问题,没有下载下来,可以直接下载 master 分支 tgz 包
2. third_party/ssl/linux_syscall_support.h 缺失问题,https://chromium.googlesource.com/linux-syscall-support/+/refs/heads/master 同样下载 tgz 包,解压 linux_syscall_support.h 放在项目 third_party/ssl/ 目录
3. PC 端 minidump_stackwalk 工具,需要用 breakpad 源码编译
./configure
make
make install
项目例子地址 : https://github.com/sunnybird/AdvanAndroid/tree/master/BreakpadDemo
2018-12-03 10:13:54
2021-11-29 05:24:12
能成功的版本号:
nkd:16
Android Gradle Plugin Version:4.0.1
Gradle version: 6.1.1
cmake:3.6
遇到的问题:
1.下好ndk16时发现系统仍然会通过ANDROID_NDK读到之前的ndk20,导致错误
解决方法:手动在terminal输入export ANDROID_NDK=path/to/ndk16
2.emulator闪退报错:
Android Emulator closed because internal error: emuglConfig_init: blacklisted=0 has_guest_renderer=1
解决方法:
在AVD Configuration->Emulated Performance, 选择Software而不是Graphics
minidump_stackwalker 工具来根据 minidump 文件生成堆栈跟踪log:
$ ./minidump_stackwalk ../../../Chapter01/1e39fbb5-2d42-41be-23ac02b4-7fa793dc.dmp > ../../../Chapter01/crashLog.txt
Operating system: Android
0.0.0 Linux 4.4.124+ #1 SMP PREEMPT Wed Jan 30 07:13:09 UTC 2019 i686
CPU: x86
AuthenticAMD family 6 model 6 stepping 3
4 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x515
eip = 0xd14ef515 esp = 0xffb643a8 ebp = 0xffb643b8 ebx = 0xd14f0fe4
esi = 0xd19bc35f edi = 0xffb645bc eax = 0x00000000 ecx = 0x53525a6e
edx = 0xffb643d0 efl = 0x00210296
Found by: given as instruction pointer in context
1 libcrash-lib.so + 0x535
eip = 0xd14ef535 esp = 0xffb643c0 ebp = 0xffb643c8
Found by: previous frame's frame pointer
2 libart.so + 0x5f6a18
eip = 0xe9e7da18 esp = 0xffb643d0 ebp = 0xffb643f0
Found by: previous frame's frame pointer
符号解析,可以使用 ndk 中提供的addr2line来根据地址进行一个符号反解的过程:
$ ./Android/Sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line -f -C -e Learning/Chapter01/sample/build/intermediates/merged_native_libs/debug/out/lib/x86/libcrash-lib.so 0x515
输出结果如下:
Crash()
Learning/Chapter01/sample/.cxx/cmake/debug/x86/../../../../src/main/cpp/crash.cpp:10
2019-03-26 11:41:12
// Get information about the stack, given the stack pointer. We don't try to
// walk the stack since we might not have all the information needed to do
// unwind. So we just grab, up to, 32k of stack.),
请问这个理解是正确的吗?如果是正确的,那么只要运行时的栈空间大于32k,那么在服务端进行堆栈回溯的时候,就会有问题,为了验证这个猜测,我在您给的demo基础上进行了测试,代码如下:
//==========Code 1===========
int frame[1024];
memset(frame, 0, sizeof(int)*1024);
volatile int *a = (int *) (NULL);
*a = 1;
//==========Code 2===========
int frame[40*1024];
memset(frame, 0, sizeof(int)*40*1024);
volatile int *a = (int *) (NULL);
*a = 1;
Code 1和Code2的堆栈是不一致的,Code1中可以看到函数的调用关系,而Code2中只能看到最后crash的函数,没有调用关系。