iOS崩溃问题总结

前言

在iOS开发过程中,总会遇到各种各样的崩溃问题,那么如何可能的降低应用的崩溃率,就成为每位iOS开发人员的必修课。所以,归纳总结iOS崩溃问题就显得尤为重要了。

crash类型

1 OC层面的crash

  • 找不到方法的实现unrecognized selector
  • KVC崩溃
  • KVO崩溃
  • NSInvalidArgumentException: 非法参数异常,传入非法参数导致异常,nil参数比较常见
  • NSRangeException: 下标越界导致的异常
  • NSGenericException: foreach的循环当中修改元素导致的异常

2 Signal层面的crash

除了OC层面的异常捕获之外,还有很多崩溃则需要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。该函数中我们可以输出栈信息,版本信息等其他一切我们所想要的。这个unix标准的signal又是如何产生的呢?主要来源于这两类:

  • 软件异常:软件异常主要来自 kill(),pthread_kill()。iOS 中的 NSException 未捕获,abort都属于这种情况。
  • 硬件异常:硬件的信号始于处理器 trap,是和平台相关的。野指针崩溃大部分是硬件异常。

其中,硬件异常的流程是:硬件异常 -> Mach异常 -> Unix信号。根据《Mac OS X Internals》中说到的bsd相关的处理代码 ux_exception.c ,可以看到 Mach 异常和 Unix 信号存在的对应关系。

Mach 异常.png

2.1 Mach 异常

  1. EXC_BAD_ACCESS: 不能访问的内存
  2. EXC_BAD_INSTRUCTION: 非法或未定义的指令或操作数
  3. EXC_ARITHMETIC: 算术异常(例如除以0)。iOS 默认是不启用的,所以我们一般不会遇到
  4. EXC_EMULATION: 执行打算用于支持仿真的指令
  5. EXC_SOFTWARE:软件生成的异常,我们在 Crash 日志中一般不会看到这个类型,苹果的日志里会是 EXC_CRASH
  6. EXC_BREAKPOINT:跟踪或断点
  7. EXC_SYSCALL: UNIX 系统调用
  8. EXC_MACH_SYSCALL: Mach 系统调用

2.2 UNIX 信号:

  1. SIGSEGV,段错误。访问未分配内存、写入没有写权限的内存等。
  2. SIGBUS,总线错误。比如内存地址对齐、错误的内存类型访问等。
  3. SIGILL,执行了非法指令,一般是可执行文件出现了错误。
  4. SIGFPE ,致命的算术运算。比如数值溢出、NaN数值等。
  5. SIGABRT,调用 abort() 产生,通过 pthread_kill() 发送。
  6. SIGPIPE,管道破裂。通常在进程间通信产生。比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。根据苹果相关文档,可以忽略这个信号。
  7. SIGSYS,系统调用异常。
  8. SIGKILL,此信号表示系统中止进程。崩溃报告会包含代表中止原因的编码。exit(), kill(9) 等函数调用。iOS 系统杀进程,如 watchDog 杀进程。
  9. SIGTRAP,断点指令或者其他trap指令产生。

崩溃原因与解决方案

对于OC层面的崩溃问题,都是代码不够严谨导致,收集到崩溃后找到具体的出错位置,问题自然也就迎刃而解了。但是,对于Signal层面的崩溃,主要是由于野指针和OOM引起,让开发人员头疼不已的,这里就对Signal部分的崩溃问题进一步探讨探讨。

1 野指针

野指针,指向一个已删除的对象或未申请访问受限内存区域的指针。而iOS中的野指针,一般都是对象释放之后指针未置空导致的野指针(iOS里面一般不会出现为初始化对象的常识性错误)。
腾讯Bugly团队对野指针情况做一下分类:

Pasted Graphic 2.png

部分野指针问题并不是按照某个操作步骤就能够复现,那如何才能定位野指针问题的具体位置呢?可以通过Xcode提供的Malloc Scribble 和 Zombie Objects。

1.1 Malloc Scribble

设置开启Malloc Scribble,申请内存 alloc 时在内存上填0xAA,释放内存 dealloc 在内存上填 0x55。如果内存未被初始化就被访问或者释放后被访问,那么Crash必现。

1.2 代码实现Malloc Scribble

开发人员可以通过Xcode使用Malloc Scribble,那么测试同学如果想用Malloc Scribble进行测试验证怎么办呢?总不能要求测试同学安装Xcode编译代码吧。所以,就得想想如何通过代码实现Malloc Scribble功能,即想办法通过代码实现指针指向的对象在释放时在其内存上填入0x55。

1.2.1 OC对象的释放流程

