gdb
這篇文章基本上是摘自gdb手冊,除此之外就是加了實際的代碼樣例,這樣可以更清楚的看到一些命令的執行效果。當然,這兒不會涉及到所有的gdb命令,而只是一些常用的。
我這兒使用的gdb的版本是6.8。不過因為只是一些常用命令,因此在老版本上應該也沒問題。操作系統是Windows XP。
一.概述
調試器的目的在于讓你查看程序運行時的內部狀態;或者在程序崩潰的時候,查看程序異常的原因。它們可以在一下幾個方面提供幫助:
1)啟動程序,按照你的意愿影響程序的行為
2)讓程序在特定條件發生時停止運行
3)當程序停止的時候,檢查程序正在做什么
4)改變程序的某些東西,這樣就可以運行時修正bug,然后繼續測試其他的問題。
gdb支持多種語言的調試,不過最成熟的應該就是c/c++了。這兒也以c++程序為例來說明。如果想知道其他語言的支持情況,可以看gdb手冊。
二.gdb的起停
gdb最常用的啟動方式就是:
gdb program
或者在程序異常終止的時候這樣:
gdb program core
也或者,你可以attach到一個已經運行的進程來調試它,就像這樣
gdb program PID
當然,也可以在命令行設置gdb的啟動參數,例如args:
gdb —args gcc -O2 -c foo.c
這樣就可以調試gcc,同時將參數"-O2 -c foo.c"傳給gcc。
可以用gdb -help查看gdb的所有選項。
要想中止gdb的運行,可以輸入quit或者q,也可以按ctrl-d組合鍵。
三.gdb命令
命令語法
gdb命令的形式為:command [arg1...argn]。每個命令單獨一行。行的長度沒有限制(當然,一般應該也不會有多長)。
許多gdb的命令都會有縮寫的形式,例如break可以縮寫為b。
如果直接按回車,gdb會執行剛剛被執行命令。
如果命令中有#字符,那么從#開始的字符都會被當成是注釋。這個一般用在命令文件中。后面會有介紹。
命令補全
gdb具有補全功能。功能鍵是<TAB>。例如break,在輸入bre之后按<TAB>
鍵,gdb就會補全為break。如果只輸入b,然后按<TAB>,gdb會響一聲,這說明有多個以b開始的命令。這種情況下再按一
次<TAB>,gdb就會把所有以b開始的命令輸出出來。下面是此時的屏幕截圖:

如果只是想看一下以某個(些)字符開始的命令,可以按<META>?,而不用按兩次<TAB>。在沒
有<META>鍵的電腦上,可以用<ESC>鍵代替。這個命令按起來有點麻煩,比按兩次<TAB>要麻煩多了,如果
不怕<TAB>鍵被按壞,我建議你還是按<TAB>吧。
命令補全可以用于gdb命令,gdb子命令,程序中的符號名(例如函數名等)。
在調試c++程序時,基本上肯定會遇到的問題就是重載函數。例如,在設置斷點的時候,假設有兩個名為overload的函數,這時為了區分到底是那
個函數,就需要加上函數參數類型(函數名加上參數列表作為邏輯上的一個詞)。為了使gdb能將參數列表兩邊的括號作為這個詞的一部分,需要用單引號'將函
數整個的括起來。看下圖:

獲取幫助
啟動gdb后,可以輸入命令help得到gdb的命令列表。注意的是這時輸出的每個條目都是一類命令。上個圖:

得到命令類別后,可以用命令 help class得到此類別的所有命令。上圖:

