contributed by < hsuedw
>
如果你寫的程式用的是 C, objective C, C++, Fortran, Pascal, Ada, … 等等語言, 而且採用的編譯器 來自 gnu, 就可以拿 gdb 來除錯。
gdb
的除錯環境在命令列的 shell 提示符號下,執行 gdb
就可以進入 gdb
的除錯環境。
gdb
的除錯環境在 gdb
的提示符號下輸入 q
或 quit
就可以離開 gdb
的除錯環境。
gdb
的基本操作在接下來的章節中,以 factorial_buggy.c
學習 gdb
的基本操作
factorial_buggy
會要求使用者輸入一個正整數,然後算出該正整數的階乘。也就是說,如果使用者輸入 4
, factorial_buggy
應該會印出 4! = 24
。將 factorial_buggy.c
編譯並執行後,卻得到下列結果。可見,目前這版程式有問題。
在開始之前有一件事要注意。gdb
只能對可執行檔 (executable file) 進行除錯。它無法對原始碼 (source code) 進行除錯。
還有一件事要注意的是,最好可以在編譯程式碼時,加入 -g
參數。這樣我們在用 gdb
除錯時可以有更完整的資訊
$ man gcc
:
-g Produce debugging information in the operating system's native format
(stabs, COFF, XCOFF, or DWARF). GDB can work with this debugging
information.On most systems that use stabs format, -g enables use of extra debugging
information that only GDB can use; this extra information makes debugging
work better in GDB but probably makes other debuggers crash or refuse to
read the program. If you want to control for certain whether to generate
the extra information, use -gstabs+, -gstabs, -gxcoff+, -gxcoff, or -gvms
(see below).
:
為了方便起見,我們寫了一個簡單的 Makefile
來協助我們編譯程式。
首先,在 shell 提示符號下執行 gdb ./factorial_buggy
進入 gdb
的執行環境,並開始對 factorial_buggy
進行除錯。
gdb
中執行程式在 gdb
的提示符號下執行 r
或 run
就可以執行我們想要除錯的程式。
執行 r
指令後,我們想要除錯的程式便開始執行。然後, gdb
會停在 factorial_buggy
所印出的提示訊息 Enter a postitive integer:
。接著,在此提示訊息後輸入 4
。 果然,4!
不是 24
,而是一個奇怪的數字。
做到這裡,factorial_buggy
已經執行結束。我們仍然無法得知到底是哪裡出了問題。所以,我們必須重新執行程式。
breakpoint 的功能是讓 gdb
執行到某一行程式時,可以暫停執行,讓我們可以觀察當時程式的狀態。例如,查看變數內的值,或是記憶體中的內容。
因為我們目前還不知道程式到的是哪裡有問題,所以我們把 breakpoing 先設定在程式的開頭。從程式的開頭開始追蹤程式並除錯。
在 gdb
提示符號下輸入 break main
或 b main
。
至此,我們在程式的一開始設定了一個中斷點 Breakpoint 1
。由 gdb
顯示的訊息可以看出,這個 breakpoint 的位置對應到原始碼的第 4 行。
然後,在 gdb
提示符號下輸入 r
或 run
重新開始執行 factorial_buggy
。
果然, gdb
停在第 4 行。也就是我們剛剛設定的 Breakpoint 1
。
gdb
中印出程式碼這時,我們可以在 gdb
提示符號下輸入 l
或 list
印出程式碼。這時,gdb
會以我們停在的那一行為中心,上下展開一個範圍,並印出這個範圍的程式碼。在執行一次 l
則會印出下一組程式碼。我們可以一直執行 l
直到檔案末尾為止。
此時若再執行一次 l
,則 gdb
會顯示 out of range
的訊息。
沒關係,在 l
後面加上行號就可以在印出程式碼了。
接下來用 next
(可簡寫為 n
) 做單步執行。
可以觀察到,執行 next
後,程式停在第 7 行。但第 7 行還沒有執行。也就是 Enter a postitive integer:
這行提示訊息還沒有被印出來。再執行一次 next
,同樣地,程式停在地 8 行,但也還沒執行。雖然此時第 7 行已被執行,但我們還沒有看到提示訊息被印出來。
再執行一次 next
,提示訊息 Enter a postitive integer:
就被印出來了。這時,factorial_buggy
正等著我們輸入一個正整數。
這時我們輸入 4
,然後程式就進入了第 11~12 行的 for
迴圈。然後,我們接連執行 next
幾次之後,for
迴圈結束。程式執行到了第 13 行。算一下 for
迴圈共執行了 4 次。也就是說變數 i
由初值 1
,每執行一次 for
迴圈,變數 i
遞增 1
。一路遞增到變數 i
的值為 5,在第 5 次檢查測試條件時失敗 (5 <= 4
為 false)。然後離開 for
迴圈。所以,for
迴圈的執行看起來沒有問題。
再執行一次 next
。我們可以看到 factorial_buggy
印出了 4! = -173568
。最後,停在第 15 行。
再執行幾次 next
。程式又再一次的結束。
可是,我們還是不知道到底錯誤在哪裡?
我們再重新執行一次 factorial_buggy
。這次我們要觀察在程式執行的過程中,變數內容的的變化。
這次我們還是一樣想要知道 4!
是多少。所以,我們仍然輸入 4
。然後,程式就進入 for
迴圈。
此時,用 p
或 print
印出變數 num
的內容。結果如我們所預期的,就是我們剛剛輸入的 4
。
在 factorial_buggy.c
中,我們使用了三個變數,num
, factorial
以及 i
。 所以,也把另外兩個印出來看看。
這是 for 迴圈的第一個 iteration。很明顯地, factorial
這個變數的內容怪怪的。再看一下程式碼
這次我們注意到了第 10 行。變數 factorial
在第 10 行被定義之後,並沒有被指定初值。接著就進入 for
迴圈,不斷重複運算第 12 行,以計算 4!
。
至此,我們已經知道問題出在哪裡了。在計算 4!
前,沒有對變數 factorial
指定適當的初始值。所以變數 factorial
被定義後,其內容是 garbage。
gdb
中修改變數內容我們可以在 gdb
中直接修改變數內容來確認我們的想法是否正確。
這次在程式第 9 行再設定一個 breakpoint。然後重新執行程式。
這次設定 breakpoint 的指令跟上次不大一樣。我們指定了檔名與行號。這樣可以讓 gdb
明確地知道我們想把 breakpoint 設在哪裡。這個用法在程式由多個 source file 編譯而成時很好用。
此時,程式停留在 Breakpoint 1
,也就是第 4 行。我們可以執行 c
或 cont
直接執行到 Breakpoint 2
。
此時,變數 factorial
的值為 -7232
。
最棒的是,我們不需要離開 gdb
,修改程式碼,重新編譯,然後再進入 gdb
進行除錯。我們現在就可以直接用 p
或 print
指令更改變數 factorial
的初始值。所以,我們執行 p factoria = 1
將變數 factorial
初始化為 1
。
然後,我們檢查一下變數 factorial
, num
以及 i
的值是否分別為 1
, 4
與 1
。
或者,我們可以用 info locals
指令印出目前所有的變數內容。
我們在執行一次 iteration,確認是否所有的變數內容都如我們所預期。
變數 factorial
的值的確變為 2
。所以我們可以直接用 c
或 cont
命令一路直行到最後。
而經過修改後的程式也正確地算出 4! = 24
。這也驗證了我們當初的想法是正確的。
我們可以用 i b
或 info break
列出目前所有的 breakpoint
請注意! 我們沒有離開 gdb
。
接下來我們跳到另一個 console ,用編輯器開啟 factorial_buggy.c
。修改第 10 行,在定義變數 factorial
的同時,將它初始化為 1
。
然後回到我們剛剛用 gdb
進行除錯的 console。在 gdb
的提示符號下執行 make factorial
。我們的程式就會被重新編譯了。
接著,在 gdb
的提示符號下執行 disable
關閉所有的 breakpoint。
最後,在 gdb
的提示符號下執行 r
確認修改過並重新編譯過的程式是否可以正常運作。
看起來我們已經把 bug 解決了。
gdb
指令列表