美文网首页
iOS常见异常及其产生原因

iOS常见异常及其产生原因

作者: zzzworm | 来源:发表于2022-04-19 22:40 被阅读0次

    应用程序崩溃是程序开发过程中除了bug外一直伴随我们的最大的幽灵,时不时给我们致命一击。

    应用程序崩溃的原因有很多,一般应用程序在崩溃时会触发相应的异常退出信号。iOS应用程序崩溃信号可以分为操作系统异常信号,iOS系统限制以及语言运行时错误。

    操作系统常见异常

    由于iOS源自MacOS,而MacOS由基于Unix并被Apple添加了很多自定义组件。在iOS系统中会出现两种异常

    Mach异常与UNIX异常

    • Mach异常

    Mach是一个XNU的微内核核心,Mach异常是指最底层的内核级异常。每个thread,task,host都有一个异常端口数组,Mach的部分API暴露给了用户态,用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口,来捕获Mach异常,抓取Crash事件。

    所有Mach异常都在host层被ux_exception转换为相应的Unix信号,并通过threadsignal将信号投递到出错的线程。iOS中的 POSIX API就是通过Mach之上的 BSD层实现的。

    Mach微内核中有几个基础概念:

    • Tasks,拥有一组系统资源的对象,允许"thread"在其中执行。

    • Threads,执行的基本单位,拥有task的上下文,并共享其资源。

    • Ports,task之间通讯的一组受保护的消息队列;task可对任何port发送/接收数据。

    • Host, Mach 最基础的对象是“主机(host)”,也就是表示机器本身的对象。主机对象是一个简单的数据结构。主机只不过是一组“特殊端口”的集合(用于向主机发送各种消息),以及一组异常处理程序的集合。主机定义了一个锁组用于保护异常处理的并发访问。主机的数据结构主要有三个基本功能:

    1. 提供机器信息:Mach 提供了一组异常丰富的API调用用于查询机器信息,所有这些调用都要求获得主机端口才能工作。

    2. 提供子系统的访问:通过主机抽象,应用程序可以请求访问子系统使用的任何“特殊”端口。此外,还可以获得所有其他机器抽象(例如:processor 和 processor_set)的访问权。

    3. 提供默认的异常处理:异常从线程基本提升到进程(任务)基本,如果没有被处理的话。则进一步提升到主机级别做通用的处理。

    • UNIX信号

    这就是我们常说的信号了,实际上由于Mach异常会被转换为Unix信号,通常我们看到的crash都是触发为unix异常信号。常见的Unix信号如下:

    信号 说明
    SIGHUP 用户终端退出进程时,终端将接收到SIGHUP信号。这个信号的默认操作为终止进程。iOS中通常不出现该信号
    SIGTERM 终止请求,发送到程序(软kill),iOS中通常不出现该信号
    SIGSEGV 无效的内存访问(分段故障)
    SIGINT 外部中断,通常由用户发起,程序终止信号(interrupt),在用户键入INTR字符(通常Ctrl-C)时发出,用于通知前台进程组终止进程。iOS中通常不出现该信号
    SIGILL 无效的程序映像,如无效指令,通常是kill命令调用产生(强制kill),iOS下杀死app会走应用程序周期,一般不出现该信号
    SIGABRT 异常终止条件,例如由中止abort函数调用
    SIGFPE 错误的算术运算,如除以零
    SIGBUS 试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。
    SIGTRAP 常来说SIGTRAP是由断点指令或其它trap指令产生. 由debugger使用。如果没有附加调试器,则该过程将终止并生成崩溃报告。 较低级的库(例如,libdispatch)会在遇到致命错误时捕获进程。

    SIGABRT

    在C/C++的库中有较多触发SIGABRT的场景

    • 多次free指针
    #include "stdlib.h"
    #include "string.h"
    #include "stdio.h
    int main()
    {
    void *pc = malloc(1024);
    free(pc);
    //free(pc); //打开注释会导致错误
    printf("free ok!\n");
    return 0;
    }
    
    • C/C++中使用abort函数
    #include "stdlib.h"  
    
    int main()  
    {  
        printf("before run abort!\n");  
        abort();  
        printf("after run abort!\n");  
    
        return 0;  
    }  
    

    assert函数内部也是会调用abort。在使用一些系统库容易出现SIGABRT异常,在一些被禁止调用的函数被调用时也会出现该异常错误。

    • 在C/C++中对容器类的越界访问也会产生该信号Crash

    (备注:OC中容器访问越界会触发SIGSEGV信号,或NSRangeException异常)

    int i = 0;
    int arr[3] = {0}; // 包含三个 int 元素的数组,并且,每个元素的值初始化为 0
    for (; i < 4; i++) { // i < 4, 这个地方会造成数组越界
    arr[i] = i;
    cout << "arr[" << i << "] = " << arr[i] << endl;
    }
    

    SIGSEGV

    SIGSEGV错误是我们应用最常见的错误,SIGSEGV在很多时候是由于指针越界引起的,但并不是所有的指针越界都会引发SIGSEGV。一个越界的指针,如果不解引用它,是不会引起SIGSEGV的。而即使解引用了一个越界的指针,也不一定会引起SIGSEGV。

    • 非法的内存访问

    • 错误的访问类型

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        char* s = "hello world";
        s[1] = 'H';
    }
    

    这是最常见的一个例子。此例中,”hello world”作为一个常量字符串,在编译后会被放在.rodata节(GCC),最后链接生成目标程序时.rodata节会被合并到text segment与代码段放在一起,故其所处内存区域是只读的。

    这就是错误的访问类型引起的SIGSEGV。

    • 访问非进程空间内存

    内存地址不在进程的地址空间之内

    • 以空指针为代表的程序起始空间

    • 未申请的堆空间

    • 段与段之间的空洞

    内存地址空间合法,但是权限不满足

    • 对代码段进行写操作:野指针,向代码段进行写操作

    • 对数据段进行执行操作:rip错误,把数据段的数据当作指令来执行

    • 访问不存在内存:invalid memory access (segmentation fault),例如

    char *p = NULL;
      *p = 1;
    
    • 多线程访问同一内存地址

    • 函数跳转到了一个非法的地址上执行

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    void foo () {
        char c;
        memset (&c, 0x55, 128);
    }
    int main () {
        foo();
    }
    

    通过栈溢出,我们将函数foo的返回地址覆盖成了0x55555555,函数跳转到了一个非法地址执行,最终引发SIGSEGV。

    SIGBUS

    一般是由于地址未对齐导致的,例如内存地址对齐出错,或者试图执行没有权限的代码地址。子码有以下几种情况:

    • KERN_MEMORY_ERROR:试图访问当时无法返回数据的内存,如内存映射文件不可用。

    • EXC_ARM_DA_ALIGN:试图访问没有正确对齐的内存。此异常代码很少见,因为64位ARM CPU可处理未对齐的数据。但是,如果内存地址既未对齐又位于未映射的内存区域中,则可能会看到此异常子类型。

    SIGILL

    常见EXC_BAD_INSTRUCTION非法指令,通常与特定非法或未定义指令或操作数相关。

    EXC_BREAKPOINT(SIGTRAP)

    在ARM处理器上,断点异常类型指示跟踪陷阱中断进程。 跟踪陷阱使附上的调试器有机会在执行特定位置时中断该进程。

    断点异常类型指示跟踪陷阱中断了该过程。 跟踪陷阱使附加的调试器有机会在执行的特定点中断该进程。 在ARM处理器上,它显示为EXC_BREAKPOINT(SIGTRAP)。 在x86_64处理器上,它显示为EXC_BAD_INSTRUCTION(SIGILL)。

    Swift运行时将跟踪陷阱用于特定类型的不可恢复的错误-有关这些错误的信息,请参见Addressing Crashes from Swift Runtime Errors。 一些较低级别的库(例如Dispatch)会在遇到不可恢复的错误时使用此异常来捕获进程,并在崩溃报告的“其他诊断信息”部分中记录有关该错误的其他信息。 有关这些消息的信息,请参阅Diagnostic Messages

    当使用swift时,以下几种情况也会抛出此异常:

    • 一个非可选类型值为nil;

    • 强制类型转换失败;

    如果要在自己的代码中使用相同的技术来解决不可恢复的错误,请调用__builtin_trap()函数。 这使系统可以生成带有线程回溯的崩溃报告,以显示你如何达到不可恢复的错误。

    • ILL_ILLTRP:ILL_ILLTRP at 0xxxxx通常是二进制出错,典型比如app升级前后二进制缓存出错。

    SIGFPE

    崩溃的线程执行了无效的算术运算。

    包括除以0或取余0的情况,及发生数据溢出导致的除以0或取余0的情况;包括浮点错误。

    The following values can be placed in si_code for a SIGFPE signal:
    FPE_INTDIV Integer divide by zero.
    FPE_INTOVF Integer overflow.
    FPE_FLTDIV Floating-point divide by zero.
    FPE_FLTOVF Floating-point overflow.
    FPE_FLTUND Floating-point underflow.
    FPE_FLTRES Floating-point inexact result.
    FPE_FLTINV Floating-point invalid operation.
    FPE_FLTSUB Subscript out of range.

    SIGKILL

    此信号表示系统中止进程,通常是调用函数exit()或kill(9)产生。iOS中常见为受到系统资源限制而导致退出

    崩溃报告会包含代表中止原因的编码:

    • 0x8badf00d:eate bad food,系统监视程序由中止无响应应用。注意在生命周期的不同阶段,触发看门狗机制的超时时间是不一样的。

    • 0xc00010ff:cool off,系统由于过热保护中止应用,通常与特定的手机和环境有关。

    • 0xdead10cc:dead lock,系统中止在挂起期间一直保持文件锁或SQLite数据库锁的应用。

    • 0xbaadca11:bad all,系统由于应用在响应PushKit通知时无法报告CallKit呼叫而中止它。

    • 0xbad22222:系统由于VoIP应用恢复太频繁而中止它。

    • 0xc51bad01:OS终止了该应用程序,因为它在执行后台任务时占用了过多的CPU时间。

    • 0xc51bad02:OS终止了该应用程序,因为它未能在分配的时间内完成后台任务。

    • 0xc51bad03:OS终止了该应用程序,因为它未能在分配的时间内完成后台任务,但是系统总体上非常繁忙,以至于该应用程序可能没有收到太多的CPU时间来执行后台任务。

    • 0xbada5e47:系统可能由于你启动了过多了后台任务而中止你的应用。

    iOS系统异常和限制

    由于iOS通常运行在移动设备上,为了保证移动设备的使用体验,iOS操作系统通常会设定各种限制

    • UI线程无响应

    通常来自主线程阻塞,操作系统为保障应用程序的流畅会对主线程进行watchdog监听,如果发现主线程在较长时间都没有处理UI刷新或者事件响应,会触发watchdog的kill机制。引起主线程无响应的情况有多种可能,常见场景有:

    • 主线程死锁

      • 使用dispath_sync不规范导致死锁

      • 在主线程中执行过长任务或者进行死循环

      • 主线程等待信号时间过长。

    • 启动时间过长

    启动时间过长通常也是由于主线程无响应导致的,例子:

    Exception Type: EXC_CRASH (SIGKILL)
    Exception Codes: 0x0000000000000000, 0x0000000000000000
    Exception Note: EXC_CORPSE_NOTIFY
    Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
    Termination Description: SPRINGBOARD, scene-update watchdog transgression: application<com.soulapp.cn>:1952 
    exhausted real (wall clock) time allowance of 10.00 seconds | ProcessVisibility: Foreground | ProcessState: Running | WatchdogEvent: scene-update 
    | WatchdogVisibility: Foreground | WatchdogCPUStatistics: 
    ( | "Elapsed total CPU time (seconds): 33.200 (user 33.200, system 0.000), 80% CPU", | "Elapsed application CPU time (seconds): 3.620, 9% CPU" | )
    Triggered by Thread: 0
    
    • 触发系统资源限制

    EXC_GUARD

    受保护资源的非法访问,一般是由违背受保护资源防护触发,例如非法访问某些文件描述符。

    EXC_RESOURCE

    资源受限,应用由于达到资源的消耗限制而被退出,例如:

    • CPU使用过高

    • 内存使用过高

    • weakups过高

    OC语言异常

    OC运行时也有一些自己特点的错误,通常表现为异常退出

    异常 说明 触发原因
    NSGenericException 通用异常
    NSRangeException 访问越界异常 访问数组,容器类越界
    NSInvalidArgumentException 非法参数异常 是 Objective -C 代码最常出现的错误,所以平时在写代码的时候,需要多加注意,加强对参数的检查,避免传入非法参数导致异常,其中尤以nil参数为甚。
    NSInternalInconsistencyException 内部不一致异常 通常出现为对非mutable容器当成mutable容器使用,也见于把Xib当成Storyboard使用
    NSMallocException 分配内存异常 通常是内存不足引起的,如一次性申请过大内存空间,图片占有内存过大。iOS系统有时候也会限制在短时间内频繁申请和释放内存行为
    NSObjectInaccessibleException 对象不可访问异常 常见于使用CoreData中对象被删除或者不可用,也出现于远程过程调用中对象不可访问
    NSObjectNotAvailableException 对象不存在异常 访问一些被限制的对象/方法,例如:在SwiftUI APP中使用alertView,远程过程调用对象不存在,使用WebKitView可能会遇到
    NSDestinationInvalidException 目的不合法异常 常见于发起远程连接时地址不合法
    NSPortTimeoutException 通信超时异常 在建立网络连接通信超时
    NSInvalidSendPortException 发送端口已经失效 NSConnection断开/失效时发送方收到异常
    NSInvalidReceivePortException 接收端口失效异常 NSConnection断开/失效时接收方收到异常
    NSPortSendException 端口发送异常 NSConnection连接使用时可能遇到
    NSPortReceiveException 端口接收异常 NSConnection连接使用时可能遇到
    NSOldStyleException 老式异常 已经不在使用
    NSInconsistentArchiveException 初始化或者编码异常 通常出现在文档解析,存储解析操作

    此外还有一些自定义的异常,比如:

    FileNotFoundException 文件读写时找不到异常

    参考:

    1. https://zhuanlan.zhihu.com/p/269371735 iOS异常

    相关文章

      网友评论

          本文标题:iOS常见异常及其产生原因

          本文链接:https://www.haomeiwen.com/subject/ltehtktx.html