iOS -- Crash处理方法总结

来源:互联网 发布:淘宝联盟文案生成器 编辑:程序博客网 时间:2024/06/08 12:06

在调试程序的时候,总是碰到crash的bug,而且一追踪就是一些汇编的代码,让人特别疑惑。下面总结一些crash调试几种的方法。

基本上有错误分为以下几种类型:

signal(SIGABRT, MySignalHandler);

signal(SIGILL, MySignalHandler);

signal(SIGSEGV, MySignalHandler);

signal(SIGFPE, MySignalHandler);

signal(SIGBUS, MySignalHandler);

signal(SIGPIPE, MySignalHandler);

SIGABRT和EXC_BAD_ACCESS较为特殊,算是比较好跟进。

SIGABRT是系统报错,在memery warning之后,系统会把程序强制退出,报的就是这个错误。

EXC_BAD_ACCESS 大多数时候是内存提前释放而引起的问题,或者访问的方法不存在引起的。

SIGSEGV 使用全局断点解决。

追踪程序的调用stacktrace的方法,跟踪问题:

一般出错了之后出现的常见界面,再熟悉不过了。。

切换到breakpoint界面,拖动底端的slider按钮,显示调用堆栈

虽然调用堆栈已经出来了,但是都是一堆的汇编代码,无法给予明确的信息。。


设置全局异常断点(SIGSEGV 

有时候我们看不懂堆栈里的汇编代码,最后能够定位到出错的地方。那就用到下面的内容了:添加Symbol breakpoint  Exception breakpoint

一、Exception breakpoint全局断点。

在程序抛出异常时候,往往需要定位到异常,添加Add Exception BreakPoint,这个就是捕获所有的exception, 貌似stackoverflow上说,bad_access那种错误无法捕获的,这个用于捕获那些SIGSEGV 的错误。

二、Symbolic breakpoint

系统抛出异常处设置断点

有时候我们的程序不知道跑到哪个地方就 crash 了,而 crash 又很难重现。保守的做法是在系统抛出异常之前设置断点,具体来说是在 objc_exception_throw处设置断点。 这样在 Debug 模式下,如果程序即将抛出异常,就能在抛出异常处中断了。

添加完成之后在 Symbol 一栏输入:objc_exception_throw,然后点击 done,完成。

添加完成只两个断点之后,程序中很多异常也可以捕获了,直接定位到出问题的位置。


开启僵尸模式EXC_BAD_ACCESS

1、为什么会使用NSZombieEnabled?

应用调试可能会收到类似 Thread 1: Program received signal:"EXC_BAD_ACCESS 这样的错误提示信息,这样的信息通常是内存操作错误引起,例如你对已释放的对象发送消息时就会出现,再如release 的对象再release,release 那些autorelease 的对象等。可以这么说,90%的错误来源在于对一个已经释放的对象进行release操作。

当设置NSZombieEnabled环境变量后,一个对象销毁时会被转化为_NSZombie,设置NSZombieEnabled后,当你向一个已经释放的对象发送消息,这个对象就不会向之前那样Crash或者产生一个难以理解的行为,而是放出一个错误消息,然后以一种可预测的可以产生debug断点的方式消失, 因此我们就可以找到具体或者大概是哪个对象被错误的释放了。 


2、如何设置为NSZombieEnabled模式?

Xcode4 下设置 NSZombieEnabled 的方法:

方法一:Product -> Edit Scheme-> Arguments, 然后将点击”加号”, 将 NSZombieEnabled 参数加到Environment Variables 窗口中, 后面的数值写上 ”YES”.

方法二:Xcode4 菜单 Product -> EditScheme -> Diagnostics 设置窗口中直接勾上Enable ZombieObjects 即可

Xcode 可用 cmd+shift+< 进到这个窗口。

NSZombieEnabled与EXC_BAD_ACCESS调试

3、一个简单实例

static NSMutableArray*array;  
- (void)viewWillAppear:(BOOL)animated{     
   [array addObject:@"Hello"];//使用释放掉的数组  
}  
-(void)viewDidLoad  
{  
   [super viewDidLoad];  
   array= [[NSMutableArray alloc]initWithCapacity:5];  
   [array release];  
   [array addObject:@"Hello"];//之所以不会crash,是在于事件周期未完,内存回收机制还没有执行,没有真正的回收掉array的对象内存。  
   NSLog(@"%@",[array objectAtIndex:0]);  

}  

上例的运行结果:在未开启NSZombieEnabled的状态下,会输出Hello,程序在调用之viewWillAppear的时候crash,开启NSZombieEnabled的情况下,不会输出Hello,在调用Hello的情况下应用即会停止运行,控制台报错:

*** -[__NSArrayM respondsToSelector:]: message sent to deallocated instance 0x6aa0200


4、一个复杂一点的实例

#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

        NSString* s = [[NSString alloc]initWithString:@”This is a test string”];

        s = [s substringFromIndex:[s rangeOfString:@"a"].location];//内存泄露

        [s release];//错误释放

[pool drain];//EXC_BAD_ACCESS

return 0;

}
    这个例子当然狠容易的看出问题所在,如果这段代码包含在一个很大的逻辑中,确实容易被忽略。Objective-C 这段代码有三个致命问题:1、内存泄露;2、错误释放;3、造成 EXC_BAD_ACCESS 错误。

    (1), NSString* s = [[NSString alloc]initWithString:@”This is a test string”]; 创建了一个 NSString Object,随后的 s = [s substringFromIndex:[s rangeOfString:@"a"].location]; 执行后,导致创建的对象引用消失,直接造成内存泄露。

    (2),错误释放。[s release]; 这个问题,原因之一是一个逻辑错误,以为 s 还是我们最初创建的那个 NSString 对象。第二是因为从 substringFromIndex:(NSUInteger i) 这个方法返回的 NSString 对象,并不需要我们来释放,它其实是一个被 substringFromIndex 方法标记为 autorelease 的对象。如果我们强行的释放了它,那么会造成 EXC_BAD_ACCESS 问题。

    (3), EXC_BAD_ACCESS。由于 s 指向的 NSString 对象被标记为 autorelease, 则在 NSAutoreleasePool 中已有记录。但是由于我们在前面错误的释放了该对象,则当 [pool drain] 的时候,NSAutoreleasePool 又一次的对它记录的 s 对象调用了 release 方法,但这个时候 s 已经被释放不复存在,则直接导致了 EXC_BAD_ACCESS问题。