上面的圖中只顯示了部分breakpoints命令。
其他跟幫助相關的命令有:
a)help command
用于顯示命令command的幫助信息。
b)apropos args
這里的args可以使正則表達式。顯示所有匹配args的命令的簡要說明。
c)complete args
顯示所有以args開始的命令。
另外還有兩個很有用的命令: info 和 show 。
a)info
用于顯示被調試程序的信息。例如傳遞到當前函數的參數-info args;或者查看當前寄存器的值-info registers;也可以查看斷點-info breakpoints。可以用help info查看info的說明。
b)show
這個命令用于顯示gdb的信息。也就是gdb的一些屬性值。可以用help show查看幫助信息。這個命令通常應該是配合set用的(用于設定gdb的屬性)。
好了,現在對gdb應該有個大概的認識了,下面我們就要拿幾個小例子來驗證一些常用命令的效果。Let's go!
四.示例1
首先說明一點,要想高效的使用gdb的功能,需要在編譯程序的時候要加上-g選項,這個選項會把調試信息加到可執行文件中。
下面說一下示例文件,包括三個:gdb.h, gdb.cpp, test.cpp
gdb.h
#ifndef _GDB_H
#define _GDB_H 1
class gdb
{
public:
explicit gdb(int v);
void overload(int one);
void overload(int one, int two);
void catch_ex(int ex); //exception
void loop();
private:
int value;
int array[10];
};
#endif
gdb.cpp
#include "gdb.h"
#include <iostream>
using namespace std;
gdb::gdb(int v)
{
value = v;
for(int i=0; i<10; i++)
{
array[i] = i;
}
}
void gdb::overload(int one)
{
cout<<"function overload with one parameter: "<<one<<endl;
}
void gdb::overload(int one, int two)
{
cout<<"function overload with two paremeters: "<<one<<" "<<two<<endl;
}
void gdb::loop()
{
int loop_array[10];
for(int i=0; i<10; i++)
{
loop_array[i] = i;
}
int v=3;
v=3;
v=4;
}
void gdb::catch_ex(int ex)
{
int e = ex;
try
{
if(e <= 0)
{
throw e;
}
else
{
cout<<"function catch_ex: "<<ex<<endl;
}
}
catch(int x)
{
cout<<"exception: "<<x<<endl;
}
}
test.cpp
#include "gdb.h"
int main()
{
gdb g(5);
g.overload(1);
g.overload(1, 2);
g.loop();
g.catch_ex(3);
g.catch_ex(-1);
return 0;
}
好了,現在開始調試。首先啟動gdb,指定要調試的可執行文件。前面已經說過了,可以簡單地使用gdb program來啟動。或者可以首先啟動gdb,然后用file命令指定要調試的文件。下面是僅啟動gdb后的畫面:

現在用file命令指定要調試的文件:

然后就可以用 run 或者 r 命令來運行程序。在運行之前,你可能需要為程序設定一些信息,這些信息有一下四種:
1)程序參數
可以用set args命令設定程序的參數。設定完后可以用show args查看設置的是否正確。如果set args后面不帶任何參數,則向程序傳遞的參數為空。
2)環境
這兒的環境就是在系統/用戶配置文件中設置的環境變量,像HOME, PATH之類的.GDB提供了在調試的時候改變這些變量值的方式,這樣當需要的時候就不用退出gdb來重新設置.GDB提供的命令有:
a) path directory -- 將 directory 加到環境變量PATH前面. 注意 對PATH的改變只對調試的程序有效, GDB使用的PATH不會有改變.
b) show paths -- 顯示PATH的值。
c) show environment [varname] -- 顯示環境變量varname的值,如果不指定varname,則顯示所有環境變量的值。
d) set environment varname [= value] -- 設置環境變量varname的值為value。這個改變只是對調試的程序生效。如果不提供value,則將varname的值置為空。
e) unset environment varname -- 從環境中移除傳遞給程序的變量varname。
3)工作目錄
在啟動gdb調試程序的時候,被調試的程序會從gdb繼承工作目錄。當然gdb也提供了命令來修改工作目錄:
a) cd directory -- 將 directory 設為新的工作目錄。
b) pwd -- 顯示當前工作目錄。
4)標準輸入輸出
還沒找到在windows里面這個東西有啥用,現在也沒有linux可用,不好多說。有需要的自己看gdb手冊吧。我簡單抄一下手冊吧。
在gdb中,可以將run命令的輸入輸出重定向到文件或者其他終端。也可以通過tty命令設置被調試程序輸入輸出的設備。命令格式是:
tty terminal 或者 set inferior-tty terminal.
tty 就是 set inferior-tty 的別名。
咚咚咚咚,下面正式開始!
上面我們已經啟動了程序, 也知道了如何運行程序。可是如果你直接執行run命令會發現,程序直接運行結束了。如果你想在某一行或者某個函數調用的地方,或者當某個變量/表達式的值改變的時候,也或者在某些事件發生的時候--例如拋出異常、加載動態庫,或者創建子進程--的時候停止程序運行,那應該怎么辦呢?
有了gdb,一切就都好辦了:), 利用下面這三個強大的武器,你可以任意的停止程序。小心了,大家小心了,偶要祭出這三件寶物了,它們是:
斷點
斷點就是指定一個位置,使得程序運行到這個位置的時候會停下來(當然,還可以設置條件斷點,當運行到指定位置時,只有滿足了設置的條件,程序才會停下來),這樣便于觀察程序的內部狀態。斷點相關的命令主要有:
a)break location
在指定位置 location 處設置斷點,這里的 location 可以是函數名,行號,指令地址等(關于如何指定 location ,可以看這里)。
b)break
如果不指定任何參數,break會在選定的棧幀的下一條指令處設置斷點。
c)break ... if cond
設置條件斷點。每次到達斷點的時候都會對表達式 cond 求值,只有當結果為非0的時候程序才會在這個斷點停下來。
d)tbreak args
設置一個只生效一次的斷點。args跟break命令里的參數意義相同(也就是說,可以為location,為空,或者條件)。
e)hbreak args
設置硬件斷點。
f)thbreak args
設置只生效一次的硬件斷點。
g)rbreak regex
在所有匹配正則表達式 regex 的函數上設置斷點。
h)info breakpoints [n]
i)info break [n]
j)info watchpoints [n]
上面三個命令都是列出當前的斷點、觀察點和捕捉點,如果指定參數n,則僅列出第n個的信息。
來試驗一把吧。首先用gdb啟動程序,假設我們想在test.cpp的 g.overload(1) 這一行添加一個斷點,那就執行命令:b test.cpp:6,執行完后:

這時可以用info break等命令查看斷點信息:

然后我們運行程序,看看有什么效果。

看到了吧,程序停在了斷點所在的行。這時可以用where或者frame查看當前的棧幀信息,也可以用 print 查看一些變量或者表達式的信息,或者info args查看參數信息,等等。
其他的命令大家可以自己嘗試一下。
有時候我們并不確定要在哪里加斷點,例如當我們想在某個變量被改變或者被讀、被寫的時候讓程序停下來,可能由于訪問變量的地方比較多,要想每個地方都加上斷點比較麻煩,而且很可能有遺漏,這時候我們就需要依賴另一個強大的命令了,也就是觀察點。
觀察點
觀察點是一類特殊的斷點,如果針對某個變量或者表達式指定一個觀察點,那么當它們的值被讀/寫的時候,gdb會停止程序的執行。你不需要像設置斷點時那樣明確指定這個觀察點在程序中的位置。觀察點相關的命令有:
a)watch expr [thread threadnum]
對 expr 設置一個觀察點。當 expr 的值被改變的時候,gdb會停止程序的運行。
如果指定了線程參數thread threadnum ,則 只有 在線程 threadnum 改變 expr 的值時,程序才會停止。
b)rwatch expr [thread threadnum]
對 expr 設置一個讀觀察點。當程序讀 expr 的值時,gdb會停止程序的運行。
c)awatch expr [thread threadnum]
對 expr 設置一個訪問觀察點。當程序讀或者寫 expr 時,gdb會停止程序的運行。
d)info watchpoints
顯示所有的斷點、觀察點、捕捉點。跟info break 相同。
下面再來看看觀察點的使用。
首先我們設置一個斷點在g.loop()這一行,然后運行到這里。step進入loop()函數。這一串命令的執行如下:

這時我們已經進入loop()函數,現在我們設置幾個觀察點。設置命令序列和設置完后的效果如小:

可以看到有5個停止點,其中前兩個是我們設置的斷點,后面三個是觀察點。分別為watch, rwatch 和 awatch。另外還有一個地方就是,x86上默認是硬件觀察點。
好了,來運行一下試試。

到達第一個觀察點的時候程序停止運行,同時打印出了變量的舊值和新值,以及觀察點的位置。
下面我們接著運行,看看遇到后面的觀察點的時候又會怎樣。

程序在第三個觀察點停了下來(第二個觀察點為讀觀察點),同樣打印出了變量的新舊值和觀察點的位置。第二個觀察點由于是讀觀察點,而程序中沒有讀這個變量的地方,因此運行的時候被跳過了。為了看一下讀觀察點的效果,我們再設置一個讀觀察點:

i是循環內的迭代器,它會被復制給loop_array[i]變量,也就是會被讀。可以看到,程序會停止運行,輸出i的值。注意:每次對i的讀操作都會使得程序停止。下面是兩次執行continue命令后的輸出:

