您好,登錄后才能下訂單哦!
這篇文章主要講解了“用 GDB 調試代碼的方法”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“用 GDB 調試代碼的方法”吧!
GNU 調試器常以它的命令 gdb
稱呼它,它是一個交互式的控制臺,可以幫助你瀏覽源代碼、分析執行的內容,其本質上是對錯誤的應用程序中出現的問題進行逆向工程。
故障排除的麻煩在于它很復雜。GNU 調試器 并不是一個特別復雜的應用程序,但如果你不知道從哪里開始,甚至不知道何時和為何你可能需要求助于 GDB 來進行故障排除,那么它可能會讓人不知所措。如果你一直使用 print
、echo
或 printf 語句來調試你的代碼,當你開始思考是不是還有更強大的東西時,那么本教程就是為你準備的。
要開始使用 GDB,你需要一些代碼。這里有一個用 C++ 寫的示例應用程序(如果你一般不使用 C++ 編寫程序也沒關系,在所有語言中原理都是一樣的),其來源于 猜謎游戲系列 中的一個例子。
#include <iostream>#include <stdlib.h> //srand#include <stdio.h> //printf using 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,但它確實可以編譯(至少在 GCC 5 的時候)。如果你熟悉 C++,你可能已經看到了,但這是一個簡單的問題,可以幫助新的 GDB 用戶了解調試過程。編譯并運行它就可以看到錯誤:
$ g++ -o buggy example.cpp$ ./buggyHello world.Segmentation fault
從這個輸出中,你可以推測變量 alpha
的設置是正確的,因為否則的話,你就不會看到它后面的那行代碼執行。當然,這并不總是正確的,但這是一個很好的工作理論,如果你使用 printf
作為日志和調試器,基本上也會得出同樣的結論。從這里,你可以假設 bug 在于成功打印的那一行之后的某行。然而,不清楚錯誤是在下一行還是在幾行之后。
GNU 調試器是一個交互式的故障排除工具,所以你可以使用 gdb
命令來運行錯誤的代碼。為了得到更好的結果,你應該從包含有調試符號的源代碼中重新編譯你的錯誤應用程序。首先,看看 GDB 在不重新編譯的情況下能提供哪些信息:
$ gdb ./buggyReading symbols from ./buggy...done.(gdb) startTemporary breakpoint 1 at 0x400a44Starting program: /home/seth/demo/buggy Temporary breakpoint 1, 0x0000000000400a44 in main ()(gdb)
當你以一個二進制可執行文件作為參數啟動 GDB 時,GDB 會加載該應用程序,然后等待你的指令。因為這是你第一次在這個可執行文件上運行 GDB,所以嘗試重復這個錯誤是有意義的,希望 GDB 能夠提供進一步的見解。很直觀,GDB 用來啟動它所加載的應用程序的命令就是 start
。默認情況下,GDB 內置了一個斷點,所以當它遇到你的應用程序的 main
函數時,它會暫停執行。要讓 GDB 繼續執行,使用命令 continue
:
(gdb) continueContinuing.Hello world. Program received signal SIGSEGV, Segmentation fault.0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6(gdb)
毫不意外:應用程序在打印 “Hello world” 后不久就崩潰了,但 GDB 可以提供崩潰發生時正在發生的函數調用。這有可能就足夠你找到導致崩潰的 bug,但為了更好地了解 GDB 的功能和一般的調試過程,想象一下,如果問題還沒有變得清晰,你想更深入地挖掘這段代碼發生了什么。
要充分利用 GDB,你需要將調試符號編譯到你的可執行文件中。你可以用 GCC 中的 -g
選項來生成這個符號:
$ g++ -o debuggy example.cpp$ ./debuggyHello world.Segmentation fault
將調試符號編譯到可執行文件中的結果是得到一個大得多的文件,所以通常不會分發它們,以增加便利性。然而,如果你正在調試開源代碼,那么用調試符號重新編譯測試是有意義的:
$ 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*
加載新的可執行文件(本例中為 debuggy
)以啟動 GDB:
$ gdb ./debuggyReading symbols from ./debuggy...done.(gdb) startTemporary breakpoint 1 at 0x400a44Starting program: /home/seth/demo/debuggy Temporary breakpoint 1, 0x0000000000400a44 in main ()(gdb)
如前所述,使用 start
命令進行:
(gdb) startTemporary breakpoint 1 at 0x400a48: file debug.cpp, line 9.Starting program: /home/sek/demo/debuggy Temporary breakpoint 1, main () at debug.cpp:99 srand (time(NULL));(gdb)
這一次,自動的 main
斷點可以指明 GDB 暫停的行號和該行包含的代碼。你可以用 continue
恢復正常操作,但你已經知道應用程序在完成之前就會崩潰,因此,你可以使用 next
關鍵字逐行步進檢查你的代碼:
(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) next Program received signal SIGSEGV, Segmentation fault.0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6(gdb)
從這個過程可以確認,崩潰不是發生在設置 beta
變量的時候,而是執行 printf
行的時候。這個 bug 在本文中已經暴露了好幾次(破壞者:向 printf
提供了錯誤的數據類型),但暫時假設解決方案仍然不明確,需要進一步調查。
一旦你的代碼被加載到 GDB 中,你就可以向 GDB 詢問到目前為止代碼所產生的數據。要嘗試數據自省,通過再次發出 start
命令來重新啟動你的應用程序,然后進行到第 11 行。一個快速到達 11 行的簡單方法是設置一個尋找特定行號的斷點:
(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/debuggy Temporary breakpoint 2, main () at debug.cpp:99 srand (time(NULL));(gdb) break 11Breakpoint 3 at 0x400a74: file debug.cpp, line 11.
建立斷點后,用 continue
繼續執行:
(gdb) continueContinuing. Breakpoint 3, main () at debug.cpp:1111 cout << "Hello world." << endl;(gdb)
現在暫停在第 11 行,就在 alpha
變量被設置之后,以及 beta
被設置之前。
要查看一個變量的值,使用 print
命令。在這個示例代碼中,alpha
的值是隨機的,所以你的實際結果可能與我的不同:
(gdb) print alpha$1 = 3(gdb)
當然,你無法看到一個尚未建立的變量的值:
(gdb) print beta$2 = 0
要繼續進行,你可以步進代碼行來到達將 beta
設置為一個值的位置:
(gdb) nextHello world.12 int beta = 2;(gdb) next14 printf("alpha is set to is %s\n", alpha);(gdb) print beta$3 = 2
另外,你也可以設置一個觀察點,它就像斷點一樣,是一種控制 GDB 執行代碼流程的方法。在這種情況下,你知道 beta
變量應該設置為 2
,所以你可以設置一個觀察點,當 beta
的值發生變化時提醒你:
(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 > 0 Old value = falseNew value = truemain () at debug.cpp:1414 printf("alpha is set to is %s\n", alpha);(gdb)
你可以用 next
手動步進完成代碼的執行,或者你可以用斷點、觀察點和捕捉點來控制代碼的執行。
你可以以不同格式查看數據。例如,以八進制值查看 beta
的值:
(gdb) print /o beta$4 = 02
要查看其在內存中的地址:
(gdb) print /o beta$5 = 0x2
你也可以看到一個變量的數據類型:
(gdb) whatis betatype = int
這種自省不僅能讓你更好地了解什么代碼正在執行,還能讓你了解它是如何執行的。在這個例子中,對變量運行的 whatis
命令給了你一個線索,即你的 alpha
和 beta
變量是整數,這可能會喚起你對 printf
語法的記憶,使你意識到在你的 printf
語句中,你必須使用 %d
來代替 %s
。做了這個改變,就可以讓應用程序按預期運行,沒有更明顯的錯誤存在。
當代碼編譯后發現有 bug 存在時,特別令人沮喪,但最棘手的 bug 就是這樣,如果它們很容易被發現,那它們就不是 bug 了。使用 GDB 是獵取并消除它們的一種方法。
生活的真相就是這樣,即使是最基本的編程,代碼也會有 bug。并不是所有的錯誤都會導致應用程序無法運行(甚至無法編譯),也不是所有的錯誤都是由錯誤的代碼引起的。有時,bug 是基于一個特別有創意的用戶所做的意外的選擇組合而間歇性發生的。有時,程序員從他們自己的代碼中使用的庫中繼承了 bug。無論原因是什么,bug 基本上無處不在,程序員的工作就是發現并消除它們。
GNU 調試器是一個尋找 bug 的有用工具。你可以用它做的事情比我在本文中演示的要多得多。你可以通過 GNU Info 閱讀器來了解它的許多功能:
$ info gdb
感謝各位的閱讀,以上就是“用 GDB 調試代碼的方法”的內容了,經過本文的學習后,相信大家對用 GDB 調試代碼的方法這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。