iOS崩溃分析查找

描述崩溃

当应用发生崩溃时,会创建崩溃报告并存储在设备上,通过日志查找问题并解决

崩溃日志头部分析:

Exception Type: EXC_BAD_ACCESS (SIGSEGV) 异常类型。
Exception Codes: 0x0000000000000000, 0x0000000000000000 有关异常的处理器特定信息,编码为一个或多个64位十六进制数。通常,此字段将不存在,因为Crash Reporter会解析异常代码以将其作可读的描述的在其它字段中。
Exception Note: EXC_CORPSE_NOTIFY 非特定于一种异常类型的附加信息。如果字段为SIMULATED则进程未崩溃,但在系统请求(通常是监视程序)时被杀死。
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000 可读的异常代码名称。
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb 终止进程时指定的退出原因信息。进程内部和外部的关键系统组件将在遇到致命错误时终止进程(例如,错误的代码签名,缺少的依赖库,或在没有适当权利的情况下访问隐私敏感信息)。
Terminating Process: exc handler [0]
Triggered by Thread: 0 崩溃的线程。

Exception Type异常类型的信息:

Thread 1: signal SIGABRT

  • SIGABRT: 收到Abort信号退出, 通常Foundtion库中的容器为了保护状态正常会做一些检测, 例如插入nil到数据中等会遇到此类错误.

  • SIGSEGV:通常由于重复释放对象导致, 一般在ARC以后很少见到

  • SEGV:代表无效内存地址, 比如空指针, 未初始化指针, 栈溢出等.

  • SIGBUS:总栈错误, 与SIGSEGV不同的是, SIGSEGV访问的是无效的地址, 而SIGBUS访问的是有效的地址, 但是总栈访问异常(如地址对齐问题)

  • SIGILL: 尝试执行非法的指令, 可能不被识别或者没有权限

  • SIGFPE:数学计算相关问题, 比如除零操作

  • SIGIPIPE: 管道另一端没有进程接手数据

  • SIGQUIT:该进程在另一个进程的请求下终止,并具有管理其生命周期的权限。SIGQUIT并不意味着这是崩溃

  • SIGKILL:该过程在系统请求时终止
    1、终止代码0xc51bad01表示监视应用程序已终止,因为它在执行后台任务时使用了太多CPU时间。要解决此问题,请优化执行后台任务的代码以提高CPU效率,或减少应用程序在后台运行时执行的工作量。
    2、终止代码0xc51bad02表示监视应用程序因未能在分配的时间内完成后台任务而终止。要解决此问题,请减少应用在后台运行时执行的工作量。
    3、终止代码0xc51bad03表示监视应用程序未能在分配的时间内完成后台任务,并且系统整体上非常繁忙,以至于应用程序可能没有多少CPU时间来执行后台任务。虽然应用程序可以通过减少后台任务中执行的工作量来避免此问题,0xc51bad03但并不表示应用程序执行了任何错误操作。更有可能的是,由于整体系统负载,应用程序无法完成其工作

EXC_BAD_ACCESS:此类型是最常见的crash, 通常用于访问了不该访问的内存导致的

  • KERN_PROTECTION_FAILURE:是指的地址无权限访问 + 错误地址
  • KERN_INVALID_ADDRESS:是指的地址不可用,异常信息还会给出具体的错误地址

EXC_CRASH:此类异常最常见的是调用abort()

EXC_BREAKPOINT:与异常退出类似,此异常在使附加调试器有机会在其执行的特定点中断进程。

EXC_BAD_INSTRUCTION:此类异常通常由于线程执行非法指令导致,该过程可能试图通过配置错误的函数指针跳转到无效地址

EXC_ARITHMETIC:除零错误会抛出此类异常

Exception Codes: 常见代码有以下几种

  • 0x8badf00d错误码:iOS已终止应用程序,因为发生了监视程序超时。应用程序花费太长时间来启动,终止或响应系统事件。
  • 0xdeadfa11错误码:用户强制退出,意为“dead fall”。
  • 0xbaaaaaad错误码:用户按住Home键和音量键,获取当前内存状态,不代表崩溃。
  • 0xbad22222错误码:VoIP应用(因为太频繁?)被iOS干掉。
  • 0xc00010ff错误码:因为太烫了被干掉,意为“cool off”。
  • 0xdead10cc错误码:因为在后台时仍然占据系统资源(比如通讯录)被干掉,意为“dead lock”
  • 0x2bad45ec错误代码:表示由于安全违规而导致应用程序被iOS终止。终止描述“在安全模式下检测到进行不安全绘图的过程”表示应用程序试图在不允许的情况下绘制到屏幕,例如屏幕被锁定时。用户可能不会注意到此终止,因为屏幕关闭或在此终止发生时显示锁定屏幕。

还有一种崩溃是 低内存崩溃 此类型的崩溃产生的文件和异常不太一样,如果app应用了大量图片视频耗内存操作可以了解下

