Linux 包含了一個叫 gdb 的 GNU 調試程式. gdb 是一個用來調試 C 和 C++ 程式的強力調試器. 它使你能在程式運作時觀察程式的内部結構和記憶體的使用情況. 以下是 gdb 所提供的一些功能:
· 它使你能監視你程式中變量的值.
· 它使你能設定斷點以使程式在指定的代碼行上停止執行.
· 它使你能一行行的執行你的代碼.
在指令行上鍵入 gdb 并按Enter鍵就可以運作 gdb 了, 如果一切正常的話, gdb 将被啟動并且你将在螢幕上看到類似的内容:
GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc. (gdb) |
當你啟動 gdb 後, 你能在指令行上指定很多的選項. 你也可以以下面的方式來運作 gdb :
gdb
當你用這種方式運作 gdb , 你能直接指定想要調試的程式. 這将告訴gdb 裝入名為 fname 的可執行檔案. 你也可以用 gdb 去檢查一個因程式異常終止而産生的 core 檔案, 或者與一個正在運作的程式相連. 你可以參考 gdb 指南頁或在指令行上鍵入 gdb -h 得到一個有關這些選項的說明的簡單清單.
為調試編譯代碼(Compiling Code for Debugging)
為了使 gdb 正常工作, 你必須使你的程式在編譯時包含調試資訊. 調試資訊包含你程式裡的每個變量的類型和在可執行檔案裡的位址映射以及源代碼的行号. gdb 利用這些資訊使源代碼和機器碼相關聯. 在編譯時用 -g 選項打開調試選項.
gdb 基本指令
gdb 支援很多的指令使你能實作不同的功能. 這些指令從簡單的檔案裝入到允許你檢查所調用的堆棧内容的複雜指令, 表1列出了你在用 gdb 調試時會用到的一些指令.
表1. 基本 gdb 指令.
命 令 | 描 述 |
file | 裝入想要調試的可執行檔案 |
kill | 終止正在調試的程式 |
list | 列出産生執行檔案的源代碼的一部分 |
next | 執行一行源代碼但不進入函數内部 |
step | 執行一行源代碼而且進入函數内部 |
run | 執行目前被調試的程式 |
quit | 終止 gdb |
watch | 使你能監視一個變量的值而不管它何時被改變 |
break | 在代碼裡設定斷點, 這将使程式執行到這裡時被挂起 |
make | 使你能不退出 gdb 就可以重新産生可執行檔案 |
shell | 使你能不離開 gdb 就執行 UNIX shell 指令 |
gdb 支援很多與 UNIX shell 程式一樣的指令編輯特征. 你能象在 bash 或 tcsh裡那樣按 Tab 鍵讓 gdb 幫你補齊一個惟一的指令, 如果不惟一的話 gdb 會列出所有比對的指令. 你也能用光标鍵上下翻動曆史指令.
gdb 應用舉例
本節用一個執行個體教你一步步的用 gdb 調試程式. 被調試的程式相當的簡單, 但它展示了 gdb 的典型應用.
下面列出了将被調試的程式. 這個程式被稱為 greeting , 它顯示一個簡單的問候, 再用反序将它列出.
#include <stdio.h> main () { char my_string[] = "hello there"; my_print (my_string); my_print2 (my_string); } void my_print (char *string) { printf ("The string is %s/n", string); } void my_print2 (char *string) { char *string2; int size, i; size = strlen (string); string2 = (char *) malloc (size + 1); for (i = 0; i < size; i++) string2[size - i] = string[i]; string2[size+1] = `/0'; printf ("The string printed backward is %s/n", string2); } |
用下面的指令編譯它:
gcc -o test test.c
這個程式執行時顯示如下結果:
The string is hello there
The string printed backward is
輸出的第一行是正确的, 但第二行列印出的東西并不是我們所期望的. 我們所設想的輸出應該是:
The string printed backward is ereht olleh
由于某些原因, my_print2 函數沒有正常工作. 讓我們用 gdb 看看問題究竟出在哪兒, 先鍵入如下指令:
gdb greeting
注意: 記得在編譯 greeting 程式時把調試選項打開.
如果你在輸入指令時忘了把要調試的程式作為參數傳給 gdb , 你可以在 gdb 提示符下用 file 指令來載入它:
(gdb) file greeting
這個指令将載入 greeting 可執行檔案就象你在 gdb 指令行裡裝入它一樣.
這時你能用 gdb 的 run 指令來運作 greeting 了. 當它在 gdb 裡被運作後結果大約會象這樣:
(gdb) run Starting program: /root/greeting The string is hello there The string printed backward is Program exited with code 041 |
這個輸出和在 gdb 外面運作的結果一樣. 問題是, 為什麼反序列印沒有工作? 為了找出症結所在, 我們可以在 my_print2 函數的 for 語句後設一個斷點, 具體的做法是在 gdb 提示符下鍵入 list 指令三次, 列出源代碼:
(gdb) list
(gdb) list
(gdb) list
技巧: 在 gdb 提示符下按回車健将重複上一個指令.
第一次鍵入 list 指令的輸出如下:
1 #include <stdio.h> 2 3 main () 4 { 5 char my_string[] = "hello there"; 6 7 my_print (my_string); 8 my_print2 (my_string); 9 } 10 |
如果按下回車, gdb 将再執行一次 list 指令, 給出下列輸出:
11 my_print (char *string) 12 { 13 printf ("The string is %s/n", string); 14 } 15 16 my_print2 (char *string) 17 { 18 char *string2; 19 int size, i; 20 |
再按一次回車将列出 greeting 程式的剩餘部分:
21 size = strlen (string); 22 string2 = (char *) malloc (size + 1); 23 for (i = 0; i < size; i++) 24 string2[size - i] = string[i]; 25 string2[size+1] = `/0'; 26 printf ("The string printed backward is %s/n", string2); 27 } |
根據列出的源程式, 你能看到要設斷點的地方在第24行, 在 gdb 指令行提示符下鍵入如下指令設定斷點:
(gdb) break 24
gdb 将作出如下的響應:
Breakpoint 1 at 0x139: file greeting.c, line 24
(gdb)
現在再鍵入 run 指令, 将産生如下的輸出:
Starting program: /root/greeting The string is hello there Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24 24 string2[size-i]=string[i] |
你能通過設定一個觀察 string2[size - i] 變量的值的觀察點來看出錯誤是怎樣産生的, 做法是鍵入:
(gdb) watch string2[size - i]
gdb 将作出如下回應:
Watchpoint 2: string2[size - i]
現在可以用 next 指令來一步步的執行 for 循環了:
(gdb) next
經過第一次循環後, gdb 告訴我們 string2[size - i] 的值是 `h`. gdb 用如下的顯示來告訴你這個資訊:
Watchpoint 2, string2[size - i] Old value = 0 `/000' New value = 104 `h' my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23 23 for (i=0; i<size; i++) |
這個值正是期望的. 後來的數次循環的結果都是正确的. 當 i=10 時, 表達式 string2[size - i] 的值等于 `e`, size - i 的值等于 1, 最後一個字元已經拷到新串裡了.
如果你再把循環執行下去, 你會看到已經沒有值配置設定給 string2[0] 了, 而它是新串的第一個字元, 因為 malloc 函數在配置設定記憶體時把它們初始化為空(null)字元. 是以 string2 的第一個字元是空字元. 這解釋了為什麼在列印 string2 時沒有任何輸出了.
現在找出了問題出在哪裡, 修正這個錯誤是很容易的. 你得把代碼裡寫入 string2 的第一個字元的的偏移量改為 size - 1 而不是 size. 這是因為 string2 的大小為 12, 但起始偏移量是 0, 串内的字元從偏移量 0 到 偏移量 10, 偏移量 11 為空字元保留.
為了使代碼正常工作有很多種修改辦法. 一種是另設一個比串的實際大小小 1 的變量. 這是這種解決辦法的代碼:
#include <stdio.h> main () { char my_string[] = "hello there"; my_print (my_string); my_print2 (my_string); } my_print (char *string) { printf ("The string is %s/n", string); } my_print2 (char *string) { char *string2; int size, size2, i; size = strlen (string); size2 = size -1; string2 = (char *) malloc (size + 1); for (i = 0; i < size; i++) string2[size2 - i] = string[i]; string2[size] = `/0'; printf ("The string printed backward is %s/n", string2); } |
Linux中包含有一個很有用的調試工具--gdb(GNU Debuger),它可以用來調試C和C++程式,功能不亞于Windows下的許多圖形界面的調試工具。
和所有常用的調試工具一樣,gdb提供了以下功能:
# 監視程式中變量的值
# 在程式中設定斷點
# 程式的單步執行
在使用gdb前,必須先載入可執行檔案,因為要進行調試,檔案中就必須包含調試資訊,是以在用gcc或cc編譯時就需要用-g參數來打開程式的調試選項。
調試開始時,必須先載入要進行調試的程式,可以用以下兩種方式:
* 在啟動gdb後執行以下指令:
file 可執行檔案路徑
* 在gdb啟動時就載入程式:
gdb 可執行檔案路徑
載入程式後,接下來就是要進行斷點的設定,要監視的變量的添加等工作,下面對在這個過程中常會用到的指令逐一進行介紹:
* list:顯示程式中的代碼,常用使用格式有:
list
輸出從上次調用list指令開始往後的10行程式代碼。
list -
輸出從上次調用list指令開始往前的10行程式代碼。
list n
輸出第n行附近的10行程式代碼。
list function
輸出函數function前後的10行程式代碼。
* forward/search:從目前行向後查找比對某個字元串的程式行。使用格式:
forward/search 字元串
查找到的行号将儲存在$_變量中,可以用print $_指令來檢視。
* reverse-search:和forward/search相反,向前查找字元串。使用格式同上。
* break:在程式中設定斷點,當程式運作到指定行上時,會暫停執行。使用格式:
break 要設定斷點的行号
* tbreak:設定臨時斷點,在設定之後隻起作用一次。使用格式:
tbreak 要設定臨時斷點的行号
* clear:和break相反,clear用于清除斷點。使用格式:
clear 要清除的斷點所在的行号
* run:啟動程式,在run後面帶上參數可以傳遞給正在調試的程式。
* awatch:用來增加一個觀察點(add watch),使用格式:
awatch 變量或表達式
當表達式的值發生改變或表達式的值被讀取時,程式就會停止運作。
* watch:與awatch類似用來設定觀察點,但程式隻有當表達式的值發生改變時才會停止運作。使用格 式:
watch 變量或表達式
需要注意的是,awatch和watch都必須在程式運作的過程中設定觀察點,即可運作run之後才能設定。
* commands:設定在遇到斷點後執行特定的指令。使用格式有:
commands
設定遇到最後一個遇到的斷點時要執行的指令
commands n
設定遇到斷點号n時要執行的指令
注意,commands後面跟的是斷點号,而不是斷點所在的行号。
在輸入指令後,就可以輸入遇到斷點後要執行的指令,每行一條指令,在輸入最後一條指令後輸入end就可以結束輸入。
* delete:清除斷點或自動顯示的表達式。使用格式:
delete 斷點号
* disable:讓指定斷點失效。使用格式:
disable 斷點号清單
斷點号之間用空格間隔開。
* enable:和disable相反,恢複失效的斷點。使用格式:
enable 斷點編号清單
* ignore:忽略斷點。使用格式:
ignore 斷點号 忽略次數
* condition:設定斷點在一定條件下才能生效。使用格式:
condition 斷點号 條件表達式
* cont/continue:使程式在暫停在斷點之後繼續運作。使用格式:
cont
跳過目前斷點繼續運作。
cont n
跳過n次斷點,繼續運作。
當n為1時,cont 1即為cont。
* jump:讓程式跳到指定行開始調試。使用格式:
jump 行号
* next:繼續執行語句,但是跳過子程式的調用。使用格式:
next
執行一條語句
next n
執行n條語句
* nexti:單步執行語句,但和next不同的是,它會跟蹤到子程式的内部,但不列印出子程式内部的語句。使用格式同上。
* step:與next類似,但是它會跟蹤到子程式的内部,而且會顯示子程式内部的執行情況。使用格式同上。
* stepi:與step類似,但是比step更詳細,是nexti和step的結合。使用格式同上。
* whatis:顯示某個變量或表達式的資料類型。使用格式:
whatis 變量或表達式
* ptype:和whatis類似,用于顯示資料類型,但是它還可以顯示typedef定義的類型等。使用格式:
ptype 變量或表達式
* set:設定程式中變量的值。使用格式:
set 變量=表達式
set 變量:=表達式
* display:增加要顯示值的表達式。使用格式:
display 表達式
* info display:顯示目前所有的要顯示值的表達式。
* delete display/undisplay:删除要顯示值的表達式。使用格式:
delete display/undisplay 表達式編号
* disable display:暫時不顯示一個要表達式的值。使用格式:
disable display 表達式編号
* enable display:與disable display相反,使用表達式恢複顯示。使用格式:
enable display 表達式編号
* print:列印變量或表達式的值。使用格式:
print 變量或表達式
表達式中有兩個符号有特殊含義:$和$$。
$表示給定序号的前一個序号,$$表示給定序号的前兩個序号。
如果$和$$後面不帶數字,則給定序号為目前序号。
* backtrace:列印指定個數的棧幀(stack frame)。使用格式:
backtrace 棧幀個數
* frame:列印棧幀。使用格式:
frame 棧幀号
* info frame:顯示目前棧幀的詳細資訊。
* select-frame:選擇棧幀,選擇後可以用info frame來顯示棧幀資訊。使用格式:
select-frame 棧幀号
* kill:結束目前程式的調試。
* quit:退出gdb。