转载:http://www.cocoachina.com/bbs/read.php?tid=66525
原作地址:http://www.mikeash.com/pyblog/friday-qa-2011-06-17-gdb-tips-and-tricks.html
作者:mikeash
这是作者Friday Q&A 中的一篇。觉得很好于是翻译了一下。
\*********************************************/
关于GDB
对于大多数Cocoa程序员来说,最常用的debugger莫过于Xcode自带的调试工具了。而实际上,它正是gdb的一个图形化包装。相对于gdb,图形化带来了很多便利,但同时也缺少了一些重要功能。而且在某些情况下,gdb反而更加方便。因此,学习gdb,了解一下幕后的实质,也是有必要的。
gdb可以通过终端运行,也可以在Xcode的控制台调用命令。本文将通过终端讲述一些gdb的基本命令和技巧。
首先,我们来看一个例子:
#import <Foundation/Foundation.h>
int main(int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"Hello, world!");
[pool release];
return 0;
}
我们把文件命名为test.m,然后编译:
gcc -g -framework Foundation test.m
准备工作已经完成。现在我们可以开始调试了。只要把要调试的文件名作为参数,启动gdb:
gdb a.out
gdb启动后会输出很多法律声明之类的信息。无视它们,最后我们看到一个提示:
(gdb)
成功!现在debugger和刚才编译好的程序都被装载了。不过,现在程序还没有开始运行。因为gdb在程序开始前把它暂停了,好让我们有机会设置调试参数。这次我们不需要做特别设置,所以马上开始运行吧:
(gdb) run
Starting program: /Users/mikeash/shell/a.out
Reading symbols for shared libraries .++++....................... done
2011-06-16 20:28:53.658 a.out[2946:a0f] Hello, world!
Program exited normally.
(gdb)
糟糕,程序竟然exited normally了(==|||)。这可不行,我们得让他崩溃才行。所以我们给这个小程序添加一个bug:
int x = 42;
NSLog("Hello, world! x = %@", x);
nice。这样一来程序就会漂亮地崩溃了:
(gdb) run
Starting program: /Users/mikeash/shell/a.out
Reading symbols for shared libraries .++++....................... done
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: 13 at address: 0x0000000000000000
0x00007fff84f1011c 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:10
9 NSLog("Hello, world! x = %@", x);
这回不仅是函数名,连出错的那行代码也打印出来了。但是,我们还可以使用list(简写为l)命令,打印出更多信息:
ps: 如果需要回到栈列表。可以使用down命令。
(gdb) l
5
6 int main(int argc, char **argv)
7 {
8 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
9 int x = 42;
10 NSLog("Hello, world! x = %@", x);
11 [pool release];
12
13 return 0;
14 }
啊,整个代码都被列出来了。虽然我们用编辑器打开test.m文件然后找到第10行也可以打到同样效果,但显然没有上面的方法更有效率。(当然没有Xcode自带的那个快就是了)
好了,现在我们再来看看这个bug(虽然是我们自己弄出来的)。很明显,在格式化字符串前少加了一个@。我们改正它,并重新运行一遍程序:
(gdb) run
Starting program: /Users/mikeash/shell/a.out
Reading symbols for shared libraries .++++....................... done
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a
0x00007fff84f102b3 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:8
Breakpoint 1 at 0x100000e8f: file test.m, line 8.
debugger给了我们一个回应,告诉我们断点设置成功了,而且这个断点的标号是1。断点的标号很有用,可以用来给断点排序&停用&启用&删除等。不过我们现在不需要理会,我们只是接着运行程序:
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /Users/mikeash/shell/a.out
Breakpoint 1, main (argc=1, argv=0x7fff5fbff628) at test.m:8
8 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
debugger在在我们期望的地方停下了。现在我们使用next(简写n)命令单步调试程序,看看它到底是在哪一行崩溃的:
(gdb) n
9 int x = 42;
(gdb)
10 NSLog(@"Hello, world! x = %@", x);
(gdb)