野指针

  • C语言: 当我们声明1个指针变量,没有为这个指针变量赋初始值.这个指针变量的值是1个垃圾指 指向1块随机的内存空间。
  • OC语言: 指针指向的对象已经被回收掉了.这个指针就叫做野指针.

僵尸对象

对象被释放后所占用的内存在没有被复写(重新分配给其他对象)前称为僵尸对象,这是野指针是可以访问该内存的,因为对象的数据还在,所以程序不会报错。但是该内存一旦重新分配给其他对象就会出现问题

  • 内存回收的本质.
    • 申请1块空间,实际上是向系统申请1块别人不再使用的空间.
    • 释放1块空间,指的是占用的空间不再使用,这个时候系统可以分配给别人去使用.
    • 在这个空间分配给别人之前 数据还是存在的.
      • OC对象释放以后,表示OC对象占用的空间可以分配给别人.
      • 但是再分配给别人之前 这个空间仍然存在 对象的数据仍然存在.
    • 僵尸对象: 1个已经被释放的对象 就叫做僵尸对象.
  • 使用野指针访问僵尸对象.有的时候会出问题报错(EXC_BAD_ACCESS),有的时候不会出问题.
    • 当野指针指向的僵尸对象所占用的空间还没有分配给别人的时候,这个时候其实是可以访问的.因为对象的数据还在.
    • 当野指针指向的对象所占用的空间分配给了别人的时候 这个时候访问就会出问题. 所以,你不要通过1个野指针去访问1个僵尸对象.
    • 虽然可以通过野指针去访问已经被释放的对象,但是我们不允许这么做.
  • 僵尸对象检测.
    • 默认情况下. Xcode不会去检测指针指向的对象是否为1个僵尸对象. 能访问就访问 不能访问就报错.
    • 可以开启Xcode的僵尸对象检测. 打开Xcode顶部导航栏的Product-Scheme-Edit Scheme,在弹出的界面中选中左侧的Run模式,然后勾选右侧Dianostics下的Zombie Objects。
Person *p = [[Person alloc]init];
[p setName:@"aa"];
[p release]; //指针p指向的对象已经被释放,但是该指针还能访问该内存地址
[p setName:@"bb"]; //这种情况下被释放掉的对象就会成为僵尸对象会crash。

开启僵尸对象,这样在 [person release ]之后。person对象在释放时调用的dealloc方法在底层被swizzle了
dealloc方法执行时,代码走的并不是清理资源,回收内存。而是copy了一个NSZombie对象模版,并修改成zombie对象的isa指针。
形成了一个新的僵尸对象类_NSZombie_Person。
所以在[person release]执行之后, 打印的person对象类型为_NSZombie_Person
_NSZombie_Person类中只有一个isa指针,里面没有其他的属性和方法,所以不能响应任何事件,所以在向这个僵尸对象发送消息时,就会报错。

崩溃日志

崩溃日志文件以下三种方式获得:
1、在手机连接电脑时,打开xcode -> window -> Devices and Simulators 选择一台设备 右侧点击“view Device logs” 按钮,左侧列表中 type为crash为崩溃日志
2、崩溃设备手机 “设置”-“隐私”-“分析”/"诊断或使用"-“分析数据” 列表项就是记录的日志,格式为 _ _ ,打开 复制里面的内容
3、最好使用第三方库来记录崩溃日志,我们选择的是:fabric

崩溃日志可以分为非符号化,部分符号化,完全符号化的。

  • 非符号化日志不包含原始方法或函数名称,只能看到地址

Thread 21:
0 libsystem_kernel.dylib 0x00000001aaff70f4 0x1aafdf000 + 98548
1 libsystem_kernel.dylib 0x00000001aaff65a0 0x1aafdf000 + 95648
2 CoreFoundation 0x00000001ab3f6a10 0x1ab34d000 + 694800
3 CoreFoundation 0x00000001ab3f1920 0x1ab34d000 + 674080
4 CoreFoundation 0x00000001ab3f10b0 0x1ab34d000 + 671920
5 Foundation 0x00000001abdbefac 0x1abdb7000 + 32684
6 Aipai_appstore 0x0000000103f0832c 0x10282c000 + 23970604
7 Aipai_appstore 0x0000000103f5090c 0x10282c000 + 24267020
8 Aipai_appstore 0x0000000103f50a6c 0x10282c000 + 24267372
9 Aipai_appstore 0x0000000103f005d4 0x10282c000 + 23938516
10 Aipai_appstore 0x0000000103f503a4 0x10282c000 + 24265636
11 Aipai_appstore 0x0000000103f4dab0 0x10282c000 + 24255152

  • 符号化,即可以看到方法名或函数,部分符号化有些地址被替换为相应的方法

