iOS常见异常及其产生原因

应用程序崩溃是程序开发过程中除了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异常
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 我们在平时开发过程中经常会遇到的异常类型为Objective-C异常和UNIX信号。 1 Objective-C ...
    凤舞玖天阅读 995评论 0 2
  • 没想到都2021年,我还得写篇文章来讲讲 Crash 监听的一些事情。虽然蛮多文章讲 Crash 监听这块,但总是...
    felix9阅读 12,753评论 1 49
  • 异常简介 处理器和系统内核中有设计标识不同事件的状态码,这些状态被编码为不同的位和信号。每次处理器和内核检测到状态...
    QiShare阅读 1,185评论 0 5
  • 异常简介 处理器和系统内核中有设计标识不同事件的状态码,这些状态被编码为不同的位和信号。每次处理器和内核检测到状态...
    沐灵洛阅读 806评论 0 3
  • Crash我们不得不面对的问题,但是好多人在遇到Crash的时候都无从下手,很多的时候都是凭着感觉找问题。今天我做...
    SunshineBrother阅读 8,376评论 0 15