5、有什么需要注意的?

NSZombieEnabled只能在调试的时候使用,千万不要忘记在产品发布的时候去掉,因为NSZombieEnabled不会真正去释放dealloc对象的内存,一直开启后果可想而知


Xcode内置调试命令:GDB

1.1 po 命令:为 print object 的缩写,显示对象的文本描述
(lldb) po [$eax class]:输出异常对象的地址
(lldb) po [$eax name]:输出这个异常的名字
(lldb) po [$eax reason]:这个将会输出错误消息:
(lldb) “po $eax”:对这个对象调用“description”方法和打印出来

“$eax”是cup的一个寄存器。在一个异常的情况下,这个寄存器将会包含一个异常对象的指针。注意:$eax只会在模拟器里面工作,假如你在设备上调试,你将需要使用”$r0″寄存器

1.2 print 命令:有点类似于格式化输出,可以输出对象的不同信息
  比如:print (char*)[[dic description] cString]、(lldb) print (int)[label retainCount]
1.3 info 命令:我们可以查看内存地址所在信息

1.4 info line *内存地址:可以获取内存地址所在的代码行相关信息

1.5 show 命令:显示 GDB 相关的信息。如:show version 显示GDB版本信息

1.6 bt: 显示当前进程的函数调用栈的情况;"up num":查看调用的详细信息;down:返回栈列表;l:显示详细代码信息;p:输出数值。


打印对象生命周期日志 

NSLog

- (id)init

{self = [super init];

if (self){NSLog(@"%@: %@", NSStringFromSelector(_cmd), self);}

return self;

}

- (void)dealloc

{NSLog(@"%@: %@", NSStringFromSelector(_cmd), self);

}

动态特性

-(BOOL)respondsToSelector:(SEL)aSelector

{

if ([super respondsToSelector:aSelector] ){

NSLog(@"%@", NSStringFromSelector(aSelector));

        return YES;

    }

   else {

       return NO;

   }

}



OC的异常处理机制Exception

Object-C语言的异常处理符号和C++、JAVA相似。再加上使用NSException,NSError或者自定义的类,你可以在你的应用程序里添加强大的错误处理机制。
异常处理机制是由这个四个关键字支持的:@try,@catch,@thorw,@finally。当代码有可能出现异常时,我们把他放到@try语句块中。@catch()块包含了处理@try块里的抛出的异常的逻辑。无论异常是否发生,@finally块里面的语句都会执行。如果直接使用@throw块来抛出异常,这个异常本质上是一个OC的对象。咱们可以使用NSException对象,但是不局限于他们。

