在调试程序的时候,总是碰到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+< 进到这个窗口。
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
- #import <Foundation/Foundation.h>
- @interface SomethingException : NSException
- @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. #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. }
- 2012-07-04 09:12:05.889 ObjectiveCTest[648:f803] Box number is: 1
- 2012-07-04 09:12:05.890 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.891 ObjectiveCTest[648:f803] Box number is: 5
- 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. }
运行,程序没有崩溃,打印结果:
- 2012-07-04 09:14:35.165 ObjectiveCTest[688:f803] Box number is: 1
- 2012-07-04 09:14:35.167 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] BoxWarningException The number is above or at 60
- 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] Box number is: 6
- 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60
- 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] Box number is: 7
- 2012-07-04 09:14:35.168 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60
- 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
- 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] Box number is: 9
- 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] BoxWarningException The number is above or at 60
- 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] Box number is: 10
- 2012-07-04 09:14:35.169 ObjectiveCTest[688:f803] BoxOverflowException
- 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. }
- 2012-07-04 09:17:42.405 ObjectiveCTest[753:f803] BoxUnderflowException
- 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