OC中的Crash异常的总结和捕获方法

http://xgfe.github.io/2017/03/23/lrdcq/ios-crash-catch-and-analyze/?utm_source=tuicool&utm_medium=referral


OC Exception

oc层的异常是ios开发中最最最好抓取和分析的异常了。制造一个典型的oc异常简直再简单不过:

     
     
1
2
3
4
5
6
7
     
     
NSString *str = nil;
NSDictionary *dic = @{@"key":str};
//or
NSArray *array= @[@"a",@"b",@"c"];
[array objectAtIndex:5];
//or
NSAssert(false, @"OC Exception");

最暴力的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抓住(虽然苹果并不建议这么使用)。例如:

     
     
1
2
3
4
5
     
     
@try {
NSAssert(false, @"OC Exception");
} @catch (NSException *exception) {
NSLog(@"%@",exception);
}

就可以获取到当前抛出异常并且阻止异常继续往外抛导致程序崩溃。虽然苹果真的不建议这样做。对于程序真的往外抛出并且我们很难catch到的异常,比如界面和第三方库中甩出来的异常,我们也有方式可以截获到。NSException.m这个文件中携带了一个void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);的函数可以注册一个函数来处理未被捕获的异常。虽然无法阻止程序崩溃,但是可以取得异常进行一些准备和后续处理,使用起来这样:

     
     
1
2
3
4
5
6
7
8
9
     
     
void HandleException(NSException *exception) {
NSArray *stackArray = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
NSLog(@"%@", exceptionInfo);
}
NSSetUncaughtExceptionHandler(&HandleException);

往往我们要做的,是把异常信息保存到本地,等到下次启动的时候进行一些后续处理。这些就是crash收集工具所做的事儿。


Mach Exception

从OC异常往底层走,我们看到的是Mach异常。Mach异常是FreeBSD上特有定义的高层异常,当然,现在网络上能收集到的资料都和mac和ios开发有关。相关的源码网络上可以找到这里。看到异常定义的名称我们会感觉到异常的亲切——EXCMASK开头的异常呢。我们一一来总结常见的两个Mach异常吧:

EXC_BAD_ACCESS (Bad Memory Access)

这是最常见并且我们觉得最头疼的,内存访问错误。这种异常分为两种:

  1. 访问对象未初始化(SIGBUS信号)
  2. 访问了什么东西已经被回收掉了(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,比如这个就是这样。解决这些问题,还是得老老实实的分析堆栈猜测和分析了。

其他

其他在实际开发中有可能遇到的并不多,主要是:

  1. EXC_RESOURCE是指的程序到达资源上限,比如cpu占用过高,内存不足之类的。这样的问题也没法解决啦。
  2. EXC_GUARD是一些c层函数访问错误导致的异常,比如fopen文件访问错误之类的都会爆出这个。不过我们好好的oc不用肯定一般也不会使用这些,所以还安好。
  3. 0x00000020,这些是被FreeBSD定义为玄学异常的异常都在里面了,也提供了特殊的code来提供辅助信息。其中其实最常见的code是0x8badf00d,是主线程阻塞时间太长,程序被os杀了。其他的遇到了就是见鬼了!

Unix Signal Exceptions

从Mach异常再往上走追根究底,其实,所以异常发生的本质途径都是Unix的异常信号。

  1. OC异常并不是真正的异常,但是当一个OC异常被抛出到最外层还没被谁捕获,程序会强行发送SIGABRT信号中断程序。
  2. Mach异常没有比较方便的捕获方式,不过由于它本质就是信号,所以这一段讲的东西也能包含处理Mach异常。

产生一个不属于Mach异常的异常信号也是非常非常简单的事儿,比如:

     
     
1
2
     
     
int *i;
free(i);

总之,c层面,runtime或者其他东西控制程序就是通过信号,中断当然也不例外。通过不同的信号,我们也能知道很多不同的东西。在ios开发环境中,信号枚举在sys/signal.h文件中,我们可以看到大量的Unix信号罗列其中,参考wiki可以看到各个信号的详解。当然,我们最终关心的是能否捕获这些异常信号来抓住异常和崩溃。对,方法是有的,这里提供了一个叫void (signal(int, void ()(int)))(int);的方法来注册一个处理函数。

这个方法最后吐出来的是当前的信号,没异常信息堆栈怎么办,还好,从execinfo.h中,我们可以取出当然汇编层程序的堆栈情况。这就好办了,最后处理代码如下:

     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     
     
void SignalExceptionHandler(int signal) {
NSMutableString *mstr = [[NSMutableString alloc] init];
[mstr appendString:@"Stack:\n"];
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i <frames; ++i) {
[mstr appendFormat:@"%s\n", strs[i]];
}
}
void InstallSignalHandler(void) {
signal(SIGHUP, SignalExceptionHandler);
signal(SIGINT, SignalExceptionHandler);
signal(SIGQUIT, SignalExceptionHandler);
signal(SIGABRT, SignalExceptionHandler);
signal(SIGILL, SignalExceptionHandler);
signal(SIGSEGV, SignalExceptionHandler);
signal(SIGFPE, SignalExceptionHandler);
signal(SIGBUS, SignalExceptionHandler);
signal(SIGPIPE, SignalExceptionHandler);
}

要注意的是这里获得的堆栈信息是,当前汇编子程序的offset+指令offset,要么我们需要符号表,要么我们需要反编译一些我们的程序来对应代码了。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值