開始調(diào)試之前,必須用程序中的調(diào)試信息編譯要調(diào)試的程序。這樣,gdb 才能夠調(diào)試所使用的變量、代碼行和函數(shù)。如果要進(jìn)行編譯,請?jiān)?gcc(或 g++)下使用額外的 '-g' 選項(xiàng)來編譯程序:
gcc -g eg.c -o eg |
在 shell 中,可以使用 'gdb' 命令并指定程序名作為參數(shù)來運(yùn)行 gdb,例如 'gdb eg';或者在 gdb 中,可以使用 file 命令來裝入要調(diào)試的程序,例如 'file eg'。這兩種方式都假設(shè)您是在包含程序的目錄中執(zhí)行命令。裝入程序之后,可以用 gdb 命令 'run' 來啟動(dòng)程序。
如果一切正常,程序?qū)?zhí)行到結(jié)束,此時(shí) gdb 將重新獲得控制。但如果有錯(cuò)誤將會(huì)怎么樣?這種情況下,gdb 會(huì)獲得控制并中斷程序,從而可以讓您檢查所有事物的狀態(tài),如果運(yùn)氣好的話,可以找出原因。為了引發(fā)這種情況,我們將使用一個(gè) 示例程序:
代碼示例 eg1.c
#include int wib(int no1, int no2){ int result, diff; diff = no1 - no2; result = no1 / diff; return result;}int main(int argc, char *argv[]){ int value, div, result, i, total; value = 10; div = 6; total = 0; for(i = 0; i < 10; i++) { result = wib(value, div); total += result; div++; value--; } printf("%d wibed by %d equals %d\n", value, div, total); return 0;} |
這個(gè)程序?qū)⑦\(yùn)行 10 次 for 循環(huán),使用 'wib()" 函數(shù)計(jì)算出累積值,最后打印出結(jié)果。
在您喜歡的文本編輯器中輸入這個(gè)程序(要保持相同的行距),保存為 'eg1.c',使用 'gcc -g eg1.c -o eg1' 進(jìn)行編譯,并用 'gdb eg1' 啟動(dòng) gdb。使用 'run' 運(yùn)行程序可能會(huì)產(chǎn)生以下消息:
Program received signal SIGFPE, Arithmetic exception.0x80483ea in wib (no1=8, no2=8) at eg1.c:77 result = no1 / diff;(gdb) |
gdb 指出在程序第 7 行發(fā)生一個(gè)算術(shù)異常,通常它會(huì)打印這一行以及 wib() 函數(shù)的自變量值。要查看第 7 行前后的源代碼,請使用 'list' 命令,它通常會(huì)打印 10 行。再次輸入 'list'(或者按回車重復(fù)上一條命令)將列出程序的下 10 行。從 gdb 消息中可以看出,第 7 行中的除法運(yùn)算出了錯(cuò),程序在這一行中將變量 "no1" 除以 "diff"。
要查看變量的值,使用 gdb 'print' 命令并指定變量名。輸入 'print no1' 和 'print diff',可以相應(yīng)看到 "no1" 和 "diff" 的值,結(jié)果如下:
(gdb) print no1$5 = 8(gdb) print diff$2 = 0 |
gdb 指出 "no1" 等于 8,"diff" 等于 0。根據(jù)這些值和第 7 行中的語句,我們可以推斷出算術(shù)異常是由除數(shù)為 0 的除法運(yùn)算造成的。清單顯示了第 6 行計(jì)算的變量 "diff",我們可以打印 "diff" 表達(dá)式(使用 'print no1 - no2' 命令),來重新估計(jì)這個(gè)變量。gdb 告訴我們 wib 函數(shù)的這兩個(gè)自變量都等于 8,于是我們要檢查調(diào)用 wib() 函數(shù)的 main() 函數(shù),以查看這是在什么時(shí)候發(fā)生的。在允許程序自然終止的同時(shí),我們使用 'continue' 命令告訴 gdb 繼續(xù)執(zhí)行。
(gdb) continueContinuing.Program terminated with signal SIGFPE, Arithmetic exception.The program no longer exists. |
為了查看在 main() 中發(fā)生了什么情況,可以在程序代碼中的某一特定行或函數(shù)中設(shè)置斷點(diǎn),這樣 gdb 會(huì)在遇到斷點(diǎn)時(shí)中斷執(zhí)行??梢允褂妹?'break main' 在進(jìn)入 main() 函數(shù)時(shí)設(shè)置斷點(diǎn),或者可以指定其它任何感興趣的函數(shù)名來設(shè)置斷點(diǎn)。然而,我們只希望在調(diào)用 wib() 函數(shù)之前中斷執(zhí)行。輸入 'list main' 將打印從 main() 函數(shù)開始的源碼清單,再次按回車將顯示第 21 行上的 wib() 函數(shù)調(diào)用。要在那一行上設(shè)置斷點(diǎn),只需輸入 'break 21'。gdb 將發(fā)出以下響應(yīng):
(gdb) break 21Breakpoint 1 at 0x8048428: file eg1.c, line 21. |
以顯示它已在我們請求的行上設(shè)置了 1 號(hào)斷點(diǎn)。'run' 命令將從頭重新運(yùn)行程序,直到 gdb 中斷為止。發(fā)生這種情況時(shí),gdb 會(huì)生成一條消息,指出它在哪個(gè)斷點(diǎn)上中斷,以及程序運(yùn)行到何處:
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:2121 result = wib(value, div); |
發(fā)出 'print value' 和 'print div' 將會(huì)顯示在第一次調(diào)用 wib() 時(shí),變量分別等于 10 和 6,而 'print i' 將會(huì)顯示 0。幸好,gdb 將顯示所有局部變量的值,并使用 'info locals' 命令保存大量輸入信息。
從以上的調(diào)查中可以看出,當(dāng) "value" 和 "div" 相等時(shí)就會(huì)出現(xiàn)問題,因此輸入 'continue' 繼續(xù)執(zhí)行,直到下一次遇到 1 號(hào)斷點(diǎn)。對于這次迭代,'info locals' 顯示了 value=9 和 div=7。
與其再次繼續(xù),還不如使用 'next' 命令單步調(diào)試程序,以查看 "value" 和 "div" 是如何改變的。gdb 將響應(yīng):
(gdb) next22 total += result; |
再按兩次回車將顯示加法和減法表達(dá)式:
(gdb)23 div++;(gdb)24 value--; |
再按兩次回車將顯示第 21 行,wib() 調(diào)用。'info locals' 將顯示目前 "div" 等于 "value",這就意味著將發(fā)生問題。如果有興趣,可以使用 'step' 命令(與 'next' 形成對比,'next' 將跳過函數(shù)調(diào)用)來繼續(xù)執(zhí)行 wib() 函數(shù),以再次查看除法錯(cuò)誤,然后使用 'next' 來計(jì)算 "result"。
現(xiàn)在已完成了調(diào)試,可以使用 'quit' 命令退出 gdb。由于程序仍在運(yùn)行,這個(gè)操作會(huì)終止它,gdb 將提示您確認(rèn)。
由于我們想要知道在調(diào)用 wib() 函數(shù)之前 "value" 什么時(shí)候等于 "div",因此在上一示例中我們在第 21 行中設(shè)置斷點(diǎn)。我們必須繼續(xù)執(zhí)行兩次程序才會(huì)發(fā)生這種情況,但是只要在斷點(diǎn)上設(shè)置一個(gè)條件就可以使 gdb 只在 "value" 與 "div" 真正相等時(shí)暫停。要設(shè)置條件,可以在定義斷點(diǎn)時(shí)指定 "break <line number> if <conditional expression>"。將 eg1 再次裝入 gdb,并輸入:
(gdb) break 21 if value==divBreakpoint 1 at 0x8048428: file eg1.c, line 21. |
如果已經(jīng)在第 21 行中設(shè)置了斷點(diǎn),如 1 號(hào)斷點(diǎn),則可以使用 'condition' 命令來代替在斷點(diǎn)上設(shè)置條件:
(gdb) condition 1 value==div |
使用 'run' 運(yùn)行 eg1.c 時(shí),如果 "value" 等于 "div",gdb 將中斷,從而避免了在它們相等之前必須手工執(zhí)行 'continue'。調(diào)試 C 程序時(shí),斷點(diǎn)條件可以是任何有效的 C 表達(dá)式,一定要是程序所使用語言的任意有效表達(dá)式。條件中指定的變量必須在設(shè)置了斷點(diǎn)的行中,否則表達(dá)式就沒有什么意義!
使用 'condition' 命令時(shí),如果指定斷點(diǎn)編號(hào)但又不指定表達(dá)式,可以將斷點(diǎn)設(shè)置成無條件斷點(diǎn),例如,'condition 1' 就將 1 號(hào)斷點(diǎn)設(shè)置成無條件斷點(diǎn)。
要查看當(dāng)前定義了什么斷點(diǎn)及其條件,請發(fā)出命令 'info break':
(gdb) info breakNum Type Disp Enb Address What1 breakpoint keep y 0x08048428 in main at eg1.c:21 stop only if value == div breakpoint already hit 1 time |
除了所有條件和已經(jīng)遇到斷點(diǎn)多少次之外,斷點(diǎn)信息還在 'Enb' 列中指定了是否啟用該斷點(diǎn)??梢允褂妹?'disable <breakpoint number>'、'enable <breakpoint number>' 或 'delete <breakpoint number>' 來禁用、啟用和徹底刪除斷點(diǎn),例如 'disable 1' 將阻止在 1 號(hào)斷點(diǎn)處中斷。
如果我們對 "value" 什么時(shí)候變得與 "div" 相等更感興趣,那么可以使用另一種斷點(diǎn),稱作監(jiān)視。當(dāng)指定表達(dá)式的值改變時(shí),監(jiān)視點(diǎn)將中斷程序執(zhí)行,但必須在表達(dá)式中所使用的變量在作用域中時(shí)設(shè)置監(jiān)視點(diǎn)。要獲取作用域中的 "value" 和 "div",可以在 main 函數(shù)上設(shè)置斷點(diǎn),然后運(yùn)行程序,當(dāng)遇到 main() 斷點(diǎn)時(shí)設(shè)置監(jiān)視點(diǎn)。重新啟動(dòng) gdb,并裝入 eg1,然后輸入:
(gdb) break mainBreakpoint 1 at 0x8048402: file eg1.c, line 15.(gdb) run...Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:1515 value = 10; |
要了解 "div" 何時(shí)更改,可以使用 'watch div',但由于要在 "div" 等于 "value" 時(shí)中斷,那么應(yīng)輸入:
(gdb) watch div==valueHardware watchpoint 2: div == value |
如果繼續(xù)執(zhí)行,那么當(dāng)表達(dá)式 "div==value" 的值從 0(假)變成 1(真)時(shí),gdb 將中斷:
(gdb) continueContinuing.Hardware watchpoint 2: div == valueOld value = 0New value = 1main (argc=1, argv=0xbffff954) at eg1.c:1919 for(i = 0; i < 10; i++) |
'info locals' 命令將驗(yàn)證 "value" 是否確實(shí)等于 "div"(再次聲明,是 8)。
'info watch' 命令將列出已定義的監(jiān)視點(diǎn)和斷點(diǎn)(此命令等價(jià)于 'info break'),而且可以使用與斷點(diǎn)相同的語法來啟用、禁用和刪除監(jiān)視點(diǎn)。
在 gdb 下運(yùn)行程序可以使俘獲錯(cuò)誤變得更容易,但在調(diào)試器外運(yùn)行的程序通常會(huì)中止而只留下一個(gè) core 文件。gdb 可以裝入 core 文件,并讓您檢查程序中止之前的狀態(tài)。
在 gdb 外運(yùn)行示例程序 eg1 將會(huì)導(dǎo)致核心信息轉(zhuǎn)儲(chǔ):
$ ./eg1Floating point exception (core dumped) |
要使用 core 文件啟動(dòng) gdb,在 shell 中發(fā)出命令 'gdb eg1 core' 或 'gdb eg1 -c core'。gdb 將裝入 core 文件,eg1 的程序清單,顯示程序是如何終止的,并顯示非常類似于我們剛才在 gdb 下運(yùn)行程序時(shí)看到的消息:
...Core was generated by `./eg1'.Program terminated with signal 8, Floating point exception....#0 0x80483ea in wib (no1=8, no2=8) at eg1.c:77 result = no1 / diff; |
此時(shí),可以發(fā)出 'info locals'、'print'、'info args' 和 'list' 命令來查看引起除數(shù)為零的值。'info variables' 命令將打印出所有程序變量的值,但這要進(jìn)行很長時(shí)間,因?yàn)?gdb 將打印 C 庫和程序代碼中的變量。為了更容易地查明在調(diào)用 wib() 的函數(shù)中發(fā)生了什么情況,可以使用 gdb 的堆棧命令。
程序“調(diào)用堆棧”是當(dāng)前函數(shù)之前的所有已調(diào)用函數(shù)的列表(包括當(dāng)前函數(shù))。每個(gè)函數(shù)及其變量都被分配了一個(gè)“幀”,最近調(diào)用的函數(shù)在 0 號(hào)幀中(“底部”幀)。要打印堆棧,發(fā)出命令 'bt'('backtrace' [回溯] 的縮寫):
(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 |
此結(jié)果顯示了在 main() 的第 21 行中調(diào)用了函數(shù) wib()(只要使用 'list 21' 就能證實(shí)這一點(diǎn)),而且 wib() 在 0 號(hào)幀中,main() 在 1 號(hào)幀中。由于 wib() 在 0 號(hào)幀中,那么它就是執(zhí)行程序時(shí)發(fā)生算術(shù)錯(cuò)誤的函數(shù)。
實(shí)際上,發(fā)出 'info locals' 命令時(shí),gdb 會(huì)打印出當(dāng)前幀中的局部變量,缺省情況下,這個(gè)幀中的函數(shù)就是被中斷的函數(shù)(0 號(hào)幀)。可以使用命令 'frame' 打印當(dāng)前幀。要查看 main 函數(shù)(在 1 號(hào)幀中)中的變量,可以發(fā)出 'frame 1' 切換到 1 號(hào)幀,然后發(fā)出 'info locals' 命令:
(gdb) frame 1#1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:2121 result = wib(value, div);(gdb) info localsvalue = 8div = 8result = 4i = 2total = 6 |
此信息顯示了在第三次執(zhí)行 "for" 循環(huán)時(shí)(i 等于 2)發(fā)生了錯(cuò)誤,此時(shí) "value" 等于 "div"。
可以通過如上所示在 'frame' 命令中明確指定號(hào)碼,或者使用 'up' 命令在堆棧中上移以及 'down' 命令在堆棧中下移來切換幀。要獲取有關(guān)幀的進(jìn)一步信息,如它的地址和程序語言,可以使用命令 'info frame'。
gdb 堆棧命令可以在程序執(zhí)行期間使用,也可以在 core 文件中使用,因此對于復(fù)雜的程序,可以在程序運(yùn)行時(shí)跟蹤它是如何轉(zhuǎn)到函數(shù)的。
除了調(diào)試 core 文件或程序之外,gdb 還可以連接到已經(jīng)運(yùn)行的進(jìn)程(它的程序已經(jīng)過編譯,并加入了調(diào)試信息),并中斷該進(jìn)程。只需用希望 gdb 連接的進(jìn)程標(biāo)識(shí)替換 core 文件名就可以執(zhí)行此操作。以下是一個(gè)執(zhí)行循環(huán)并睡眠的 示例程序:
eg2 示例代碼
#include int main(int argc, char *argv[]){ int i; for(i = 0; i < 60; i++) { sleep(1); } return 0;} |
使用 'gcc -g eg2.c -o eg2' 編譯該程序并使用 './eg2 &' 運(yùn)行該程序。請留意在啟動(dòng)該程序時(shí)在背景上打印的進(jìn)程標(biāo)識(shí),在本例中是 1283:
./eg2 &[3] 1283 |
啟動(dòng) gdb 并指定進(jìn)程標(biāo)識(shí),在我舉的這個(gè)例子中是 'gdb eg2 1283'。gdb 會(huì)查找一個(gè)叫作 "1283" 的 core 文件。如果沒有找到,那么只要進(jìn)程 1283 正在運(yùn)行(在本例中可能在 sleep() 中),gdb 就會(huì)連接并中斷該進(jìn)程:
.../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) |
此時(shí),可以發(fā)出所有常用 gdb 命令??梢允褂?'backtrace' 來查看當(dāng)前位置與 main() 的相對關(guān)系,以及 mian() 的幀號(hào)是什么,然后切換到 main() 所在的幀,查看已經(jīng)在 "for" 循環(huán)中運(yùn)行了多少次:
(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:77 sleep(1);(gdb) print i$1 = 50 |
如果已經(jīng)完成了對程序的修改,可以 'detach' 命令繼續(xù)執(zhí)行程序,或者 'kill' 命令殺死進(jìn)程。還可以首先使用 'file eg2' 裝入文件,然后發(fā)出 'attach 1283' 命令連接到進(jìn)程標(biāo)識(shí) 1283 下的 eg2。
gdb 可以讓您通過使用 shell 命令在不退出調(diào)試環(huán)境的情況下運(yùn)行 shell 命令,調(diào)用形式是 'shell [commandline]',這有助于在調(diào)試時(shí)更改源代碼。
最后,在程序運(yùn)行時(shí),可以使用 'set ' 命令修改變量的值。在 gdb 下再次運(yùn)行 eg1,使用命令 'break 7 if diff==0' 在第 7 行(將在此處計(jì)算結(jié)果)設(shè)置條件斷點(diǎn),然后運(yùn)行程序。當(dāng) gdb 中斷執(zhí)行時(shí),可以將 "diff" 設(shè)置成非零值,使程序繼續(xù)運(yùn)行直至結(jié)束:
Breakpoint 1, wib (no1=8, no2=8) at eg1.c:77 result = no1 / diff;(gdb) print diff$1 = 0(gdb) set diff=1(gdb) continueContinuing.0 wibed by 16 equals 10Program exited normally. |
GNU 調(diào)試器是所有程序員工具庫中的一個(gè)功能非常強(qiáng)大的工具。在本文中,我只介紹了 gdb 的一小部分功能。要了解更多知識(shí),建議您閱讀 GNU 調(diào)試器手冊。
- 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文.
- GNU 調(diào)試器手冊
- 調(diào)試會(huì)話示例的 源代碼。
- 連接示例的 源代碼。