崩溃捕获
崩溃主要是由于 Mach 异常、Objective-C 异常(NSException)引起的,同时对于 Mach 异常,到了 BSD 层会转换为对应的 Signal 信号,那么我们也可以通过捕获信号,来捕获 Crash 事件。针对 NSException 可以通过注册 NSUncaughtExceptionHandler 捕获异常信息
冲突
在我们自己研发 Crash 收集框架之前,最早肯定都会接入网易云捕、腾讯 Bugly、Fabric 等第三方日志框架来进行崩溃的收集和分析。如果多个 Crash 收集框架存在时,往往会存在冲突
不管是对于 Signal 捕获还是 NSException 捕获都会存在 handler 覆盖的问题,正确的做法应该是先判断是否有前者已经注册了 handler,如果有则应该把这个 handler 保存下来,在自己处理完自己的 handler 之后,再把这个 handler 抛出去,供前面的注册者处理
堆栈收集
你可以直接用系统的方法获取当前线程堆栈,也可以使用 PLCrashRepoter 获取所有线程堆栈
堆栈符号解析
堆栈符号化还原有四种常见的方法
1.symbolicatecrash
2.mac 下的 atos 工具
3.linux 下的 atos 的替代品 [atosl](https://link.jianshu.com?t=https://github.com/facebookarchive/atosl)
4.通过 dSYM 文件提取地址和符号的对应关系,进行符号还原
以上方案都有对应的应用场景,对于线上的 Crash 堆栈符号还原,主要采用的还是后三种方案。atos 和 atosl 的使用方法很类似,以下是 atos 的一个示例
atos -o MonitorExample 0x0000000100062ac4 ARM-64 -l 0x100058000
// 还原结果
-[GYRootViewController tableView:cellForRowAtIndexPath:] (in GYMonitorExample) (GYRootViewController.m:41)
使用第四个方案,提取 dSYM 的符号表,可以自己研发工具,也可以直接使用 bugly 和 网易云捕提供的工具,下面是提取出来的符号表。第一列是起始内存地址,第二列是结束地址,第三列是对应的函数名、文件名以及行号
a840 a854 -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:41
a854 a858 -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42
a858 a87c -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42
a87c a894 -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42
a894 a8a0 -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42
aa3c aa80 -[GYFilePreviewViewController initWithFilePath:] GYRootViewController.m:21
aa80 aaa8 -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:23
aaa8 aab8 -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:23
aab8 aabc -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:24
aabc aac8 -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:24
因为程序每次启动基地址都会变化,所以上面提到的地址是相对偏移地址,在我们获取到崩溃堆栈地址后,可以根据堆栈中的偏移地址来与符号表中的地址来做匹配,进而找到堆栈所对应的函数符号。比如下面的第四行,偏移为 43072 转换为十六进制就是 a840,用 a840 去上面的符号表中找对应关系,会发现对应着 -[GYRootViewController tableView:cellForRowAtIndexPath:],基于这种方式,就可以将堆栈地址完全还原为函数符号啦
UUID
我们的应用存在多个版本,并且支持多种不同的架构,那么如何找到与崩溃日志对应的符号表呢?就是依靠 UUID,只有当崩溃日志的 UUID 与 dSYM 的 UUID 一致时,才能得到正确的解析结果
dSYM 的 UUID 获取方法
xcrun dwarfdump --uuid <dSYM文件>
应用内获取 UUID 的方法
#import <mach-o/ldsyms.h>
NSString *executableUUID()
{
const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);
for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {
if (((const struct load_command *)command)->cmd == LC_UUID) {
command += sizeof(struct load_command);
return [NSString stringWithFormat:@"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
command[0], command[1], command[2], command[3],
command[4], command[5],
command[6], command[7],
command[8], command[9],
command[10], command[11], command[12], command[13], command[14], command[15]];
} else {
command += ((const struct load_command *)command)->cmdsize;
}
}
return nil;
}
系统库符号化
上面只是提取到了我们应用中 dSYM 中的符号表,对于系统库还是无能为力的,比如 UIKit 就没有办法将其地址符号化,想要将动态库符号化,需要先获取系统库的符号文件。提取系统符号文件可以从 iOS 固件中获取,也可以从 Github 上开源项目中找到对应系统的符号文件
网友评论