此文下半部分为转载:在此感谢原创者。
---------------------
关于调试异常崩溃:
一般崩溃是由内存使用错误导致的,要么多了,要么少了。
用xcode的调试提示可以知道是什么原因导致的崩溃。
在xcode中product àedit scheme à diagnostics 将enable Zombie objects 和 Malloc Stack 选中, 如果是内存释放错误,则gdb会提示release dealloc object。
然后可以用断点缩小错误范围, 在可能出现错误的地方用单步调试, 当执行到有错误代码时, gdb会再次提示 release dealloc object。
其实XCODE内嵌GDB,那个 lldb就是gdb!
-----------------------
http://blog.csdn.net/ch_soft/article/details/7005998
关于GDB
对于大多数Cocoa程 序员来说,最常用的debugger莫过于Xcode自带的调试工具了。而实际上,它正是gdb的一个图形化包装。相对于gdb,图形化带来了很多便利, 但同时也缺少了一些重要功能。而且在某些情况下,gdb反而更加方便。因此,学习gdb,了解一下幕后的实质,也是有必要的。gdb可以通过终端运行,也可以在Xcode的控制台调用命令。本文将通过终端讲述一些gdb的基本命令和技巧。首先,我们来看一个例子:#importint main(int argc, char **argv){ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];NSLog(@"Hello, world!");[pool release];return 0;}糟糕,程序竟然exited normally了(==|||)。这可不行,我们得让他崩溃才行。所以我们给这个小程序添加一个bug:int x = 42;NSLog("Hello, world! x = %@", x);nice。这样一来程序就会漂亮地崩溃了:(gdb) runStarting program: /Users/mikeash/shell/a.outReading symbols for shared libraries .++++....................... doneProgram received signal EXC_BAD_ACCESS, Could not access memory.Reason: 13 at address: 0x00000000000000000x00007fff84f1011c in objc_msgSend ()(gdb)如果我们是在shell中直接运行的程序,在崩溃后就会回到shell。不过现在我们是通过gdb运行的,所以现在并没有跳出。gdb暂停了我们的程序,但依然使之驻留在内存中,让我们有机会做调试。首先,我们想知道具体是哪里导致了程序崩溃。gdb已经通过刚才的输出告知了我们: 函数objc_msgSend就是祸之根源。但是这个信息并不足够,因为这个objc_msgSend是objc运行时库中的函数。我们并不知道它是怎么调用的。我们关注的是我们自己的代码。要知道这一点,我们需要得到当前进程的函数调用栈的情况,以此回溯找到我们自己的方法。这时我们需要用到backtrace命令,一般简写为bt:(gdb) bt#0 0x00007fff84f1011c in objc_msgSend ()#1 0x00007fff864ff30b in _CFStringAppendFormatAndArgumentsAux ()#2 0x00007fff864ff27d in _CFStringCreateWithFormatAndArgumentsAux ()#3 0x00007fff8657e9ef in _CFLogvEx ()#4 0x00007fff87beab3e in NSLogv ()#5 0x00007fff87beaad6 in NSLog ()#6 0x0000000100000ed7 in main () at test.m:10现在我们可以看到,程序在test.m的第10行,调用NSLog方法时崩溃了。接下来我们想看一下这次调用的详细信息。这时我们要用到up命令。up命令可以在栈的各层之间跳转。本例中,我们的代码main是#6:(gdb) up 6#6 0x0000000100000ed7 in main () at test.m:109NSLog("Hello, world! x = %@", x);这回不仅是函数名,连出错的那行代码也打印出来了。但是,我们还可以使用list(简写为l)命令,打印出更多信息:ps: 如果需要回到栈列表。可以使用down命令。(gdb) l56int main(int argc, char **argv)7{ 8NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];9int x = 42;10NSLog("Hello, world! x = %@", x);11[pool release];1213return 0;14}啊,整个代码都被列出来了。虽然我们用编辑器打开test.m文件然后找到第10行也可以打到同样效果,但显然没有上面的方法更有效率。(当然没有Xcode自带的那个快就是了)好了,现在我们再来看看这个bug(虽然是我们自己弄出来的)。很明显,在格式化字符串前少加了一个@。我们改正它,并重新运行一遍程序:(gdb) runStarting program: /Users/mikeash/shell/a.outReading symbols for shared libraries .++++....................... doneProgram received signal EXC_BAD_ACCESS, Could not access memory.Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a0x00007fff84f102b3 in objc_msgSend_fixup ()(gdb) bt#0 0x00007fff84f102b3 in objc_msgSend_fixup ()#1 0x0000000000000000 in ?? ()啊咧,程序还是崩溃了。更杯具的是,栈信息没有显示出这个objc_msgSend_fixup方法是从哪里调用的。这样我们就没法用上面的方法找到目标代码了。这时,我们只好请出一个debugger最常用的功能:断点。在gdb中,设置断点通过break命令实现。它可以简写为b。有两种方法可以确定断点的位置:传入一个已定义的符号,或是直接地通过一个file:line对设置位置。现在让我们在main函数的开始处设置一个断点:(gdb) b test.m:8Breakpoint 1 at 0x100000e8f: file test.m, line 8.debugger给了我们一个回应,告诉我们断点设置成功了,而且这个断点的标号是1。断点的标号很有用,可以用来给断点排序&停用&启用&删除等。不过我们现在不需要理会,我们只是接着运行程序:(gdb) runThe program being debugged has been started already.Start it from the beginning? (y or n) yStarting program: /Users/mikeash/shell/a.outBreakpoint 1, main (argc=1, argv=0x7fff5fbff628) at test.m:88NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];debugger在在我们期望的地方停下了。现在我们使用next(简写n)命令单步调试程序,看看它到底是在哪一行崩溃的:(gdb) n9int x = 42;(gdb)10NSLog(@"Hello, world! x = %@", x);(gdb)Program received signal EXC_BAD_ACCESS, Could not access memory.Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a0x00007fff84f102b3 in objc_msgSend_fixup ()值得注意的是,我只键入了一次n命令,随后直接敲了2次回车。这样做的原因是gdb把任何空输入当作最近一次输入命令的重复。所以这里相当于输入了3次n。现在我们可以看到,崩溃之处依然是NSLog。原因嘛,当然是在格式化输出的地方用%@表示int型变量x了。我们仔细看一下输出信息:崩溃原因是错误地 访问了0x000000000000002a这个地址。而2a的十进制表示正是42--我们为x赋的值。编译器把它当作地址了。输出数值一个很重要的调试方法是输出表达式和变量的值。在gdb中,这是通过print命令完成的。(gdb) p x$1 = 42在print命令后追加/format可以格式化输出。/format是一个gdb的格式化字符串,比较有用的格式化字符有 x:十进制数; c:字符; a:地址等。(gdb) p/x x$2 = 0x2aprint-object方法(简写为po)用来输出obj-c中的对象。它的工作原理是,向被调用的对象发送名为debugDescription的消息。它和常见的description消息很像。举例来说,让我们输出一下autorelease pool:(gdb) po pool这个命令不仅仅可以输出显式定义的对象,也可以输出表达式的结果。这次我们测试一下nsobject中debugDescription的方法签名:返回值是对象的表达式可以用po命令输出结果,那么返回值是基本类型的方法又怎样呢?显然,它们是可以用p命令输出的。但是要小心,因为gdb并不能自动识别出返回值的类型。所以我们在输出前要显式地转换一下:(gdb) p [NSObject instancesRespondToSelector: @selector(doesNotExist)]Unable to call function "objc_msgSend" at 0x7fff84f100f4: no return type information available.To call this function anyway, you can cast the return type explicitly (e.g. 'print (float) fabs (3.0)')(gdb) p (char)[NSObject instancesRespondToSelector: @selector(doesNotExist)]$5 = 0 '00'你也许发现了,doesNotExist方法的返回值是BOOL,而我们做的转换却是char。这是因为gdb也不能识别那些用typedef定义的类型。不仅仅是你定义的,即使是Cocoa框架里定义的也不行。你也许已经注意到,在用p进行输出的时侯,输出值前面会有一个类似"$1="的前缀。它们是gdb变量。它们可以在后面的表达式中使用,来指代它后面的 值。在下面的例子里,我们开辟了一块内存,将其置零,然后释放。在这个过程中,我们使用了gdb变量,这样就不用一遍遍地复制粘贴地址了。(gdb) p (int *)malloc(4)$6 = (int *) 0x100105ab0(gdb) p (void)bzero($6, 4)$7 = void(gdb) p *$6$8 = 0(gdb) p (void)free($6)$9 = void我们也想把这个技巧用到对象上,但不幸的是po命令并不会把它的返回值存储到变量里。所以我们在得到一个新的对象时必须先使用p命令:(gdb) p (void *)[[NSObject alloc] init]$10 = (void *) 0x100105950(gdb) po $10(gdb) p (long)[$10 retainCount]$11 = 1(gdb) p (void)[$10 release]$12 = void检查内存有些时候,仅仅输出一个数值还不能帮助我们查找出错误。我们需要一次性地打印出一整块内存来窥视全局。这时候我们就需要使用x命令。x命令的格式是x/format address。其中address很简单,它通常是指向一块内存的表达式。但是format的语法就有点复杂了。它由三个部分组成:第一个是要显示的块的数量;第二个是显示格式(如x代表16进制,d代表十进制,c代表字符);第三个是每个块的大小。值得注意的是第三部分,即块大小是用字符对应的。用b, h, w,g 分别表示1, 2, 4, 8 bytes。举例来说,用十六进制方式,打印从ptr开始的4个4-byte块应该这样写:(gdb) x/4xw ptr接下来举一个比较实际的例子。我们看一下NSObject类的内容:(gdb) x/4xg (void *)[NSObject class]0x7fff70adb468 : 0x00007fff70adb4400x00000000000000000x7fff70adb478 :0x0000000100105ac00x0000000100104ac0接下来再看看一个NSObject实例的内容:(gdb) x/1xg (void *)[NSObject new]0x100105ba0:0x00007fff70adb468现在我们看到,在实例开头引用了类的地址。设置变量有时,查看数值程度的能力还是稍弱了一点,我们还想能够修改变量。这也很简单,只需要使用set命令:(gdb) set x = 43我们可以用任意表达式给一个变量赋值。比如说新创建一个对象然后赋值:(gdb) set obj = (void *)[[NSObject alloc] init]断点我们可以在程序的某个位置设置断点,这样当程序运行到那里的时候就会暂停,而把控制权转移给调试器。就像之前提到的,我们用break命令来设置断点。下面详细地列出了如何设置断点的目标:SymbolName: 为断点指定一个函数名。这样断点就会设置在该函数上。file.c:1234: 把断点设置在指定文件的一行。-[ClassName method:name:]: 把断点设置在objc的方法上。用+代表类方法。*0xdeadbeef: 在内存的指定位置设置断点。这不是很常用,一般在没有源码的调试时使用。断点可以用enable命令和disable命令来切换到使用和停用状态,也可以通过delete命令彻底删除。想要查看现有断点的话,使用info breakpoints命令(可以简写成info b,或是i b)。另外,我们也可以用if命令,把断点升级成条件断点。顾名思义,条件断点只会在设定的条件成真时起作用。举例来说,下面的语句为MyMethod添加了一个条件断点,它只在参数等于5的时候有效:(gdb) b -[Class myMethod:] if parameter == 5最后,在断点上可以附加gdb命令。这样,当断点中断时,附带的命令会自动执行。附加命令使用commands breakpointnumber。这时gdb就会进入断点指令输入状态。断点指令就是一个以end结尾的标准gdb指令序列。举个例子,我们想在每次NSLog被调用时输出栈信息:(gdb) b NSLogBreakpoint 4 at 0x7fff87beaa62(gdb) commandsType commands for when breakpoint 4 is hit, one per line.End with a line saying just "end".>bt>end
2)其他
XCode 内置GDB,我们可以在命令行中使用 来调试我们的程序。下面将介绍一些常用的命令以及调试技巧。
po 命令:为 print object 的缩写,显示对象的文本描述(显示从对象的 description 消息获得的字符串信息)。
比如:
上图中,我使用 po 命令显示一个 NSDictionary 的内容。注意在左侧我们可以看到 dict 的一些信息:3 key/value pairs,显示该 dict 包含的数据量,而展开的信息显示 isa 层次体系(即)。我们可以右击左侧的 dict,选中“Print Description of "dict"”,则可以在控制台输出 dict 的详细信息:
[cpp] view plain copy
print?
- Printing description of dict:
- <CFBasicHash 0x1001149e0 [0x7fff7e27ff40]>{type = immutable dict, count = 3,
- entries =>
- 0 : <CFString 0x100002458 [0x7fff7e27ff40]>{contents = "first"} = <CFString 0x100002438 [0x7fff7e27ff40]>{contents = "one"}
- 1 : <CFString 0x100002498 [0x7fff7e27ff40]>{contents = "second"} = <CFString 0x100002478 [0x7fff7e27ff40]>{contents = "two"}
- 2 : <CFString 0x1000024d8 [0x7fff7e27ff40]>{contents = "third"} = <CFString 0x1000024b8 [0x7fff7e27ff40]>{contents = "three"}
- }
- (gdb)
如:
[cpp] view plain copy
print?
- (gdb) print (char *)[[dict description] cStringUsingEncoding:4]
- $1 = 0x1001159c0 "{\n first = one;\n second = two;\n third = three;\n}"
- (gdb) print (int)[dict retainCount]
- $2 = 1
- (gdb)
注:4是 NSUTF8StringEncoding 的值。
info 命令:我们可以查看内存地址所在信息
比如 "info symbol 内存地址" 可以获取内存地址所在的 symbol 相关信息:
[cpp] view plain copy
print?
- (gdb) info symbol 0x00000001000017f7
- main + 343 in section LC_SEGMENT.__TEXT.__text of /Users/LuoZhaohui/Library/Developer/Xcode/DerivedData/RunTimeSystem-anzdlhiwvlbizpfureuvenvmatnp/Build/Products/Debug/RunTimeSystem
比如 "info line *内存地址" 可以获取内存地址所在的代码行相关信息:
[cpp] view plain copy
print?
- (gdb) info line *0x00000001000017f7
- Line 62 of "/Users/LuoZhaohui/Documents/Study/RunTimeSystem/RunTimeSystem/main.m" starts at address 0x1000017f7 <main+343> and ends at 0x10000180a <main+362>.
show 命令:显示 GDB 相关的信息。如:show version 显示GDB版本信息
[cpp] view plain copy
print?
- (gdb) show version
- GNU gdb 6.3.50-20050815 (Apple version gdb-1708) (Mon Aug 8 20:32:45 UTC 2011)
- Copyright 2004 Free Software Foundation, Inc.
- GDB is free software, covered by the GNU General Public License, and you are
- welcome to change it and/or distribute copies of it under certain conditions.
- Type "show copying" to see the conditions.
- There is absolutely no warranty for GDB. Type "show warranty" for details.
- This GDB was configured as "x86_64-apple-darwin".
[cpp] view plain copy
print?
- (gdb) help info
- Generic command for showing things about the program being debugged.
- List of info subcommands:
- info address -- Describe where symbol SYM is stored
- info all-registers -- List of all registers and their contents
- info args -- Argument variables of current stack frame
- info auxv -- Display the inferior's auxiliary vector
- info breakpoints -- Status of user-settable breakpoints
- info catch -- Exceptions that can be caught in the current stack frame
- info checkpoints -- Help
- info classes -- All Objective-C classes
- ......
- Type "help info" followed by info subcommand name for full documentation.
- Command name abbreviations are allowed if unambiguous.
- (gdb)
在系统抛出异常处设置断点
有时候我们的程序不知道跑到哪个地方就 crash 了,而 crash 又很难重现。保守的做法是在系统抛出异常之前设置断点,具体来说是在 objc_exception_throw处设置断点。设置步骤为:首先在 XCode 按 CMD + 6,进入断点管理窗口;然后点击右下方的 +,增加新的 Symbolic Breakpoint,在 Symbol 一栏输入:objc_exception_throw,然后点击 done,完成。 这样在 Debug 模式下,如果程序即将抛出异常,就能在抛出异常处中断了。比如在前面的代码中,我让 [firstObjctcrashTest]; 抛出异常。在 objc_exception_throw 处设置断点之后,程序就能在该代码处中断了,我们从而知道代码在什么地方出问题了。