觀察點的內容差不多就這些了。另外有個需要注意的地方就是: watch命令設置的觀察點只有在變量或者表達式的值被改變得時候才會使得程序停止運行,如果只是被寫,但是值沒有改變,則程序不會停止。
上面已經講了斷點、觀察點,而對于某些情況,這兩種停止點并不是最有效的方式。例如想在c++程序中跑出異常的時候停
止程序,這時候用斷點就不夠有效了,因為程序中可能好多異常處理的地方,如果一個個設置斷點,那就太麻煩了(當然如果只處理某幾個異常,用斷點也無不可,
甚至用起來更靈活);而觀察點就更不可用了。
這種情況就需要用到捕捉點了。
捕捉點
捕捉點也是一類特殊的斷點,它可以使得程序在某種事件發生時停止運行,例如c++異常,或者加載動態庫、創建子進程等。設置捕捉點的命令是catch.
catch event
其中 event 可以是:
a)throw
c++拋出異常。
b)catch
c++捕捉異常。
c)exception
Ada異常。
d)exception unhandled
程序中未處理的異常。
e)assert
失敗的Ada斷言。
f)exec
對exec的調用(只在HP-UX和GNU/Linux中可用)。
g)fork
對fork的調用(只在HP-UX和GNU/Linux中可用)。
h)vfork
對vfork的調用(只在HP-UX和GNU/Linux中可用)。
i)load
動態加載共享庫(只在HP-UX中可用)。
j)load libname
動態加載共享庫 libname (只在HP-UX中可用)。
k)unload
卸載已加載的共享庫(只在HP-UX中可用)。
l)unload libname
卸載已加載的共享庫 libname (只在HP-UX中可用)。
還有一個設置只生效一次的捕捉點的命令是: tcatch event 。
下面再看一下捕捉點的使用。
首先在vtest.cpp的 g.catch_ex(-1); 設置一個斷點,然后運行,進入此函數。

現在我們設置一個捕捉點。繼續運行:

可以看到程序在拋出異常的地方停止了。
刪除斷點
當斷點不再需要了,那就應該刪除掉,否則每次執行到斷點的位置程序都要停下來,會把人逼瘋的。刪除斷點的命令有兩個:clear和delete。
a)clear [ location ]
如果不指定 location ,則刪除選擇的棧幀中下一條要執行的指令上的任何斷點。如果選擇的是最內部的棧幀(也就是當前正執行的函數的棧幀),則clear會將剛剛使程序停止的斷點被刪除。
關于 location 的說明可以看這里。
b)delete [breakpoints] [ range ... ]
刪除指定范圍 range 那的所有的斷點、觀察點、捕捉點。如果不指定參數 range ,則會刪除所有的停止點。這里的 range 指定的是斷點編號區間。可以用info break查看斷點信息。
禁用斷點
如果不想刪除斷點,只是想暫時使它失效,則可以使用disable命令。disable命令的形式如下:
a)disable [breakpoints] [ range ...]
使指定區間 range 內的斷點失效。如果不指定 range ,則所有的斷點都失效。
使斷點生效的命令是enable,形式有:
a)enable [breakpoints] [ range ...]
使指定區間 range 內的斷點或者所有斷點生效。
b)enable [breakpoints] once range...
使指定區間內的斷點生效一次。
c)enable [breakpoints] delete range...
使指定區間內的斷點生效一次,然后刪除。
斷點條件
斷點條件使得只有在相應的條件滿足時,斷點才有效。這里的條件表達式跟程序所用語言的邏輯表達式的語法相同,例如在c/c++語言里,可以用 a==b 或者 x&&y這種表達式。
斷點條件可以在設置斷點的時候指定,也可以在斷點設置后通過condition命令來設置或者改變。 condition的形式為:
a)condition bnum expression
設置表達式 expression 為停止點 bnum 的條件。
b)condition bnum
刪除停止點 bnum 的條件。
還有一個命令,可以使得gdb忽略斷點的條件一定的次數,其形式為:
a)ingore bnum count
指定位置
許多gdb命令都接受一個用于指定程序位置的參數。位置的指定方式有下面幾種:
a) linenum
當前源文件的行號。
b) -offset
當前行前面,跟當前行間隔為 offset 的行。當前行可以這樣確定:使用list命令,打印出來的最后一行就是當前行;或者對于斷點命令,選定的棧幀中,程序停止執行的位置就是當前行。
c) +offset
當前行后面,跟當前行間隔為 offset 的行。
d) filename:linenum
源文件 filename 中的行 linenum。
e) function
當前源文件中的函數 function。
f) filename:function
源文件 filename 中的函數 function。
g) * address
指定程序地址 address。常用的 address 形式有:
expression -- 當前語言中有效的表達式。
funcaddr -- 函數的地址。在c/c++中就是函數名。
'filename'::funcaddr -- 源文件 filename 中的函數地址 funcaddr。
后面懶得寫了,暫時先放一放。發現就算是抄文檔,內容多了也是個很累人的活。唉,懶了,不行了...
棧
待添加
數據
待添加
五 示例2
待添加
六 后記
這篇文章只是撿了GDB中最常用的一些東西,而且還只是最常用的東西中的一小部分,有興趣或者需要的可以直接看GDB的文檔,可以在這里找到。