Fatal Exception: NSInvalidArgumentException
0 CoreFoundation 0x21f290518 __exceptionPreprocess
1 libobjc.A.dylib 0x21e46b9f8 objc_exception_throw
2 CoreFoundation 0x21f1ad278 -[NSOrderedSet initWithSet:copyItems:]
3 CoreFoundation 0x21f295d60 forwarding
4 CoreFoundation 0x21f2979fc _CF_forwarding_prep_0
5 Aipai_appstore 0x104f710bc -[LYRemotePushModule handleRemotePushData:launchingWithOptions:] + 73 (LYRemotePushModule.m:73)
6 Aipai_appstore 0x105aa126c -[AppDelegate application:didReceiveRemoteNotification:] + >76 (AppDelegate.m:76)
7 UIKitCore 0x24b81dda0 -[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:]
8 UIKitCore 0x24b0cab34 -[__UICanvasLifecycleMonitor_Compatability activateEventsOnly:withContext:completion:]

非符号化转成符号化过程,DSYM(符号表)先获得:
PS:dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件

  1. xcode -> window -> Organizer


    iOS崩溃分析查找_第1张图片
    截图1.png
  2. 右键-“show in Finder” ,看到xxx.xcarchive右键-“显示包内容”, dSYMs/xxx.app.dSYM

开始符号化Crash:

  1. find /Applications/Xcode.app -name symbolicatecrash -type f
    找到symbolicatecrash并复制大桌面
  2. ./symbolicatecrash 崩溃日志.crash路径 xxx.app.dSYM文件路径 >最终符号化后的文件.crash
    注:
    第一次运行symbolicatecrash可能会报错,错误内容大概是这样
    Error: "DEVELOPER_DIR" is not defined at ./symbolicate
    解决方法:命令行直接运行下面这行命令
    export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer

或者部分符号化
xcrun atos -o xxx(dSYMs/xxx.app.dSYM/Contents/Resources/DWARF/xxx的路径) -arch armv7/armv7s/arm64(选其一) -l 0x10282c000 (起始地址) 0x0000000103f0832c(运行地址)

如果符号化出错或出现太大偏差,有可能崩溃日志和符号表不是对应的,可通过UUID来对比确认:

  • 查看.crash的UUID
    grep --after-context=2 "Binary Images:" xxx.crash
    打印出来的第一行有尖括号<32个小写字符串>,将其转换为以8-4-4-4-12(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)为一组分隔的32个字符串
  • 查看.dSYM的UUID
    xcrun dwarfdump --uuid xxx.dSYM

我们还可以通过UUID反查找对应的DSYM
$ mdfind "com_apple_xcode_dsym_uuids == "

可避免的崩溃:

  1. unrecognized selector sent to instance 0x...(内存地址)
    调用未实现的方法导致崩溃,很多情况是我们在接收后端返回的数据可能不是预定中的。我们可能用NSArray接收到了NSDictionary 然后调用下标访问器;又或者接收数字型时接收到了null,序列化后成了NSNull却调用了integerValue

不保证数据类型的正确,必须先判断类型

NSArray *array = (NSArray *)data;
if([array isKindOfClass:NSArray.class]) {
   array[0];
}

id count = [NSNull null];
if([count isKindOfClass:NSNumber.class]) {
   [count integer];
}
  1. 集合或富文本插入空值问题
    经常使用到NSArray和NSDictionary集合,它们是不允许插入nil值,将导致崩溃
  • 首先数组插入nil导致,如何避免低级错误,一种是插入前判断非nil,但是所有每个地方都需要判断,我们可以通过分类添加 safeAddObject方法或者分类交换掉系统的API保证其正确性;最好方式是写一个基于NSMutableArray的派生类,重写常用方法
  • 字典创建方式可能是这种
    NSDictionary *dict = @{@"name":name}; name是nil的将会崩溃
    建议改用:
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    dict[@"name"] = name; 这样name是nil时不会加到字典里
    [dict setObject:name forKey:@"name"]; 也要避免使用这种方式
  1. 移除未监听的KVO
    通常情况下你会选择在dealloc移除KVO监听:
- (void)dealloc {
    [self removeObserver:self.viewModel forKeyPath:@"progress"];
}

这有可能是潜在的崩溃,除非确保非某条件下采取监听的,否则引发 '...because it is not registered as an observer.' crash.。
建议改进,使用try-catch包裹:

- (void)dealloc {
    @try {
        [self removeObserver:self.viewModel forKeyPath:@"progress"];
    } @catch (NSException *exception) {}
}
  1. 在dealloc使用了self.属性,有可能崩溃, 以下例子是必崩:
    self可能被释放或正在释放,所以在delloc使用变量访问最安全
- (void)dealloc {
    self.tableView;
}

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] init];
        _tableView.dataSource = self;
    }
    return _tableView;
}
  1. 在多线程情况下对可变数组进行forin,以下代码必崩
NSMutableArray *array = [@[@"aa",@"bb",@"cc",@"dd",@"ee"] mutableCopy];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    for (NSString *obj in array) {
        NSLog(@"%@",obj);
    }
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [array addObject:@"ff"];
});

采用EnumerateObjectsUsingBlock

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@",obj);
    }];
});

6、方法有返回值缺return 会报:EXC_BAD_ACCESS

你可能感兴趣的:(iOS崩溃分析查找)