Momo 跨平台的crash-report解决方案
- 最近负责公司的质量平台建设,参与了陌陌的crash-report方案设计和开发。
- 经过优化封装后,已对外使用,组件地址
- 使用方式请联系陌陌企业服务
- iOS程序崩溃和抓取原理
Crash 背景
对于所有崩溃场景,仅25%的崩溃可通过信号量捕获,实施相应改进;另有75%的崩溃则难以识别,从而对App的用户体验,造成了巨大的潜在影响。
Facebook的工程师将App退出分为以下6个类别:
1.App内部主动调用exit()或abort()退出;
2.App升级过程中,用户进程被杀死;
3.系统升级过程中,用户进程被杀死;
4.App在后台被杀死;
5.App在前台被杀死,且可获取堆栈;
6.App在前台被杀死,且无法获取堆栈。
对于第1~4类退出,属于App的正常退出,对用户体验没有太大影响,无需进行相应处理;对于第5类退出,可通过堆栈代码级定位崩溃原因,对此业界已形成比较成熟的解决方案,;对于第6类退出,可能的原因很多,包括但不限于:系统内存不足时继续申请内存、主线程卡死20s以上、CPU使用率过高Stack Overflow等,在此我们统一称之为iOS客户端的“Abort问题”。
Abort问题无法被堆栈捕获,且发生频次远高于可被捕获的崩溃(下称“堆栈崩溃”)。从历史数据来看,手淘(电商类超级App代表)的Abort问题数量一般是堆栈崩溃数量的3倍左右;优酷Pad(视频类超级App代表)的Abort问题数量一般是堆栈崩溃数量的5倍左右。可见,Abort问题对用户的使用体验造成巨大影响。
本文将针对iOS客户端的Abort问题,进行根因定位分析,并提出系统性解决方案。
对于oom,除了facebook的排除法,还有字条抖音的 Memory Graph主动分析方式,参看iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%+
微信 https://wetest.qq.com/lab/view/367.html
task_vm_info.phys_footprint: 47.4MB
task_info.resident_size: 80MB
Abort问题的原因分类
形成Abort问题的原因主要包括以下4个。
1 内存 Jetsam
移动端设备的物理内存资源紧张,但App仍不断申请内存。因此系统signal 9杀死进程,造成异常退出。
1 | { "memoryPages" : { "active" : 24493, "throttled" : 0, "fileBacked" : 24113, "wired" : 13007, "anonymous" : 12915, "purgeable" : 127, "inactive" : 10955, "free" : 2290, "speculative" : 1580 }, "uncompressed" : 125795, "decompressions" : 143684 }, "largestProcess" : "Taobao4iPhone", "processes" : [ { ... { "rpages" : 2050, "states" : [ "frontmost", "resume" ], "name" : "Taobao4iPhone", "pid" : 1518, "reason" : "vm-thrashing", "fds" : 50, "uuid" : "5103a88a-917f-319e-8553-c0189dd1abac", "purgeable" : 127, "cpuTime" : 4.619693, "lifetimeMax" : 3557 }, ... } |
2 主线程死锁
A/B两个线程同时等待对方完成某些操作,因而无法继续执行,形成死锁,造成异常退出。
1 | Exception Type: 00000020Exception Codes: 0x000000008badf00dHighlighted Thread: 0 Application Specific Information:com.myapp.myapp failed to scene-create in time Elapsed total CPU time (seconds): 4.230 (user 4.230, system 0.000), 10% CPU Elapsed application CPU time (seconds): 1.039, 3% CPU Thread 0 name: Dispatch queue: com.apple.main-threadThread 0:0 libsystem_kernel.dylib 0x36360540 semaphore_wait_trap + 81 libdispatch.dylib 0x36297eee _dispatch_semaphore_wait_slow + 1862 libxpc.dylib 0x364077b8 xpc_connection_send_message_with_reply_sync + 1523 Security 0x2b8dd310 securityd_message_with_reply_sync + 644 Security 0x2b8dd48c securityd_send_sync_and_do + 445 Security 0x2b8ea452 __SecItemCopyMatching_block_invoke + 1666 Security 0x2b8e96f6 SecOSStatusWith + 147 Security 0x2b8ea36e SecItemCopyMatching + 174 |
3 启动/重启超时
App由于启动/重启的时间超过系统允许的时间限制,造成异常退出。
scene-create watchdog transgression: app exhausted real (wall clock) time allowance of 19.93 seconds, Elapsed total CPU time (seconds): 21.050 (user 21.050, system 0.000)
4 CPU打爆
主线程死锁、启动/重启超时,都可能间接导致CPU打爆,造成异常退出。
1.Abort问题发生的场景:例如,哪个页面、什么操作。
2.Abort问题发生的原因:例如,内存Jetsam、主线程死锁、启动/重启超时、CPU打爆。
3.对于内存Jetsam,需进一步定位到是否发生了内存泄露以及泄露的循环引用(Retain Cycle)。
4.对于主线程死锁,需进一步定位到卡死的堆栈。
5.对于启动/重启超时,以及CPU打爆,需进一步定位到堆栈。
abort问题的系统性解决方案
- 现场捕获
为实现Abort问题的系统性解决方案,需充分考虑以下问题:
1.通过signal 9杀死进程造成的Abort问题,往往难以通过信号量捕获至堆栈。在这种情况下,应如何尽可能完整地捕获崩溃现场的关键信息?具体包含哪些信息?
2.App崩溃时系统处于极不稳定的状态,应如何保证崩溃现数据稳定落盘?
3.在信息采集、数据捕获的过程中,需对大量数据进行写入操作,应如何保证日志高性能写入?
4.在数据量较大的情况下,数据的存储、上传可能对系统造成较大压力,应如何保证数据的高压缩率?基于以上考虑,我们提出并设计了一套基于mmap的高性能、高压缩率、高一致性、可自解释的trace文件协议,作为iOS端高可用体系的数据载体。
mmap数据存储层保证数据写入的高性能和高一致性
- 通过mmap将一个文件或者其它对象映射到进程的地址空间,对内存的操作会由内核将数据写到对应的磁盘文件上;数据写入的性能与内存操作相当(略比内存操作高)
- 用户进程崩溃之后,这块映射区仍由内核管理,可以保证数据的一致性
二进制编码协议保证数据压缩率最高
- 具体编码协议
- 实测编码在压缩率能达到80%以上,或者直观一点说,使用50k的内存可以记录下用户二十分钟内详细的使用记录,包括页面访问记录、系统事件、秒级别的内存、CPU数据。
尽可能多的记录系统多维度指标及异常事件包括:
- 性能数据,包括CPU、内存数据,用于判断应用当前是不是处理overload状态
- 大内存申请
- Retain Cycle,用于定位Jetsam Event
- 卡顿,用于定位watch dog kill
- 当前存活VC实例数量
BreakPad 跨平台的crash-report解决方案
官方地址
Github地址
介绍breakpad
大致流程
breakpad原理图
主要包括三个部分
dumpSyms
负责 读取 用户开发应用中的debug信息 并生成特定的符号文件参考client
在崩溃系统中也就是指的崩溃上报的sdk 负责抓取当前线程和当前载入的库 生成 minidump文件processor
也就是崩溃系统中的mnidump_stackwalk 读取minidump文件 找到合适的符号文件 产生一个人类可读的c/c++调用栈
MiniDump文件格式
- minidump文件格式是由微软开发的用于崩溃上传,它包括:
- 当dump生成时进程中一系列executable和shared libraries, 包括这些文件的文件名和版本号。
- 进程中的线程列表,对于每个线程,minidump包含它在寄存器中的状态,线程的stack memory内容。这些数据都是未解析的字节流,Breakpad client通常没有调试信息(debugging information)能生成函数名,行号,甚至无法确定stack frame的边界。
- 其他收集关于系统的信息,如:处理器,操作系统高版本,dump的原因等等。
- breakpad在所有平台上(windows/linux等)都统一使用minidump文件格式,而不使用core files,原因是因为:
- core files可能很大,而minidump比较小。
- core files文档不全
- 很难说服windows机器去生成core files,但可以说服其他机器来生成minidump文件。
- breakpad只支持一种统一的格式会比较简单,而不是同时支持多种格式。
Symbols文件格式
symbols文件是基于纯文本的,每一行一条记录,每条记录中的字段以一个空格作为分隔符,每条记录的第一个字段表示这一行是什么类型的记录。
记录类型:
- 模块记录:
MODULE
operatingsystem architecture id name - 文件记录:
FILE
number name - 函数记录:
FUNC
address size parameter_size name - 行号记录:
address size line filenum
- PUBLIC记录:
PUBLIC
address parameter_size name STACK WIN
STACK CFI
不同平台的实现原理
默认情况下,当崩溃时breakpad会生成一个minidump文件,在不同平台上的实现机制不一样:
在windows平台上,使用微软提供的 SetUnhandledExceptionFilter() 方法来实现。
在OS X平台上,通过创建一个线程来监听 Mach Exception port 来实现。
在Linux平台上,通过设置一个信号处理器来监听 SIGILL SIGSEGV 等异常信号。
发送minidump文件
在真实环境中,你通常需要以某种方式来处理minidump文件,例如把它发送给服务器来进行分析,Breakpad源码提供了一些HTTP上传的代码,并提供一个minidump上传工具( 详见minidump_upload.cc)。
生成symbols文件
为了生成可读的stack trace, breakpad需要你将binaries里的调试符号(debugging symbols)转换成基于文本格式的symbol files。
首先确保你在编译代码的时候加上 -g 参数来生成带调试符号的。
然后使用 configure && make breakpad源码来生成 dump_syms
工具。
生成Stack Trace
breakpad包含一个叫做 minidump_stackwalk 的工具来将 minidump 文件,外加symbol files来生成一个人可读的stack trace。在编译breakpad后,这个工具一般在 google-breakpad/src/processor 目录下, 通过将 minidump 和 symbol files 传入给它即可:
客户端功能
每次crash都会生成三部分文件,目录结构如下
1 | └── 根目录 |
接下来写一篇KSCrash读后感
kscm_handleException
参考
https://www.jianshu.com/p/295ebf42b05b
https://juejin.cn/post/6868230552571346951
https://engineering.fb.com/2015/08/24/ios/reducing-fooms-in-the-facebook-ios-app/
https://wetest.qq.com/lab/view/367.html