通过runtime源码可以查看到NSObject对象的dealloc处理流程是:

  1. 首先调用_objc_rootDealloc()
  2. 然后调用rootDealloc()
  3. 判断是否可以被释放,判断依据为,是否有以下5中情况:
    (1)NONPointer_ISA
    (2)weakly_reference
    (3)has_assoc
    (4)has_cxx_dtor
    (5)has_sidetable_rc
  4. 如果有以上5中情况中的任意一种,则调用object_dispose()方法;如果没有其中任意一种,表明可以执行释放操作,执行C函数的free()。

1.2.2 实现Malloc Scribble功能

通过上面的dealloc处理流程可以看出,NSObject对象的释放最终需要调用C函数的free()方法,而且C对象的释放也需要调用free()方法。所以,可以利用 fishHook 直接 hook 掉 free() 方法就可以实现Malloc Scribble功能。

void safe_free(void* p) {
    size_tmemSiziee=malloc_size(p);
    memset(p,0x55, memSiziee);
    orig_free(p);
    return;
}

当然,如果代码中不涉及C语音,也可以对dealloc()、rootDealloc() 或者 object_dispose() 进行hook操作。

1.3 Zombie Objects

僵尸对象,可用于检测内存错误(EXC_BAD_ACCESS),给僵尸对象发送消息的话,它仍然可以响应,然后将会发生崩溃,并输出错误日志来显示野指针对象调用的类名和方法。

1.4 Malloc scribble与Zombie Objects对比

Zombie Objects相对Malloc scribble的优势,就是不用考虑不会崩溃的情况,只要野指针指向僵尸对象,再次访问就一定会崩溃。
Zombie Objects相对Malloc scribble的劣势,就是不如 Malloc scribble 方案中覆盖的范围广,可以通过 hook free() 的方式将 C 语音编码部分也包含在内。

2 OOM问题

OOM,Out Of Memory,是指在 iOS 设备上应用因为内存占用过高而被操作系统的Jetsam机制强制终止,即用户所感知到的应用闪退。这类崩溃的日志一般都是Jetsam开头,无法用于定位问题的根源所在。

2.1 Memory Warning

每个UIViewController都有一个didReceivedMemoryWarning的方法。当使用的内存是一点点上涨时,而不是一下子直接把内存撑爆。在达到内存临界点之前,系统会给各个正在运行的应用发出内存警告,告知app去清理自己的内存。而内存警告,并不总是由于自身app导致的。
出现OOM前一定会出现Memory Warning么? 答案是不一定,有可能瞬间申请了大量内存,而恰好此时主线程在忙于其他事情,导致可能没有经历过Memory Warning就发生了OOM。即便触发了多次Memory Warning,也不一定会出现OOM。

2.2 如何确定OOM的阈值

必须要明确的一点是,不同iOS设备的OOM阈值是不同的。

2.2.1 通过Jetsam日志获取阀值

从应用被Jetsam机制终止时生成的日志中,计算rpages * pageSize即可得到OOM的阈值,单位上byte。

2.2.2 通过Memory Warning日志获取阀值

Memory Warning警告的EXC_RESOURCE_EXCEPTION异常也包含了OOM阈值信息。

2.2.3 phys_footprint

通过task_vm_info_data_t的phys_footprint可以获取到一个OOM阀值,然后通过代码循环申请内存,查看应用在OOM发生时的内存消耗情况OOM阀值。

2.2.4 os_proc_available_memory

iOS13系统os/proc.h中os_proc_available_memory方法,可以查看当前可用内存。

2.3 如何判定OOM的发生

facebook和微信的Matrix都是采用的排除法。在Matrix初始化的时候调用checkRebootType方法,来判定是否发生了OOM,具体流程如下:

  1. 如果当前设备正在DEBUG,则直接返回,不继续执行。
  2. 上次打开app是否发生了普通的崩溃,如果不是继续执行。
  3. 上次打开app后,是用户是否主动退出的应用(监听UIApplicationWillTerminateNotification消息),如果不是继续执行。
  4. 上次打开app后,是否调用exit相关的函数(通过atexit函数监控),如果不是继续执行
  5. 上次打开app后,app是否挂起suspend或者执行backgroundFetch,如果此时没有被看门狗杀死,则是一种OOM,Matrix起名叫Suspend OOM,如果不是继续执行
  6. app的uuid是否变化了,如果不是继续执行
  7. 上次打开app后,系统是否升级了,如果不是继续执行
  8. 上次打开app后,设备是否重启了,如果不是继续执行
  9. 上次打开app时,app是否处于后台,如果是,则触发了Background OOM,如果不是继续执行
  10. 上次打开app后,app是否处于前台,是否主线程卡死了,如果没有卡死,则说明触发了Foreground OOM。

参考文章

iOS 野指针处理
如何定位Obj-C野指针随机Crash
深入了解iOS中的OOM

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

推荐阅读更多精彩内容