http://xgfe.github.io/2017/03/23/lrdcq/ios-crash-catch-and-analyze/?utm_source=tuicool&utm_medium=referral
OC Exception
oc层的异常是ios开发中最最最好抓取和分析的异常了。制造一个典型的oc异常简直再简单不过:
|
|
最暴力的assert直接抛出来的异常。这些在oc层面由iOS库或者各种第三方库或者oc runtime验证出错误而抛出的异常,就是oc异常了。在debug环境下,oc异常导致的崩溃log中都会输出完整的异常信息,比如:* Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘OC Exception’。包括这个Exception的类名和描述,下面是这个异常的完整堆栈。所以就算xcode的断点停在了main.m里面,我们也可以轻易的找到异常的位置修复问题。
另外,oc异常还有一个非常好用的特性是可以用trycatch抓住(虽然苹果并不建议这么使用)。例如:
|
|
就可以获取到当前抛出异常并且阻止异常继续往外抛导致程序崩溃。虽然苹果真的不建议这样做。对于程序真的往外抛出并且我们很难catch到的异常,比如界面和第三方库中甩出来的异常,我们也有方式可以截获到。NSException.m这个文件中携带了一个void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);的函数可以注册一个函数来处理未被捕获的异常。虽然无法阻止程序崩溃,但是可以取得异常进行一些准备和后续处理,使用起来这样:
|
|
往往我们要做的,是把异常信息保存到本地,等到下次启动的时候进行一些后续处理。这些就是crash收集工具所做的事儿。
Mach Exception
从OC异常往底层走,我们看到的是Mach异常。Mach异常是FreeBSD上特有定义的高层异常,当然,现在网络上能收集到的资料都和mac和ios开发有关。相关的源码网络上可以找到这里。看到异常定义的名称我们会感觉到异常的亲切——EXCMASK开头的异常呢。我们一一来总结常见的两个Mach异常吧:
EXC_BAD_ACCESS (Bad Memory Access)
这是最常见并且我们觉得最头疼的,内存访问错误。这种异常分为两种:
- 访问对象未初始化(SIGBUS信号)
- 访问了什么东西已经被回收掉了(SIGSEGV信号)
当然,事实上到底是怎样的错误比上面描述的复杂神秘得多,这才是这个最难处理的主要原因。
EXC_BAD_ACCESS同时也提供了辅助的异常code来帮助我们判断到底是什么错误,比如KERN_PROTECTION_FAILURE是指的地址无权限访问,KERN_INVALID_ADDRESS是指的地址不可用,异常信息中还会包括具体出错的地址。也许可以获得更多的帮助呢。在debug运行是打开内存管理的Zombie Objects可以获得有效的调试信息。
EXC_BAD_INSTRUCTION (Illegal Instruction)
通常通过SIGILL信号触发的异常,很明显,它是在说运行了一条非法的指令。往往错误是这样子的:
XC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
虽然是这样说,都是编译器编译出来的指令怎么会有非法指令嘛。所以事实上遇到这样的问题往往是运行指令的参数不对,多半是为0即nil了。然后我们又回到了空指针的问题了~。
当然,除了代码中的问题。更多的是ios开发中的玄学问题导致的ios本身异常和bug,比如这个就是这样。解决这些问题,还是得老老实实的分析堆栈猜测和分析了。
其他
其他在实际开发中有可能遇到的并不多,主要是:
- EXC_RESOURCE是指的程序到达资源上限,比如cpu占用过高,内存不足之类的。这样的问题也没法解决啦。
- EXC_GUARD是一些c层函数访问错误导致的异常,比如fopen文件访问错误之类的都会爆出这个。不过我们好好的oc不用肯定一般也不会使用这些,所以还安好。
- 0x00000020,这些是被FreeBSD定义为玄学异常的异常都在里面了,也提供了特殊的code来提供辅助信息。其中其实最常见的code是0x8badf00d,是主线程阻塞时间太长,程序被os杀了。其他的遇到了就是见鬼了!
Unix Signal Exceptions
从Mach异常再往上走追根究底,其实,所以异常发生的本质途径都是Unix的异常信号。
- OC异常并不是真正的异常,但是当一个OC异常被抛出到最外层还没被谁捕获,程序会强行发送SIGABRT信号中断程序。
- Mach异常没有比较方便的捕获方式,不过由于它本质就是信号,所以这一段讲的东西也能包含处理Mach异常。
产生一个不属于Mach异常的异常信号也是非常非常简单的事儿,比如:
|
|
总之,c层面,runtime或者其他东西控制程序就是通过信号,中断当然也不例外。通过不同的信号,我们也能知道很多不同的东西。在ios开发环境中,信号枚举在sys/signal.h文件中,我们可以看到大量的Unix信号罗列其中,参考wiki可以看到各个信号的详解。当然,我们最终关心的是能否捕获这些异常信号来抓住异常和崩溃。对,方法是有的,这里提供了一个叫void (signal(int, void ()(int)))(int);的方法来注册一个处理函数。
这个方法最后吐出来的是当前的信号,没异常信息堆栈怎么办,还好,从execinfo.h中,我们可以取出当然汇编层程序的堆栈情况。这就好办了,最后处理代码如下:
|
|
要注意的是这里获得的堆栈信息是,当前汇编子程序的offset+指令offset,要么我们需要符号表,要么我们需要反编译一些我们的程序来对应代码了。