學(xué)習(xí)使用 GDB 調(diào)試代碼
使用 GNU 調(diào)試器來解決你的代碼問題。
GNU 調(diào)試器常以它的命令 gdb 稱呼它,它是一個交互式的控制臺,可以幫助你瀏覽源代碼、分析執(zhí)行的內(nèi)容,其本質(zhì)上是對錯誤的應(yīng)用程序中出現(xiàn)的問題進(jìn)行逆向工程。
故障排除的麻煩在于它很復(fù)雜。GNU 調(diào)試器 并不是一個特別復(fù)雜的應(yīng)用程序,但如果你不知道從哪里開始,甚至不知道何時和為何你可能需要求助于 GDB 來進(jìn)行故障排除,那么它可能會讓人不知所措。如果你一直使用 print、echo 或 printf 語句來調(diào)試你的代碼,當(dāng)你開始思考是不是還有更強(qiáng)大的東西時,那么本教程就是為你準(zhǔn)備的。
有錯誤的代碼
要開始使用 GDB,你需要一些代碼。這里有一個用 C++ 寫的示例應(yīng)用程序(如果你一般不使用 C++ 編寫程序也沒關(guān)系,在所有語言中原理都是一樣的),其來源于 猜謎游戲系列 中的一個例子。
#include <iostream>#include <stdlib.h> //srand#include <stdio.h> //printfusing namespace std;int main () {srand (time(NULL));int alpha = rand() % 8;cout << "Hello world." << endl;int beta = 2;printf("alpha is set to is %s\n", alpha);printf("kiwi is set to is %s\n", beta);return 0;} // main
這個代碼示例中有一個 bug,但它確實(shí)可以編譯(至少在 GCC 5 的時候)。如果你熟悉 C++,你可能已經(jīng)看到了,但這是一個簡單的問題,可以幫助新的 GDB 用戶了解調(diào)試過程。編譯并運(yùn)行它就可以看到錯誤:
$ g++ -o buggy example.cpp$ ./buggyHello world.Segmentation fault
排除段故障
從這個輸出中,你可以推測變量 alpha 的設(shè)置是正確的,因?yàn)榉駝t的話,你就不會看到它后面的那行代碼執(zhí)行。當(dāng)然,這并不總是正確的,但這是一個很好的工作理論,如果你使用 printf 作為日志和調(diào)試器,基本上也會得出同樣的結(jié)論。從這里,你可以假設(shè) bug 在于成功打印的那一行之后的某行。然而,不清楚錯誤是在下一行還是在幾行之后。
GNU 調(diào)試器是一個交互式的故障排除工具,所以你可以使用 gdb 命令來運(yùn)行錯誤的代碼。為了得到更好的結(jié)果,你應(yīng)該從包含有調(diào)試符號的源代碼中重新編譯你的錯誤應(yīng)用程序。首先,看看 GDB 在不重新編譯的情況下能提供哪些信息:
$ gdb ./buggyReading symbols from ./buggy...done.(gdb) startTemporary breakpoint 1 at 0x400a44Starting program: /home/seth/demo/buggyTemporary breakpoint 1, 0x0000000000400a44 in main ()(gdb)
當(dāng)你以一個二進(jìn)制可執(zhí)行文件作為參數(shù)啟動 GDB 時,GDB 會加載該應(yīng)用程序,然后等待你的指令。因?yàn)檫@是你第一次在這個可執(zhí)行文件上運(yùn)行  GDB,所以嘗試重復(fù)這個錯誤是有意義的,希望 GDB 能夠提供進(jìn)一步的見解。很直觀,GDB 用來啟動它所加載的應(yīng)用程序的命令就是 start。默認(rèn)情況下,GDB 內(nèi)置了一個斷點(diǎn),所以當(dāng)它遇到你的應(yīng)用程序的 main 函數(shù)時,它會暫停執(zhí)行。要讓 GDB 繼續(xù)執(zhí)行,使用命令 continue:
(gdb) continueContinuing.Hello world.Program received signal SIGSEGV, Segmentation fault.0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6(gdb)
毫不意外:應(yīng)用程序在打印 “Hello world” 后不久就崩潰了,但 GDB 可以提供崩潰發(fā)生時正在發(fā)生的函數(shù)調(diào)用。這有可能就足夠你找到導(dǎo)致崩潰的 bug,但為了更好地了解 GDB 的功能和一般的調(diào)試過程,想象一下,如果問題還沒有變得清晰,你想更深入地挖掘這段代碼發(fā)生了什么。
用調(diào)試符號編譯代碼
要充分利用 GDB,你需要將調(diào)試符號編譯到你的可執(zhí)行文件中。你可以用 GCC 中的 -g 選項來生成這個符號:
$ g++ -o debuggy example.cpp$ ./debuggyHello world.Segmentation fault
將調(diào)試符號編譯到可執(zhí)行文件中的結(jié)果是得到一個大得多的文件,所以通常不會分發(fā)它們,以增加便利性。然而,如果你正在調(diào)試開源代碼,那么用調(diào)試符號重新編譯測試是有意義的:
$ ls -l *buggy* *cpp-rw-r--r-- 310 Feb 19 08:30 debug.cpp-rwxr-xr-x 11624 Feb 19 10:27 buggy*-rwxr-xr-x 22952 Feb 19 10:53 debuggy*
用 GDB 調(diào)試
加載新的可執(zhí)行文件(本例中為 debuggy)以啟動 GDB:
$ gdb ./debuggyReading symbols from ./debuggy...done.(gdb) startTemporary breakpoint 1 at 0x400a44Starting program: /home/seth/demo/debuggyTemporary breakpoint 1, 0x0000000000400a44 in main ()(gdb)
如前所述,使用 start 命令進(jìn)行:
(gdb) startTemporary breakpoint 1 at 0x400a48: file debug.cpp, line 9.Starting program: /home/sek/demo/debuggyTemporary breakpoint 1, main () at debug.cpp:99 srand (time(NULL));(gdb)
這一次,自動的 main 斷點(diǎn)可以指明 GDB 暫停的行號和該行包含的代碼。你可以用 continue 恢復(fù)正常操作,但你已經(jīng)知道應(yīng)用程序在完成之前就會崩潰,因此,你可以使用 next 關(guān)鍵字逐行步進(jìn)檢查你的代碼:
(gdb) next10 int alpha = rand() % 8;(gdb) next11 cout << "Hello world." << endl;(gdb) nextHello world.12 int beta = 2;(gdb) next14 printf("alpha is set to is %s\n", alpha);(gdb) nextProgram received signal SIGSEGV, Segmentation fault.0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6(gdb)
從這個過程可以確認(rèn),崩潰不是發(fā)生在設(shè)置 beta 變量的時候,而是執(zhí)行 printf 行的時候。這個 bug 在本文中已經(jīng)暴露了好幾次(破壞者:向 printf 提供了錯誤的數(shù)據(jù)類型),但暫時假設(shè)解決方案仍然不明確,需要進(jìn)一步調(diào)查。
設(shè)置斷點(diǎn)
一旦你的代碼被加載到 GDB 中,你就可以向 GDB 詢問到目前為止代碼所產(chǎn)生的數(shù)據(jù)。要嘗試數(shù)據(jù)自省,通過再次發(fā)出 start 命令來重新啟動你的應(yīng)用程序,然后進(jìn)行到第 11 行。一個快速到達(dá) 11 行的簡單方法是設(shè)置一個尋找特定行號的斷點(diǎn):
(gdb) startThe program being debugged has been started already.Start it from the beginning? (y or n) yTemporary breakpoint 2 at 0x400a48: file debug.cpp, line 9.Starting program: /home/sek/demo/debuggyTemporary breakpoint 2, main () at debug.cpp:99 srand (time(NULL));(gdb) break 11Breakpoint 3 at 0x400a74: file debug.cpp, line 11.
建立斷點(diǎn)后,用 continue 繼續(xù)執(zhí)行:
(gdb) continueContinuing.Breakpoint 3, main () at debug.cpp:1111 cout << "Hello world." << endl;(gdb)
現(xiàn)在暫停在第 11 行,就在 alpha 變量被設(shè)置之后,以及 beta 被設(shè)置之前。
用 GDB 進(jìn)行變量自省
要查看一個變量的值,使用 print 命令。在這個示例代碼中,alpha 的值是隨機(jī)的,所以你的實(shí)際結(jié)果可能與我的不同:
(gdb) print alpha$1 = 3(gdb)
當(dāng)然,你無法看到一個尚未建立的變量的值:
(gdb) print beta$2 = 0
使用流程控制
要繼續(xù)進(jìn)行,你可以步進(jìn)代碼行來到達(dá)將 beta 設(shè)置為一個值的位置:
(gdb) nextHello world.12 int beta = 2;(gdb) next14 printf("alpha is set to is %s\n", alpha);(gdb) print beta$3 = 2
另外,你也可以設(shè)置一個觀察點(diǎn),它就像斷點(diǎn)一樣,是一種控制 GDB 執(zhí)行代碼流程的方法。在這種情況下,你知道 beta 變量應(yīng)該設(shè)置為 2,所以你可以設(shè)置一個觀察點(diǎn),當(dāng) beta 的值發(fā)生變化時提醒你:
(gdb) watch beta > 0Hardware watchpoint 5: beta > 0(gdb) continueContinuing.Breakpoint 3, main () at debug.cpp:1111 cout << "Hello world." << endl;(gdb) continueContinuing.Hello world.Hardware watchpoint 5: beta > 0Old value = falseNew value = truemain () at debug.cpp:1414 printf("alpha is set to is %s\n", alpha);(gdb)
你可以用 next 手動步進(jìn)完成代碼的執(zhí)行,或者你可以用斷點(diǎn)、觀察點(diǎn)和捕捉點(diǎn)來控制代碼的執(zhí)行。
用 GDB 分析數(shù)據(jù)
你可以以不同格式查看數(shù)據(jù)。例如,以八進(jìn)制值查看 beta 的值:
(gdb) print /o beta$4 = 02
要查看其在內(nèi)存中的地址:
(gdb) print /o beta$5 = 0x2
你也可以看到一個變量的數(shù)據(jù)類型:
(gdb) whatis betatype = int
用 GDB 解決錯誤
這種自省不僅能讓你更好地了解什么代碼正在執(zhí)行,還能讓你了解它是如何執(zhí)行的。在這個例子中,對變量運(yùn)行的 whatis 命令給了你一個線索,即你的 alpha 和 beta 變量是整數(shù),這可能會喚起你對 printf 語法的記憶,使你意識到在你的 printf 語句中,你必須使用 %d 來代替 %s。做了這個改變,就可以讓應(yīng)用程序按預(yù)期運(yùn)行,沒有更明顯的錯誤存在。
當(dāng)代碼編譯后發(fā)現(xiàn)有 bug 存在時,特別令人沮喪,但最棘手的 bug 就是這樣,如果它們很容易被發(fā)現(xiàn),那它們就不是 bug 了。使用 GDB 是獵取并消除它們的一種方法。
下載我們的速查表
生活的真相就是這樣,即使是最基本的編程,代碼也會有 bug。并不是所有的錯誤都會導(dǎo)致應(yīng)用程序無法運(yùn)行(甚至無法編譯),也不是所有的錯誤都是由錯誤的代碼引起的。有時,bug 是基于一個特別有創(chuàng)意的用戶所做的意外的選擇組合而間歇性發(fā)生的。有時,程序員從他們自己的代碼中使用的庫中繼承了 bug。無論原因是什么,bug 基本上無處不在,程序員的工作就是發(fā)現(xiàn)并消除它們。
GNU 調(diào)試器是一個尋找 bug 的有用工具。你可以用它做的事情比我在本文中演示的要多得多。你可以通過 GNU Info 閱讀器來了解它的許多功能:
$ info gdb
無論你是剛開始學(xué)習(xí) GDB 還是專業(yè)人員的,提醒一下你有哪些命令是可用的,以及這些命令的語法是什么,都是很有幫助的。















 
 
 











 
 
 
 