iOS SIGKILL 信号量崩溃抓取以及优化实践

一、什么是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 动态库依赖

iOS SIGKILL 信号量崩溃抓取以及优化实践

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 搜集溃散的不足

  1. 只支持 iOS14 今后的溃散日志搜集;PS:MetricKit是iOS13开端有的结构,可是溃散日志的支持是iOS14开端支持的。

  2. 溃散日志没有回来详细的溃散时刻和发动时刻,溃散场景信息除了仓库外没有其余信息,附加信息较少,需要另外的手法来搜集

  3. 如果运用了段迁移编译技术,主程序 Mach-O 的加载地址和 uuid MetricKit无法给出正确的值,需要破例处理。可通过 Mach-O文件的LC-MAIN进口来获取主程序main函数的地址,然后算出加载其起始地址。

    iOS SIGKILL 信号量崩溃抓取以及优化实践

  • iOS14 的溃散日志是24小时会回调通知一次,时效性低;iOS15 之后,溃散日志会在下次发动之后就回来,但经验证,可能有破例情况。

三、SIGKILL日志中Code的意义解释

  • 0x8badf00d

发音(ate bad food),意思就是吃了坏的食物。表明操作体系由于看门狗原因杀死了使用程序;详细能够参见苹果文档Addressing Watchdog Terminations (developer.apple.com/documentati…)

  • 0xc00010ff

发音 (cool off)。表明操作体系由于过热杀死了使用程序, 关于怎么样使你的程序更高效,更低消耗,能够观看:

a. iOS Performance and Power Optimization with Instruments (developer.apple.com/videos/)

b. WWDC session (developer.apple.com/videos/)

  • 0xbaadca11

发音(bad call)。表明 使用程序呼应 PushKit 的消息而且CallKit调用失利,然后使用程序被体系杀死。

  • 0xbad22222

表明由于通过 VoIP 调起程序太频频而被体系杀死。

  • 0xc51bad01

watchOS 由于后台使命耗费太多 CPU 时刻而被杀死。这就需要优化和削减后台使命的 CPU 时刻,提高 CPU 运用效率,或许在后台的时分削减很多使命。

  • 0xc51bad02

watchOS 由于后台使命不能在初始化时刻内完结而杀死 使用程序,削减在后台使命的数量能够处理这个问题。

四、百度App常见SIGKILL问题

4.1 主线程执行耗时操作太久

当使用程序在阻塞主线程一段时刻之后就会被看门狗杀死,一般的耗时事件可能有如下几种景象:

  • 弱网下同步的网络请求

  • 处理很多的数据的使命,比如大的JSON文件或许 3D 模型的加载和处理

  • 触发很多的 Core Data 同步保存操作

  • 触发很多的数据库操作等

  • 主线程解码大图片,解压文件等操作

通用处理计划:

将耗时使命的处理放在子线程处理,等处理完结之后回调给主线程, 类似如下操作,在单独的行列处理使命,处理之后回调 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

github.com/alibaba/dex…

[2] Understanding the Exception Types in a Crash Report

developer.apple.com/documentati…

[3] MetricKit Framework

developer.apple.com/documentati…

[4] Examining the Fields in a Crash Report

developer.apple.com/documentati…

引荐阅读:

如何在几百万qps的网关服务中实现灵活调度战略

浅显易懂DDD编程

百度APP iOS端内存优化实践-内存管控计划

Ernie-SimCSE对比学习在内容反作弊上使用

质量评价模型助力风险决议计划水平提高

合约广告平台架构演进实践