理解 iOS Crash

作为一名开发人员,如何定位解决线上Crash是每一个必修的课题。那问题来了,Crash 如何产生?作为一名 iOS 开发,我今天主要分享 iOS Crash 相关的内容

Crash 产生

Crash 产生一般分两种情况:

  1. 应用程序自己调用退出函数。eg:调用自杀函数 kill()
  2. 系统把你应用杀死。eg:应用内存占用过高时,系统保护机制会把你的应用干掉。

(各种崩溃场景后续再讨论)
想清楚以上两个场景之后我们我们来看几个元凶。

  • 软件异常
    软件异常主要来源于两个 API 的调用 kill() 、 pthread_kill() , 而 iOS 中我们常常遇到的 NSException 未捕获、 abort() 函数调用等,都属于这种情况。比如我们常看到 Crash 堆栈中有 pthead_kill 方法的调用。
    (此刻我知道你的内心活动,**从来没掉过啊。没错你是没掉,但是系统库会 eg:libc,刺激不刺激,惊喜不惊喜)
  • 硬件异常
    硬件产生的信号始于处理器 trap,处理器 trap 是平台相关的。出现这种情况,计算机会暂停当前程序,及时转入故障处理。比如我们遇到的野指针崩溃大部分是硬件异常。
  • Mach异常
    这里虽然叫异常,但是要和上面的两种分开来看。我们了解到苹果的内核 xnu 的核心是 Mach , 在 Mach 之上建立了 BSD 层。“Mach异常” 是 “Mach异常处理流程” 的简称。不懂没事,后面有讲解

除了以上三个大杀器,还有一个是程序语言异常,这个在我们的Crash日志也是比较常见的,但是与以上三个不同,这种异常往往是我们代码逻辑不合理造成的。没错,说的就是看文章的你,为啥会崩溃,你心里一点数都没有吗????这种稍后我也会给大家总结

接下来我们看一下 Mac OS & iOS 是如何处理这些异常的。以下内容来自“深入解析 Mac OS X & iOS 操作系统”
软件异常处理:

软件信号处理流程

硬件异常处理:
硬件信号处理流程

通过上面两张图,我们可以很清楚的看到,无论是软件异常,还是硬件异常,最终都会被转换为信号,然后通过act_set_astbsd()发送给我们的应用进程,唤醒其中的某个线程响应指定操作(记住这句话,我们后续有大用)。此时的信号是 UNIX 信号,如 SIGBUS SIGSEGV SIGABRT SIGKILL 等。此时大家可以去翻翻后台的Crash日志,你会发现好多这中以SIG开头的Crash。具体信号的含义大家可以自行 Google,或者直接点击文章参考参考查看。

UNIX信号抛出简单流程大致如下:

Unix 信号产生.jpeg

附 “Mach异常” 与 “UNIX信号” 的转换关系代码,来自 xnu 中的 bsd/uxkern/ux_exception.c :

switch(exception) {
case EXC_BAD_ACCESS:
    if (code == KERN_INVALID_ADDRESS)
        *ux_signal = SIGSEGV;
    else
        *ux_signal = SIGBUS;
    break;

case EXC_BAD_INSTRUCTION:
    *ux_signal = SIGILL;
    break;

case EXC_ARITHMETIC:
    *ux_signal = SIGFPE;
    break;

case EXC_EMULATION:
    *ux_signal = SIGEMT;
    break;

case EXC_SOFTWARE:
    switch (code) {

    case EXC_UNIX_BAD_SYSCALL:
    *ux_signal = SIGSYS;
    break;
    case EXC_UNIX_BAD_PIPE:
    *ux_signal = SIGPIPE;
    break;
    case EXC_UNIX_ABORT:
    *ux_signal = SIGABRT;
    break;
    case EXC_SOFT_SIGNAL:
    *ux_signal = SIGKILL;
    break;
    }
    break;

case EXC_BREAKPOINT:
    *ux_signal = SIGTRAP;
    break;
}

看了以上内容,我想你的内心一定是

你不要再说了

老实说以上理解以上内容确实需要你有相当的计算机基础功力才行,为了照顾你的心情,接下来咱们聊点你能看懂的。
除了上面咱们说道的,还有一种程序语言异常,这种异常通常是程序语言自己封装好的。产生的原因通常是因为程序员编写逻辑错误造成的,没错,就是你自己造的孽。作为iOS程序员,我们主要是关注的是 Objec-C 和 Swift。
Object-C 的异常的异常主要是NSException对象封装的,比较多,咱们这里看几个比较常见的,文末有参考中有链接大家可以看到所有的异常类型。
先来个王炸,这个你肯定见过:

  1. NSInvalidArgumentException
    传递非法参数给一个方法时抛出的异常。
    常见场景:

    • NSNutableDictionaryr操作key或value的函数,如setObject:forKey:、removeObjectForKey等等。
    • NSMutableArray操作value的函数,如addObject:、 insertObject:atIndex:等等。
    • NSString操作函数,如initWithString:、initWithFormat:、stringWithString:等等。

    这里要住意多线程操作,这个坑,深到一半人爬不出来,比如我前面的小朋友,直接被搞离职了

  2. NSRangeException:
    尝试访问某些数据范围之外时抛出的异常。
    常见场景:

    • NSArray包含索引的操作,如insertObject:atIndex:、objectAtIndex:等等。
    • NSString包含索引的操作,如characterAtIndex:、getCharacters:range:等等。
  3. NSFileHandleOperationException
    如果尝试确定文件句柄类型失败或尝试读取文件或通道失败,则会抛出此异常。
    常见场景:

    • 空间不足:会提示No space left on devie。
    • 没有读写权限:会提示Bad file descriptor。
    • 读文件失败。

    在操作文件时,要验证文件句柄的有效性,对文件大小进行校验,对存储空间进行判断。

  4. KVO引起的异常:
    常见场景:

    • 多次移除KVO
      抛出NSRangeException异常
    • 添加或移除时keyPath参数为nil
    • 没有实现observeValueForKeyPath方法(非必显, iOS14)

    我在当前 iOS 15的系统测试过,无论是没有移除观察者还是VC推出时没有一处观察者都没出现崩溃。多次移除相同Path时和path名字为nil会出现Crash

  5. iOS Crash之NSMallocException
    常见场景:

    • 分配的空间过大
    • 图像占用空间过大
    • OOM问题。这个主要是程序死循环造成的
  6. NSGenericException
    常见场景:

    • 可变对象遍历过程中发生了改变。

以上大概就是我们开发和线上应用常见一些异常。至于swift,应为NSexception是Cocoa框架的,swift也绕不过,所以上面的异常,swift也是都有的,当然由于语言上的设计优势,swift出现的可能行降低一些。swift 该只有 try! 解析空。其他欢迎大家补充。
毕竟是初始 iOS Crash,就给大家分析到这里吧,下一篇我们看一下crash收集和符号化。

参考文档:
处理器陷阱 。
UNIX 信号
iOS Crash 分析策略
野指针
理解异常类型
Object-C Exception
iOS内功篇:浅谈Crash

你可能感兴趣的:(理解 iOS Crash)