在调试程序的时候,总是碰到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的方法,跟踪问题:

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

iOS crash 分几种 crash_crash_iOS crash 分几种

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

iOS crash 分几种 crash_crash_#import_02

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




设置全局异常断点(SIGSEGV )

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



一、Exception breakpoint全局断点。


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

iOS crash 分几种 crash_crash_iOS crash 分几种_03



二、Symbolic breakpoint



系统抛出异常处设置断点



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

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


iOS crash 分几种 crash_crash_异常处理_04

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


开启僵尸模式(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+< 进到这个窗口。

iOS crash 分几种 crash_crash_iOS crash 分几种_05

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.  
3.  @try { [cup fill];
4.  
5.  } @catch (NSException *exception) {
6.  
7.  NSLog(@”main: Caught %@: %@”, [exception name], [exception reason]);
8.  
9.  } @finally {
10.  
11.  [cup release];
12.  
13.  }



抛出异常



为了掷出一个异常,我们必须实例化一个对象,当然这个异常对象要包含相关的信息,比如异常的名字和为什么要掷出他。


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  





    1. #import "SomethingException.h" 
    2.  
    3. @implementation SomethingException  
    4.  
    5. @end





    1. #import <Foundation/Foundation.h> 
    2.  
    3. @interface SomeOverException : NSException  
    4.  
    5. @end
    1.  





    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. -(void) setNumber: (NSInteger) num;  
    8. -(void) pushIn;  
    9. -(void) pullOut;  
    10. -(void) printNumber;  
    11. @end

     





    1. @implementation Box  
    2. -(id) init {  
    3.    self = [super init];  
    4.      
    5.    if( self ) {  
    6.        [self setNumber: 0];  
    7.    }  
    8.      
    9.    returnself;  
    10. }  
    11.  
    12. -(void) setNumber: (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.        @throwe;  
    21.    } elseif( number >= 6 ) {  
    22.        // throw warning 
    23.        NSException *e = [SomethingException  
    24.                          exceptionWithName: @"BoxWarningException" 
    25.                          reason: @"The level is above or at 60" 
    26.                          userInfo: nil];  
    27.        @throwe;  
    28.    } elseif( number < 0 ) {  
    29.        // throw exception 
    30.        NSException *e = [NSException  
    31.                          exceptionWithName: @"BoxUnderflowException" 
    32.                          reason: @"The level is below 0" 
    33.                          userInfo: nil];  
    34.        @throwe;  
    35.    }  
    36. }  
    37.  
    38. -(void) pushIn {  
    39.    [self setNumber: number + 1];  
    40. }  
    41.  
    42. -(void) pullOut {  
    43.    [self setNumber: number - 1];  
    44. }  
    45.  
    46. -(void) printNumber {  
    47.    NSLog(@"Box number is: %d", number);  
    48. }  
    49. @end



    这里写 [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(inti = 0; i < 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(inti = 0; i < 11; i++) {  
      2.            [box pushIn];  
      3.            [box printNumber];  
      4.    }





      1. 2012-07-04 09:12:05.889 ObjectiveCTest[648:f803] Box number is: 1  
      2. 2012-07-04 09:12:05.890 ObjectiveCTest[648:f803] Box number is: 2  
      3. 2012-07-04 09:12:05.890 ObjectiveCTest[648:f803] Box number is: 3  
      4. 2012-07-04 09:12:05.890 ObjectiveCTest[648:f803] Box number is: 4  
      5. 2012-07-04 09:12:05.891 ObjectiveCTest[648:f803] Box number is: 5  
      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(inti = 0; i < 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: 1  
      2. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is: 2  
      3. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is: 3  
      4. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is: 4  
      5. 2012-07-04 09:14:35.167 ObjectiveCTest[688:f803] Box number is: 5  
      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: 6  
      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: 7  
      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: 8  
      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: 9  
      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  



      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