開始調試以前,必須用程序中的調試信息編譯要調試的程序。這樣,gdb 纔可以調試所使用的變量、代碼行和函數。若是要進行編譯,請在 gcc(或 g++)下使用額外的 '-g' 選項來編譯程序:html
1
|
gcc -g eg.c -o eg
|
在 shell 中,可使用 'gdb' 命令並指定程序名做爲參數來運行 gdb,例如 'gdb eg';或者在 gdb 中,可使用 file 命令來裝入要調試的程序,例如 'file eg'。這兩種方式都假設您是在包含程序的目錄中執行命令。裝入程序以後,能夠用 gdb 命令 'run' 來啓動程序。linux
若是一切正常,程序將執行到結束,此時 gdb 將從新得到控制。但若是有錯誤將會怎麼樣?這種狀況下,gdb 會得到控制並中斷程序,從而可讓您檢查全部事物的狀態,若是運氣好的話,能夠找出緣由。爲了引起這種狀況,咱們將使用一個 示例程序:程序員
1 #include <stdio.h> 2 3 int wib(int no1, int no2) 4 { 5 int result, diff; 6 diff = no1 - no2; 7 result = no1 / diff; 8 return result; 9 } 10 11 int main(int argc, char *argv[]) 12 { 13 int value, div, result, i, total; 14 15 value = 10; 16 div = 6; 17 total = 0; 18 19 for(i = 0; i < 10; i++) 20 { 21 result = wib(value, div); 22 total += result; 23 div++; 24 value--; 25 } 26 printf("%d wibed by %d equals %d\n", value, div, total); 27 return 0; 28 }
這個程序將運行 10 次 for 循環,使用 'wib()" 函數計算出累積值,最後打印出結果。shell
在您喜歡的文本編輯器中輸入這個程序(要保持相同的行距),保存爲 'eg1.c',使用 'gcc -g eg1.c -o eg1' 進行編譯,並用 'gdb eg1' 啓動 gdb。使用 'run' 運行程序可能會產生如下消息:express
1
2
3
4
|
Program received signal SIGFPE, Arithmetic exception.
0x80483ea in wib (no1=8, no2=8) at eg1.c:7
7 result = no1 / diff;
(gdb)
|
gdb 指出在程序第 7 行發生一個算術異常,一般它會打印這一行以及 wib() 函數的自變量值。要查看第 7 行先後的源代碼,請使用 'list' 命令,它一般會打印 10 行。再次輸入 'list'(或者按回車重複上一條命令)將列出程序的下 10 行。從 gdb 消息中能夠看出,第 7 行中的除法運算出了錯,程序在這一行中將變量 "no1" 除以 "diff"。編輯器
要查看變量的值,使用 gdb 'print' 命令並指定變量名。輸入 'print no1' 和 'print diff',能夠相應看到 "no1" 和 "diff" 的值,結果以下:函數
1
2
3
4
|
(gdb) print no1
$5 = 8
(gdb) print diff
$2 = 0
|
gdb 指出 "no1" 等於 8,"diff" 等於 0。根據這些值和第 7 行中的語句,咱們能夠推斷出算術異常是由除數爲 0 的除法運算形成的。清單顯示了第 6 行計算的變量 "diff",咱們能夠打印 "diff" 表達式(使用 'print no1 - no2' 命令),來從新估計這個變量。gdb 告訴咱們 wib 函數的這兩個自變量都等於 8,因而咱們要檢查調用 wib() 函數的 main() 函數,以查看這是在何時發生的。在容許程序天然終止的同時,咱們使用 'continue' 命令告訴 gdb 繼續執行。工具
1
2
3
4
|
(gdb) continue
Continuing.
Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.
|
爲了查看在 main() 中發生了什麼狀況,能夠在程序代碼中的某一特定行或函數中設置斷點,這樣 gdb 會在遇到斷點時中斷執行。可使用命令 'break main' 在進入 main() 函數時設置斷點,或者能夠指定其它任何感興趣的函數名來設置斷點。然而,咱們只但願在調用 wib() 函數以前中斷執行。輸入 'list main' 將打印從 main() 函數開始的源碼清單,再次按回車將顯示第 21 行上的 wib() 函數調用。要在那一行上設置斷點,只需輸入 'break 21'。gdb 將發出如下響應:ui
1
2
|
(gdb) break 21
Breakpoint 1 at 0x8048428: file eg1.c, line 21.
|
以顯示它已在咱們請求的行上設置了 1 號斷點。'run' 命令將從頭從新運行程序,直到 gdb 中斷爲止。發生這種狀況時,gdb 會生成一條消息,指出它在哪一個斷點上中斷,以及程序運行到何處:this
1
2
|
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:21
21 result = wib(value, div);
|
發出 'print value' 和 'print div' 將會顯示在第一次調用 wib() 時,變量分別等於 10 和 6,而 'print i' 將會顯示 0。幸虧,gdb 將顯示全部局部變量的值,並使用 'info locals' 命令保存大量輸入信息。
從以上的調查中能夠看出,當 "value" 和 "div" 相等時就會出現問題,所以輸入 'continue' 繼續執行,直到下一次遇到 1 號斷點。對於此次迭代,'info locals' 顯示了 value=9 和 div=7。
與其再次繼續,還不如使用 'next' 命令單步調試程序,以查看 "value" 和 "div" 是如何改變的。gdb 將響應:
1
2
|
(gdb) next
22 total += result;
|
再按兩次回車將顯示加法和減法表達式:
1
2
3
4
|
(gdb)
23 div++;
(gdb)
24 value--;
|
再按兩次回車將顯示第 21 行,wib() 調用。'info locals' 將顯示目前 "div" 等於 "value",這就意味着將發生問題。若是有興趣,可使用 'step' 命令(與 'next' 造成對比,'next' 將跳過函數調用)來繼續執行 wib() 函數,以再次查看除法錯誤,而後使用 'next' 來計算 "result"。
如今已完成了調試,可使用 'quit' 命令退出 gdb。因爲程序仍在運行,這個操做會終止它,gdb 將提示您確認。
因爲咱們想要知道在調用 wib() 函數以前 "value" 何時等於 "div",所以在上一示例中咱們在第 21 行中設置斷點。咱們必須繼續執行兩次程序纔會發生這種狀況,可是隻要在斷點上設置一個條件就可使 gdb 只在 "value" 與 "div" 真正相等時暫停。要設置條件,能夠在定義斷點時指定 "break <line number> if <conditional expression>"。將 eg1 再次裝入 gdb,並輸入:
1
2
|
(gdb) break 21 if value==div
Breakpoint 1 at 0x8048428: file eg1.c, line 21.
|
若是已經在第 21 行中設置了斷點,如 1 號斷點,則可使用 'condition' 命令來代替在斷點上設置條件:
1
|
(gdb) condition 1 value==div
|
使用 'run' 運行 eg1.c 時,若是 "value" 等於 "div",gdb 將中斷,從而避免了在它們相等以前必須手工執行 'continue'。調試 C 程序時,斷點條件能夠是任何有效的 C 表達式,必定要是程序所使用語言的任意有效表達式。條件中指定的變量必須在設置了斷點的行中,不然表達式就沒有什麼意義!
使用 'condition' 命令時,若是指定斷點編號但又不指定表達式,能夠將斷點設置成無條件斷點,例如,'condition 1' 就將 1 號斷點設置成無條件斷點。
要查看當前定義了什麼斷點及其條件,請發出命令 'info break':
1
2
3
4
5
|
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048428 in main at eg1.c:21
stop only if value == div
breakpoint already hit 1 time
|
除了全部條件和已經遇到斷點多少次以外,斷點信息還在 'Enb' 列中指定了是否啓用該斷點。可使用命令 'disable <breakpoint number>'、'enable <breakpoint number>' 或 'delete <breakpoint number>' 來禁用、啓用和完全刪除斷點,例如 'disable 1' 將阻止在 1 號斷點處中斷。
若是咱們對 "value" 何時變得與 "div" 相等更感興趣,那麼可使用另外一種斷點,稱做監視。當指定表達式的值改變時,監視點將中斷程序執行,但必須在表達式中所使用的變量在做用域中時設置監視點。要獲取做用域中的 "value" 和 "div",能夠在 main 函數上設置斷點,而後運行程序,當遇到 main() 斷點時設置監視點。從新啓動 gdb,並裝入 eg1,而後輸入:
1
2
3
4
5
6
|
(gdb) break main
Breakpoint 1 at 0x8048402: file eg1.c, line 15.
(gdb) run
...
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:15
15 value = 10;
|
要了解 "div" 什麼時候更改,可使用 'watch div',但因爲要在 "div" 等於 "value" 時中斷,那麼應輸入:
1
2
|
(gdb) watch div==value
Hardware watchpoint 2: div == value
|
若是繼續執行,那麼當表達式 "div==value" 的值從 0(假)變成 1(真)時,gdb 將中斷:
1
2
3
4
5
6
7
|
(gdb) continue
Continuing.
Hardware watchpoint 2: div == value
Old value = 0
New value = 1
main (argc=1, argv=0xbffff954) at eg1.c:19
19 for(i = 0; i < 10; i++)
|
'info locals' 命令將驗證 "value" 是否確實等於 "div"(再次聲明,是 8)。
'info watch' 命令將列出已定義的監視點和斷點(此命令等價於 'info break'),並且可使用與斷點相同的語法來啓用、禁用和刪除監視點。
在 gdb 下運行程序可使俘獲錯誤變得更容易,但在調試器外運行的程序一般會停止而只留下一個 core 文件。gdb 能夠裝入 core 文件,並讓您檢查程序停止以前的狀態。
在 gdb 外運行示例程序 eg1 將會致使核心信息轉儲:
1
2
|
$ ./eg1
Floating point exception (core dumped)
|
要使用 core 文件啓動 gdb,在 shell 中發出命令 'gdb eg1 core' 或 'gdb eg1 -c core'。gdb 將裝入 core 文件,eg1 的程序清單,顯示程序是如何終止的,並顯示很是相似於咱們剛纔在 gdb 下運行程序時看到的消息:
1
2
3
4
5
6
|
...
Core was generated by `./eg1'.
Program terminated with signal 8, Floating point exception.
...
#0 0x80483ea in wib (no1=8, no2=8) at eg1.c:7
7 result = no1 / diff;
|
此時,能夠發出 'info locals'、'print'、'info args' 和 'list' 命令來查看引發除數爲零的值。'info variables' 命令將打印出全部程序變量的值,但這要進行很長時間,由於 gdb 將打印 C 庫和程序代碼中的變量。爲了更容易地查明在調用 wib() 的函數中發生了什麼狀況,可使用 gdb 的堆棧命令。
程序「調用堆棧」是當前函數以前的全部已調用函數的列表(包括當前函數)。每一個函數及其變量都被分配了一個「幀」,最近調用的函數在 0 號幀中(「底部」幀)。要打印堆棧,發出命令 'bt'('backtrace' [回溯] 的縮寫):
1
2
3
|
(gdb) bt
#0 0x80483ea in wib (no1=8, no2=8) at eg1.c:7
#1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
|
此結果顯示了在 main() 的第 21 行中調用了函數 wib()(只要使用 'list 21' 就能證明這一點),並且 wib() 在 0 號幀中,main() 在 1 號幀中。因爲 wib() 在 0 號幀中,那麼它就是執行程序時發生算術錯誤的函數。
實際上,發出 'info locals' 命令時,gdb 會打印出當前幀中的局部變量,缺省狀況下,這個幀中的函數就是被中斷的函數(0 號幀)。可使用命令 'frame' 打印當前幀。要查看 main 函數(在 1 號幀中)中的變量,能夠發出 'frame 1' 切換到 1 號幀,而後發出 'info locals' 命令:
1
2
3
4
5
6
7
8
9
|
(gdb) frame 1
#1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
21 result = wib(value, div);
(gdb) info locals
value = 8
div = 8
result = 4
i = 2
total = 6
|
此信息顯示了在第三次執行 "for" 循環時(i 等於 2)發生了錯誤,此時 "value" 等於 "div"。
能夠經過如上所示在 'frame' 命令中明確指定號碼,或者使用 'up' 命令在堆棧中上移以及 'down' 命令在堆棧中下移來切換幀。要獲取有關幀的進一步信息,如它的地址和程序語言,可使用命令 'info frame'。
gdb 堆棧命令能夠在程序執行期間使用,也能夠在 core 文件中使用,所以對於複雜的程序,能夠在程序運行時跟蹤它是如何轉到函數的。
除了調試 core 文件或程序以外,gdb 還能夠鏈接到已經運行的進程(它的程序已通過編譯,並加入了調試信息),並中斷該進程。只需用但願 gdb 鏈接的進程標識替換 core 文件名就能夠執行此操做。如下是一個執行循環並睡眠的示例程序:
1 #include <unistd.h> 2 3 int main(int argc, char *argv[]) 4 { 5 int i; 6 for(i = 0; i < 60; i++) 7 { 8 sleep(1); 9 } 10 return 0; 11 }
使用 'gcc -g eg2.c -o eg2' 編譯該程序並使用 './eg2 &' 運行該程序。請留意在啓動該程序時在背景上打印的進程標識,在本例中是 1283:
1
2
|
./eg2 &
[3] 1283
|
啓動 gdb 並指定進程標識,在我舉的這個例子中是 'gdb eg2 1283'。gdb 會查找一個叫做 "1283" 的 core 文件。若是沒有找到,那麼只要進程 1283 正在運行(在本例中可能在 sleep() 中),gdb 就會鏈接並中斷該進程:
1
2
3
4
5
6
|
...
/home/seager/gdb/1283: No such file or directory.
Attaching to program: /home/seager/gdb/eg2, Pid 1283
...
0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
(gdb)
|
此時,能夠發出全部經常使用 gdb 命令。可使用 'backtrace' 來查看當前位置與 main() 的相對關係,以及 mian() 的幀號是什麼,而後切換到 main() 所在的幀,查看已經在 "for" 循環中運行了多少次:
1
2
3
4
5
6
7
8
9
|
(gdb) backtrace
#0 0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
#1 0x400a877d in __sleep (seconds=1) at ../sysdeps/unix/sysv/linux/sleep.c:78
#2 0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
(gdb) frame 2
#2 0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
7 sleep(1);
(gdb) print i
$1 = 50
|
若是已經完成了對程序的修改,能夠 'detach' 命令繼續執行程序,或者 'kill' 命令殺死進程。還能夠首先使用 'file eg2' 裝入文件,而後發出 'attach 1283' 命令鏈接到進程標識 1283 下的 eg2。
gdb 可讓您經過使用 shell 命令在不退出調試環境的狀況下運行 shell 命令,調用形式是 'shell [commandline]',這有助於在調試時更改源代碼。
最後,在程序運行時,可使用 'set' 命令修改變量的值。在 gdb 下再次運行 eg1,使用命令 'break 7 if diff==0' 在第 7 行(將在此處計算結果)設置條件斷點,而後運行程序。當 gdb 中斷執行時,能夠將 "diff" 設置成非零值,使程序繼續運行直至結束:
1
2
3
4
5
6
7
8
9
|
Breakpoint 1, wib (no1=8, no2=8) at eg1.c:7
7 result = no1 / diff;
(gdb) print diff
$1 = 0
(gdb) set diff=1
(gdb) continue
Continuing.
0 wibed by 16 equals 10
Program exited normally.
|
GNU 調試器是全部程序員工具庫中的一個功能很是強大的工具。在本文中,我只介紹了 gdb 的一小部分功能。要了解更多知識,建議您閱讀 GNU 調試器手冊。