一、什么是 SIGKILL 崩溃
很多时候,当我们在崩溃日志中看到 SIGKILL 关键信息的时候,这就表示操作系统从上层杀死了我们的进程,也就是我们常说的 kill -9 命令。
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace RUNNINGBOARD, Code 0xdead10cc
复制代码
一般来说,Apple 崩溃日志里面通常都会包含应用程序被杀死的具体的原因。如上所示,Termination Reason 里面就包含了这个崩溃的错误代码 0xdead10cc,就表示应用程序挂起的时候发生了文件和数据库锁操作而被操作系统杀死。
二、怎么抓取 SIGKILL 崩溃
1 为什么 SIGKILL 不能被捕获
和其他信号不同,SIGKILL 是不可被捕获的。这是 Linux / Mach 内核的限制,这种限制就是为了让操作系统在程序无法响应的时候,可以从上一层控制进程的生命周期。
2 使用 MetricKit 框架捕获 SIGKILL
2.1 Metrickit 是什么
MetricKit 框架是苹果在 iOS13 系统开始引入的用来汇总和分析有关异常和崩溃诊断以及电源和性能指标的动态库。
2.2 使用 Metrickit 收集 SIGKILL 信号量 的好处
不需要注册信号量捕获回调函数
不需要时刻监控,只需冷启阶段注册获取一次就行
2.3 怎么使用 Metrickit 获取崩溃信息
2.3.1 添加 MetricKit 动态库依赖
2.3.2 注册 MetricKit 监听者
if (@available(iOS 14.0, *)) {
MXMetricManager *manager = [MXMetricManager sharedManager];
if (self && manager && [manager respondsToSelector:@selector(addSubscriber:)]) {
[manager addSubscriber:self];
}
}
复制代码
2.3.3 监听者实现 MXMetricManagerSubscriber 协议方法,payloadDic 里面包含着上次本应用发生的崩溃日志堆栈和信息
// 用户如果有崩溃数据,注册监听之后就会回调
- (void)didReceiveDiagnosticPayloads:(NSArray<MXDiagnosticPayload *> * _Nonnull)payloads API_AVAILABLE(ios(14.0)){
if (@available(iOS 14.0, *)) {
for (MXDiagnosticPayload *payload in payloads) {
NSDictionary *payloadDic = [payload dictionaryRepresentation];
});
}
}
}
复制代码
2.3.4 当收到回调消息后,需要对关键信息做组装,获取崩溃堆栈和相关关键信息
NSArray *callStackRootFrames = [dicFrame ArrayValueForKey:@"callStackRootFrames"];
if (callStackRootFrames.count <= 0) {
continue;
}
NSDictionary *dicZero = [callStackRootFrames ObjectAtIndex:0];
int rootIndex = 0;
while (dicZero && dicZero.count > 0) {
//获取Image 的 UUID
NSString *binaryUUID = [dicZero stringValueForKey:@"binaryUUID"];
//获取Image 的 名称
NSString *binaryName = [dicZero stringValueForKey:@"binaryName"];
//获取Image 的加载地址
long long baseAdd = [[dicZero NumberValueForKey:@"offsetIntoBinaryTextSegment"] longLongValue];
//获取崩溃函数的地址
long long address = [[dicZero numberValueForKey:@"address"] longLongValue];
//看上一层调用堆栈的
NSArray *subFrames = [dicZero arrayValueForKey:@"subFrames"];
[strStack appendFormat:@"%d %@ 0x%llx 0x%llx + %lld\n", rootIndex, binaryName, baseAdd, address, address - model.baseAddress];
rootIndex++;
if (subFrames && subFrames.count >= 0) {
dicZero = [subFrames ObjectAtIndex:0];
} else {
dicZero = nil;
}
}
复制代码
2.3.5 使用 Metrickit 收集崩溃的不足
只支持 iOS14 以后的崩溃日志收集;PS:MetricKit 是 iOS13 开始有的框架,但是崩溃日志的支持是 iOS14 开始支持的。
崩溃日志没有返回具体的崩溃时间和启动时间,崩溃场景信息除了堆栈外没有其余信息,附加信息较少,需要另外的手段来收集
如果使用了段迁移编译技术,主程序 Mach-O 的加载地址和 uuid MetricKit 无法给出正确的值,需要例外处理。可通过 Mach-O 文件的 LC-MAIN 入口来获取主程序 main 函数的地址,从而算出加载其起始地址。
三、SIGKILL 日志中 Code 的含义解释
发音(ate bad food),意思就是吃了坏的食物。表示操作系统因为看门狗原因杀死了应用程序;具体可以参见苹果文档 Addressing Watchdog Terminations (https://developer.apple.com/documentation/xcode/addressing-watchdog-terminations)
发音 (cool off)。表示操作系统因为过热杀死了应用程序, 关于怎么样使你的程序更高效,更低消耗,可以观看:
a. iOS Performance and Power Optimization with Instruments (https://developer.apple.com/videos/)
b. WWDC session (https://developer.apple.com/videos/)
发音(bad call)。表示 应用程序响应 PushKit 的消息并且 CallKit 调用失败,从而应用程序被系统杀死。
表示因为通过 VoIP 调起程序太频繁而被系统杀死。
watchOS 因为后台任务耗费太多 CPU 时间而被杀死。这就需要优化和减少后台任务的 CPU 时间,提高 CPU 使用效率,或者在后台的时候减少大量任务。
watchOS 因为后台任务不能在初始化时间内完成而杀死 应用程序,减少在后台任务的数量可以解决这个问题。
四、百度 App 常见 SIGKILL 问题
4.1 主线程执行耗时操作太久
当应用程序在阻塞主线程一段时间之后就会被看门狗杀死,一般的耗时事件可能有如下几种情形:
通用解决方案:
将耗时任务的处理放在子线程处理,等处理完成之后回调给主线程, 类似如下操作,在单独的队列处理任务,处理之后回调 block
- (void)getContentArray:(void (^)(NSArray *resultArray))completeBlock {
dispatch_barrier_async(self.readWriteQueue, ^{
if (completeBlock) {
NSArray *resultArray = [NSArray arrayWithArray:self.array];
completeBlock(resultArray);
}
});
}
复制代码
4.2 主线程和子线程死锁,陷入互相等待的循环
举个栗子:
主线程和子线程在单例初始化的时候陷入死锁 [xxxConfig sharedInstance],见如下崩溃堆栈
Thread 0 Crashed:
0 libsystem_kernel.dylib ___ulock_wait (in libsystem_kernel.dylib) 8
1 libdispatch.dylib __dlock_wait (in libdispatch.dylib) 56
2 libdispatch.dylib __dispatch_once_wait (in libdispatch.dylib) 120
3 BaiduBoxApp +[xxxConfig sharedInstance] (in BaiduBoxApp) 20
4 BaiduBoxApp +[xxxConfig updateABConfig] (in BaiduBoxApp) 0
5 BaiduBoxApp -[xxxManager startOnce] (in BaiduBoxApp) 20
复制代码
子线程堆栈:
Thread 33 :0 libsystem_kernel.dylib ___ulock_wait (in libsystem_kernel.dylib) 81 libdispatch.dylib __dlock_wait (in libdispatch.dylib) 562 libdispatch.dylib __dispatch_thread_event_wait_slow (in libdispatch.dylib) 563 libdispatch.dylib ___DISPATCH_WAIT_FOR_QUEUE__ (in libdispatch.dylib) 3644 libdispatch.dylib __dispatch_sync_f_slow (in libdispatch.dylib) 144.........9 BaiduBoxApp ___48+[xxxConfig sharedInstance]_block_invoke (in BaiduBoxApp) 010 libdispatch.dylib __dispatch_client_callout (in libdispatch.dylib) 2011 libdispatch.dylib __dispatch_once_callout (in libdispatch.dylib) 3212 BaiduBoxApp +[xxxConfig sharedInstance] (in BaiduBoxApp) 20
复制代码
——END——
相关链接:
[1] Addressing Watchdog Terminations
https://github.com/alibaba/dexposed
[2] Understanding the Exception Types in a Crash Report
https://developer.apple.com/documentation/xcode/understanding-the-exception-types-in-a-crash-report#EXC_CRASH-
[3] MetricKit Framework
https://developer.apple.com/documentation/metrickit?language=objc
[4] Examining the Fields in a Crash Report
https://developer.apple.com/documentation/xcode/examining-the-fields-in-a-crash-report
评论