0%

Breakpad

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问题的系统性解决方案

  1. 现场捕获
    为实现Abort问题的系统性解决方案,需充分考虑以下问题:
    1.通过signal 9杀死进程造成的Abort问题,往往难以通过信号量捕获至堆栈。在这种情况下,应如何尽可能完整地捕获崩溃现场的关键信息?具体包含哪些信息?
    2.App崩溃时系统处于极不稳定的状态,应如何保证崩溃现数据稳定落盘?
    3.在信息采集、数据捕获的过程中,需对大量数据进行写入操作,应如何保证日志高性能写入?
    4.在数据量较大的情况下,数据的存储、上传可能对系统造成较大压力,应如何保证数据的高压缩率?

    基于以上考虑,我们提出并设计了一套基于mmap的高性能、高压缩率、高一致性、可自解释的trace文件协议,作为iOS端高可用体系的数据载体。

  1. mmap数据存储层保证数据写入的高性能和高一致性

    • 通过mmap将一个文件或者其它对象映射到进程的地址空间,对内存的操作会由内核将数据写到对应的磁盘文件上;数据写入的性能与内存操作相当(略比内存操作高)
    • 用户进程崩溃之后,这块映射区仍由内核管理,可以保证数据的一致性
  2. 二进制编码协议保证数据压缩率最高

    • 具体编码协议
    • 实测编码在压缩率能达到80%以上,或者直观一点说,使用50k的内存可以记录下用户二十分钟内详细的使用记录,包括页面访问记录、系统事件、秒级别的内存、CPU数据。
  3. 尽可能多的记录系统多维度指标及异常事件包括:

    • 性能数据,包括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
2
3
4
5
6
7
8
9
10
11
12
13
└── 根目录
├── basic.json //基础信息,展示在crash详情页

├── logs //业务方自定义日志,在展示在跟踪日志中
│ ├── log_1.txt
│ ├── log_2.txt
│ └── log_3.txt

└── stack.dmp(Android Native Crash / iOS Crash生成次文件)//堆栈信息,用于定位问题

└── logcat.log (控制台日志文件,2020110914:52:07添加,暂时只有android)

└── jvm.dmp (Android Java 堆栈信息) //安卓独有

接下来写一篇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

希望对您有所帮助,您的支持将是我莫大的动力!