關於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;