注意:要在一个应用里同时使用这些特性,应用必须在MAC OS X v10.3或者以上版本中。因为早期版本的运行环境不支持异常处理和线程同步。

举个简单的例子:

一公分为两部分一部分是注册异常,一部分是在需要的情况下抛出异常

  1. Cup *cup = [[Cup alloc] init];

  2. @try { [cup fill];

  3. } @catch (NSException *exception) {

  4. NSLog(@”main: Caught %@: %@”, [exception name], [exception reason]);

  5. } @finally {

  6. [cup release];

  7. }

抛出异常

为了掷出一个异常,我们必须实例化一个对象,当然这个异常对象要包含相关的信息,比如异常的名字和为什么要掷出他。
  1. -(void)fill{
  2. if(/*抛出异常的情况*/){
  3. NSException *exception = [NSException exceptionWithName:@"HotTeaException" reason:@"The tea is too hot" userInfo:nil];
  4. @throw exception;
  5. }else{
  6. //正常的代码逻辑}
  7. }
和@catch()块相反,你可以使用@throw再次掷出一个被抓到的异常,不用加参数哦亲。这个能使你的代码更可读。(我怎么没看出来)
你也可以继承NSEception,来实现特殊类型的异常,比如文件系统的异常或者交互异常。
注意:不用仅限于掷出NSException对象。你可以掷出任何一个OC对象作为一场对象。NSException类提供的方法可以帮助你处理异常,但是如果你愿意你可以实现你自己的。

高端处理异常(尼玛这标题怎么翻啊,来去就是这几个单词,老外哥哥你敢不敢换几个啊)
为了捕获一个使用@try块掷出的异常,你可以在@try后面使用多个@catch()块,可以用多个哦亲。@catch块可以由最特殊的到最普遍的排序(我觉得翻译成重要不重要的比普遍好多了,但是尊重GOOGLE,你们懂的)。这样你就可以度身订造你的异常处理过程。


如果这还不够透彻,那么我们就干脆我们自定义两个异常类,看看异常异常处理的使用。

1、新建SomethingException,SomeOverException这两个类,都继承与NSException类。

SomethingException.h
  1. #import <Foundation/Foundation.h>  
  2.   
  3. @interface SomethingException NSException  
  4.   
  5. @end  
SomethingException.m
  1. #import "SomethingException.h"  
  2.   
  3. @implementation SomethingException  
  4.   
  5. @end  
SomeOverException.h
  1. #import <Foundation/Foundation.h>  
  2.   
  3. @interface SomeOverException NSException  
  4.   
  5. @end  
SomeOverException.m
  1. #import "SomeOverException.h"  
  2.   
  3. @implementation SomeOverException  
  4.   
  5. @end  

2、新建Box类,在某些条件下产生异常。

  1. #import <Foundation/Foundation.h>  
  2.   
  3. @interface Box NSObject  
  4.  
  5.     NSInteger number;  
  6.  
  7. -(voidsetNumber: (NSInteger) num;  
  8. -(voidpushIn;  
  9. -(voidpullOut;  
  10. -(voidprintNumber;  
  11. @end  

  1. @implementation Box  
  2. -(id) init  
  3.     self [super init];  
  4.       
  5.     if self  
  6.         [self setNumber: 0];  
  7.      
  8.       
  9.     return self;  
  10.  
  11.   
  12. -(voidsetNumber: (NSInteger) num  
  13.     number num;  
  14.       
  15.     if number 10  
  16.         NSException *e [SomeOverException  
  17.                           exceptionWithName: @"BoxOverflowException"  
  18.                           reason: @"The level is above 100"  
  19.                           userInfo: nil];  
  20.         @throw e;  
  21.     else if number >=  
  22.         // throw warning  
  23.         NSException *e [SomethingException  
  24.                           exceptionWithName: @"BoxWarningException"  
  25.                           reason: @"The level is above or at 60"  
  26.                           userInfo: nil];  
  27.         @throw e;  
  28.     else if number  
  29.         // throw exception  
  30.         NSException *e [NSException  
  31.                           exceptionWithName: @"BoxUnderflowException"  
  32.                           reason: @"The level is below 0"  
  33.                           userInfo: nil];  
  34.         @throw e;  
  35.      
  36.  
  37.   
  38. -(voidpushIn  
  39.     [self setNumber: number 1];  
  40.  
  41.   
  42. -(voidpullOut  
  43.     [self setNumber: number 1];  
  44.  
  45.   
  46. -(voidprintNumber  
  47.     NSLog(@"Box number is: %d"number);  
  48.  
  49. @end  
这个类的作用是,初始化Box时,number数字是0,可以用pushIn 方法往Box里推入数字,每调用一次,number加1.当number数字大于等于6时产生SomethingException异常,告诉你数字达到或超过6了,超过10时产生SomeOverException异常,小于1时产生普通的NSException异常。

这里写 [SomeOverException  exceptionWithName:@"BoxOverflowException"  reason:@"The level is above 100"异常的名称和理由,在捕获时可以获取。

3、使用Box,在适当添加下捕获Box类的异常

3.1、在没超过6时,没有异常

  1. (void)viewDidLoad  
  2.  
  3.     [super viewDidLoad];      
  4.     NSAutoreleasePool *pool [[NSAutoreleasePool alloc] init];  
  5.     Box *box [[Box alloc]init];  
  6.     for (int 0; 5; i++)  
  7.         [box pushIn];  
  8.         [box printNumber];  
  9.      
  10.  
打印结果:

Box number is: 1

Box number is: 2

Box number is: 3

Box number is: 4

Box number is: 5

3.2 超过6,产生异常

  1. for (int 0; 11; i++)  
  2.             [box pushIn];  
  3.             [box printNumber];  
  4.     }  
  1. 2012-07-04 09:12:05.889 ObjectiveCTest[648:f803] Box number is:  
  2. 2012-07-04 09:12:05.890 ObjectiveCTest[648:f803] Box number is:  
  3. 2012-07-04 09:12:05.890 ObjectiveCTest[648:f803] Box number is:  
  4. 2012-07-04 09:12:05.890 ObjectiveCTest[648:f803] Box number is:  
  5. 2012-07-04 09:12:05.891 ObjectiveCTest[648:f803] Box number is:  
  6. 2012-07-04 09:12:05.891 ObjectiveCTest[648:f803] *** Terminating app due to uncaught exception 'BoxWarningException'reason: 'The number is above or at 60'  
这是时,程序抛出异常崩溃了。那怎么使程序不崩溃呢,做异常处理。

3.3、加上异常处理

  1. for (int 0; 11; i++)  
  2.         @try  
  3.             [box pushIn];  
  4.          
  5.         @catch (SomethingException *exception)  
  6.             NSLog(@"%@ %@"[exception name], [exception reason]);  
  7.          
  8.         @catch (SomeOverException *exception)  
  9.             NSLog(@"%@"[exception name]);  
  10.          
  11.         @finally  
  12.             [box printNumber];  
  13.          
  14.      
运行,程序没有崩溃,打印结果:
  1. 2012-07-04 09:14:35.165 ObjectiveCTest[688:f803] Box number is:  
  2. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is:  
  3. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is:  
  4. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is:  
  5. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is:  
  6. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60  
  7. 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] Box number is:  
  8. 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60  
  9. 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] Box number is:  
  10. 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60  
  11. 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] Box number is:  
  12. 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60  
  13. 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] Box number is:  
  14. 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60  
  15. 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] Box number is: 10  
  16. 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] BoxOverflowException  
  17. 2012-07-04 09:14:35.225 ObjectiveCTest[688:f803] Box number is: 11  
超过10时,SomeOverException异常抛出。

3.4 、小于0时的异常

在Box类的setNumber里,当number小于0时,我们抛出普通异常。
  1. @try  
  2.       [box setNumber:-10];  
  3.    
  4.   @catch (NSException *exception)  
  5.       NSLog(@"%@",[exception name]);  
  6.    
  7.   @finally  
  8.       [box printNumber];  
  9.    
打印结果:
  1. 2012-07-04 09:17:42.405 ObjectiveCTest[753:f803] BoxUnderflowException  
  2. 2012-07-04 09:17:42.406 ObjectiveCTest[753:f803] Box number is: -10  

参考链接:http://www.dkankan.com/archives/7221

参考链接:http://blog.sina.com.cn/s/blog_71715bf8010166qf.html


over



0 0