3.4 gdb調(diào)試器
調(diào)試是所有程序員都會(huì)面臨的問(wèn)題。如何提高程序員的調(diào)試效率,更好、更快地定位程序中的問(wèn)題從而加快程序開(kāi)發(fā)的進(jìn)度,是大家都很關(guān)注的問(wèn)題。就如讀者熟知的Windows下的一些調(diào)試工具,如Visual Studio自帶的設(shè)置斷點(diǎn)、單步跟蹤等,都受到了廣大用戶(hù)的贊賞。那么,在Linux下有什么很好的調(diào)試工具呢?
gdb調(diào)試器是一款GNU開(kāi)發(fā)組織并發(fā)布的UNIX/Linux下的程序調(diào)試工具。雖然,它沒(méi)有圖形化的友好界面,但是它強(qiáng)大的功能也足以與微軟的Visual Studio等工具媲美。下面就請(qǐng)跟隨筆者一步步學(xué)習(xí)gdb調(diào)試器。
3.4.1 gdb使用流程
這里給出了一個(gè)短小的程序,由此帶領(lǐng)讀者熟悉gdb的使用流程。建議讀者能夠動(dòng)手實(shí)際操作一下。
首先,打開(kāi)Linux下的編輯器vi或者emacs,編輯如下代碼(由于為了更好地熟悉gdb的操作,筆者在此使用vi編輯,希望讀者能夠參見(jiàn)3.3節(jié)中對(duì)vi的介紹,并熟練使用vi)。
/*test.c*/
#include <stdio.h>
int sum(int m);
int main()
{
int i, n = 0;
sum(50);
for(i = 1; i<= 50; i++)
{
n += i;
}
printf("The sum of 1-50 is %d n", n );
}
int sum(int m)
{
int i, n = 0;
for (i = 1; i <= m; i++)
{
n += i;
printf("The sum of 1-m is %dn", n);
}
}
在保存退出后首先使用gcc對(duì)test.c進(jìn)行編譯,注意一定要加上選項(xiàng)“-g”,這樣編譯出的可執(zhí)行代碼中才包含調(diào)試信息,否則之后gdb無(wú)法載入該可執(zhí)行文件。
[root@localhost gdb]# gcc -g test.c -o test
雖然這段程序沒(méi)有錯(cuò)誤,但調(diào)試完全正確的程序可以更加了解gdb的使用流程。接下來(lái)就啟動(dòng)gdb進(jìn)行調(diào)試。注意,gdb進(jìn)行調(diào)試的是可執(zhí)行文件,而不是如“.c”的源代碼,因此,需要先通過(guò)gcc編譯生成可執(zhí)行文件才能用gdb進(jìn)行調(diào)試。
[root@localhost gdb]# gdb test
GNU gdb Red Hat Linux (6.3.0.0-1.21rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or 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.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb)
可以看出,在gdb的啟動(dòng)畫(huà)面中指出了gdb的版本號(hào)、使用的庫(kù)文件等信息,接下來(lái)就進(jìn)入了由“(gdb)”開(kāi)頭的命令行界面了。
(1)查看文件。
在gdb中鍵入“l”(list)就可以查看所載入的文件,如下所示。
注意 |
在gdb的命令中都可使用縮略形式的命令,如“l”代表“list”,“b”代表“breakpoint”,“p”代表“print”等,讀者也可使用“help”命令查看幫助信息。 |
(gdb) l
1 #include <stdio.h>
2 int sum(int m);
3 int main()
4 {
5 int i,n = 0;
6 sum(50);
7 for(i = 1; i <= 50; i++)
8 {
9 n += i;
10 }
(gdb) l
11 printf("The sum of 1~50 is %d n", n );
12
13 }
14 int sum(int m)
15 {
16 int i, n = 0;
17 for(i = 1; i <= m; i++)
18 {
19 n += i;
20 }
21 printf("The sum of 1~m is = %dn", n);
20 }
可以看出,gdb列出的源代碼中明確地給出了對(duì)應(yīng)的行號(hào),這樣就可以大大地方便代碼的定位。
(2)設(shè)置斷點(diǎn)。
設(shè)置斷點(diǎn)是調(diào)試程序中一個(gè)非常重要的手段,它可以使程序運(yùn)行到一定位置時(shí)暫停。因此,程序員在該位置處可以方便地查看變量的值、堆棧情況等,從而找出代碼的癥結(jié)所在。
在gdb中設(shè)置斷點(diǎn)非常簡(jiǎn)單,只需在“b”后加入對(duì)應(yīng)的行號(hào)即可(這是最常用的方式,另外還有其他方式設(shè)置斷點(diǎn)),如下所示:
(gdb) b 6
Breakpoint 1 at 0x804846d: file test.c, line 6.
要注意的是,在gdb中利用行號(hào)設(shè)置斷點(diǎn)是指代碼運(yùn)行到對(duì)應(yīng)行之前將其停止,如上例中,代碼運(yùn)行到第6行之前暫停(并沒(méi)有運(yùn)行第6行)。
(3)查看斷點(diǎn)情況。
在設(shè)置完斷點(diǎn)之后,用戶(hù)可以鍵入“info b”來(lái)查看設(shè)置斷點(diǎn)情況,在gdb中可以設(shè)置多個(gè)斷點(diǎn)。
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804846d in main at test.c:6
用戶(hù)在斷點(diǎn)鍵入“backrace”(只輸入“bt”即可)可以查到調(diào)用函數(shù)(堆棧)的情況,這個(gè)功能在程序調(diào)試之中使用非常廣泛,經(jīng)常用于排除錯(cuò)誤或者監(jiān)視調(diào)用堆棧的情況。
(gdb) b 19
(gdb) c
Breakpoin 2, sum(m=50) at test.c:19
19 printf(“The sum of 1-m is %dn”, n);
(gdb) bt
#0 sum(m=50) at test.c:19 /* 停在test.c的sum()函數(shù),第19行*/
#1 0x080483e8 in main() at test.c:6 /* test.c的第6行調(diào)用sum函數(shù)*/
(4)運(yùn)行代碼。
接下來(lái)就可運(yùn)行代碼了,gdb默認(rèn)從首行開(kāi)始運(yùn)行代碼,鍵入“r”(run)即可(若想從程序中指定行開(kāi)始運(yùn)行,可在r后面加上行號(hào))。
(gdb) r
Starting program: /root/workplace/gdb/test
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0x5fb000
Breakpoint 1, main () at test.c:6
6 sum(50);
可以看到,程序運(yùn)行到斷點(diǎn)處就停止了。
(5)查看變量值。
在程序停止運(yùn)行之后,程序員所要做的工作是查看斷點(diǎn)處的相關(guān)變量值。在gdb中鍵入“p”+變量值即可,如下所示:
(gdb) p n
$1 = 0
(gdb) p i
$2 = 134518440
在此處,為什么變量“i”的值為如此奇怪的一個(gè)數(shù)字呢?原因就在于程序是在斷點(diǎn)設(shè)置的對(duì)應(yīng)行之前停止的,那么在此時(shí),并沒(méi)有把“i”的數(shù)值賦為零,而只是一個(gè)隨機(jī)的數(shù)字。但變量“n”是在第4行賦值的,故在此時(shí)已經(jīng)為零。
小技巧 |
gdb在顯示變量值時(shí)都會(huì)在對(duì)應(yīng)值之前加上“$N”標(biāo)記,它是當(dāng)前變量值的引用標(biāo)記,所以以后若想再次引用此變量就可以直接寫(xiě)作“$N”,而無(wú)需寫(xiě)冗長(zhǎng)的變量名。 |
(6)單步運(yùn)行。
單步運(yùn)行可以使用命令“n”(next)或“s”(step),它們之間的區(qū)別在于:若有函數(shù)調(diào)用的時(shí)候,“s”會(huì)進(jìn)入該函數(shù)而“n”不會(huì)進(jìn)入該函數(shù)。因此,“s”就類(lèi)似于Uisual等工具中的“step in”,“n”類(lèi)似與Uisual等工具中的“step over”。它們的使用如下所示:
(gdb) n
The sum of 1-m is 1275
7 for (i = 1; i <= 50; i++)
(gdb) s
sum (m=50) at test.c:16
16 int i, n = 0;
可見(jiàn),使用“n”后,程序顯示函數(shù)sum()的運(yùn)行結(jié)果并向下執(zhí)行,而使用“s”后則進(jìn)入sum()函數(shù)之中單步運(yùn)行。
(7)恢復(fù)程序運(yùn)行
在查看完所需變量及堆棧情況后,就可以使用命令“c”(continue)恢復(fù)程序的正常運(yùn)行了。這時(shí),它會(huì)把剩余還未執(zhí)行的程序執(zhí)行完,并顯示剩余程序中的執(zhí)行結(jié)果。以下是之前使用“n”命令恢復(fù)后的執(zhí)行結(jié)果:
(gdb) c
Continuing.
The sum of 1-50 is :1275
Program exited with code 031.
可以看出,程序在運(yùn)行完后退出,之后程序處于“停止?fàn)顟B(tài)”。
小知識(shí) |
在gdb中,程序的運(yùn)行狀態(tài)有“運(yùn)行”、“暫停”和“停止”3種,其中“暫停”狀態(tài)為程序遇到了斷點(diǎn)或觀察點(diǎn)之類(lèi)的,程序暫時(shí)停止運(yùn)行,而此時(shí)函數(shù)的地址、函數(shù)參數(shù)、函數(shù)內(nèi)的局部變量都會(huì)被壓入“棧”(Stack)中。故在這種狀態(tài)下可以查看函數(shù)的變量值等各種屬性。但在函數(shù)處于“停止”狀態(tài)之后,“棧”就會(huì)自動(dòng)撤消,它也就無(wú)法查看各種信息了。 |
3.4.2 gdb基本命令
gdb的命令可以通過(guò)查看help進(jìn)行查找,由于gdb的命令很多,因此gdb的help將其分成了很多種類(lèi)(class),用戶(hù)可以通過(guò)進(jìn)一步查看相關(guān)class找到相應(yīng)命令,如下所示:
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
…
Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
上述列出了gdb各個(gè)分類(lèi)的命令,注意底部的加粗部分說(shuō)明其為分類(lèi)命令。接下來(lái)可以具體查找各分類(lèi)的命令,如下所示:
(gdb) help data
Examining data.
List of commands:
call -- Call a function in the program
delete display -- Cancel some expressions to be displayed when program stops
delete mem -- Delete memory region
disable display -- Disable some expressions to be displayed when program stops
…
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
若用戶(hù)想要查找call命令,就可鍵入“help call”。
(gdb) help call
Call a function in the program.
The argument is the function name and arguments, in the notation of the
current working language. The result is printed and saved in the value
history, if it is not void.
當(dāng)然,若用戶(hù)已知命令名,直接鍵入“help [command]”也是可以的。
gdb中的命令主要分為以下幾類(lèi):工作環(huán)境相關(guān)命令、設(shè)置斷點(diǎn)與恢復(fù)命令、源代碼查看命令、查看運(yùn)行數(shù)據(jù)相關(guān)命令及修改運(yùn)行參數(shù)命令。以下就分別對(duì)這幾類(lèi)命令進(jìn)行講解。
1.工作環(huán)境相關(guān)命令
gdb中不僅可以調(diào)試所運(yùn)行的程序,而且還可以對(duì)程序相關(guān)的工作環(huán)境進(jìn)行相應(yīng)的設(shè)定,甚至還可以使用shell中的命令進(jìn)行相關(guān)的操作,其功能極其強(qiáng)大。gdb常見(jiàn)工作環(huán)境相關(guān)命令如表3.11所示。
表3.11 gdb工作環(huán)境相關(guān)命令
命 令 格 式 |
含 義 |
set args運(yùn)行時(shí)的參數(shù) |
指定運(yùn)行時(shí)參數(shù),如set args 2 |
show args |
查看設(shè)置好的運(yùn)行參數(shù) |
Path dir |
設(shè)定程序的運(yùn)行路徑 |
show paths |
查看程序的運(yùn)行路徑 |
set environment var [=value] |
設(shè)置環(huán)境變量 |
show environment [var] |
查看環(huán)境變量 |
cd dir |
進(jìn)入dir目錄,相當(dāng)于shell中的cd命令 |
Pwd |
顯示當(dāng)前工作目錄 |
shell command |
運(yùn)行shell的command命令 |
2.設(shè)置斷點(diǎn)與恢復(fù)命令
gdb中設(shè)置斷點(diǎn)與恢復(fù)的常見(jiàn)命令如表3.12所示。
表3.12 gdb設(shè)置斷點(diǎn)與恢復(fù)相關(guān)命令
命 令 格 式 |
含 義 |
Info b |
查看所設(shè)斷點(diǎn) |
break [文件名:]行號(hào)或函數(shù)名 <條件表達(dá)式> |
設(shè)置斷點(diǎn) |
tbreak [文件名:]行號(hào)或函數(shù)名 <條件表達(dá)式> |
設(shè)置臨時(shí)斷點(diǎn),到達(dá)后被自動(dòng)刪除 |
delete [斷點(diǎn)號(hào)] |
刪除指定斷點(diǎn),其斷點(diǎn)號(hào)為“info b”中的第一欄。若缺省斷點(diǎn)號(hào)則刪除所有斷點(diǎn) |
disable [斷點(diǎn)號(hào)] |
停止指定斷點(diǎn),使用“info b”仍能查看此斷點(diǎn)。同delete一樣,若缺省斷點(diǎn)號(hào)則停止所有斷點(diǎn) |
enable [斷點(diǎn)號(hào)] |
激活指定斷點(diǎn),即激活被disable停止的斷點(diǎn) |
condition [斷點(diǎn)號(hào)] <條件表達(dá)式> |
修改對(duì)應(yīng)斷點(diǎn)的條件 |
ignore [斷點(diǎn)號(hào)]<num> |
在程序執(zhí)行中,忽略對(duì)應(yīng)斷點(diǎn)num次 |
Step |
單步恢復(fù)程序運(yùn)行,且進(jìn)入函數(shù)調(diào)用 |
Next |
單步恢復(fù)程序運(yùn)行,但不進(jìn)入函數(shù)調(diào)用 |
Finish |
運(yùn)行程序,直到當(dāng)前函數(shù)完成返回 |
C |
繼續(xù)執(zhí)行函數(shù),直到函數(shù)結(jié)束或遇到新的斷點(diǎn) |
設(shè)置斷點(diǎn)在gdb的調(diào)試中非常重要,下面著重講解gdb中設(shè)置斷點(diǎn)的方法。
gdb中設(shè)置斷點(diǎn)有多種方式:其一是按行設(shè)置斷點(diǎn);另外還可以設(shè)置函數(shù)斷點(diǎn)和條件斷點(diǎn)。下面具體介紹后兩種設(shè)置斷點(diǎn)的方法。
① 函數(shù)斷點(diǎn)。
gdb中按函數(shù)設(shè)置斷點(diǎn)只需把函數(shù)名列在命令“b”之后,如下所示:
(gdb) b test.c:sum (可以簡(jiǎn)化為b sum)
Breakpoint 1 at 0x80484ba: file test.c, line 16.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x080484ba in sum at test.c:16
要注意的是,此時(shí)的斷點(diǎn)實(shí)際是在函數(shù)的定義處,也就是在16行處(注意第16行還未執(zhí)行)。
② 條件斷點(diǎn)。
gdb中設(shè)置條件斷點(diǎn)的格式為:b 行數(shù)或函數(shù)名 if 表達(dá)式。具體實(shí)例如下所示:
(gdb) b 8 if i==10
Breakpoint 1 at 0x804848c: file test.c, line 8.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804848c in main at test.c:8
stop only if i == 10
(gdb) r
Starting program: /home/yul/test
The sum of 1-m is 1275
Breakpoint 1, main () at test.c:9
9 n += i;
(gdb) p i
$1 = 10
可以看到,該例中在第8行(也就是運(yùn)行完第7行的for循環(huán))設(shè)置了一個(gè)“i==0”的條件斷點(diǎn),在程序運(yùn)行之后可以看出,程序確實(shí)在i為10時(shí)暫停運(yùn)行。
3.gdb中源碼查看相關(guān)命令
在gdb中可以查看源碼以方便其他操作,它的常見(jiàn)相關(guān)命令如表3.13所示。
表3.13 gdb源碼查看相關(guān)相關(guān)命令
命 令 格 式 |
含 義 |
list <行號(hào)>|<函數(shù)名> |
查看指定位置代碼 |
file [文件名] |
加載指定文件 |
forward-search 正則表達(dá)式 |
源代碼的前向搜索 |
reverse-search 正則表達(dá)式 |
源代碼的后向搜索 |
dir DIR |
將路徑DIR添加到源文件搜索的路徑的開(kāi)頭 |
show directories |
顯示源文件的當(dāng)前搜索路徑 |
info line |
顯示加載到gdb內(nèi)存中的代碼 |
4.gdb中查看運(yùn)行數(shù)據(jù)相關(guān)命令
gdb中查看運(yùn)行數(shù)據(jù)是指當(dāng)程序處于“運(yùn)行”或“暫停”狀態(tài)時(shí),可以查看的變量及表達(dá)式的信息,其常見(jiàn)命令如表3.14所示。
表3.14 gdb查看運(yùn)行數(shù)據(jù)相關(guān)命令
命 令 格 式 |
含 義 |
print 表達(dá)式|變量 |
查看程序運(yùn)行時(shí)對(duì)應(yīng)表達(dá)式和變量的值 |
x <n/f/u> |
查看內(nèi)存變量?jī)?nèi)容。其中n為整數(shù)表示顯示內(nèi)存的長(zhǎng)度,f表示顯示的格式,u表示從當(dāng)前地址往后請(qǐng)求顯示的字節(jié)數(shù) |
display 表達(dá)式 |
設(shè)定在單步運(yùn)行或其他情況中,自動(dòng)顯示的對(duì)應(yīng)表達(dá)式的內(nèi)容 |
backtrace |
查看當(dāng)前棧的情況,即可以查到哪些被調(diào)用的函數(shù)尚未返回 |
5.gdb中修改運(yùn)行參數(shù)相關(guān)命令
gdb還可以修改運(yùn)行時(shí)的參數(shù),并使該變量按照用戶(hù)當(dāng)前輸入的值繼續(xù)運(yùn)行。它的設(shè)置方法為:在單步執(zhí)行的過(guò)程中,鍵入命令“set 變量=設(shè)定值”。這樣,在此之后,程序就會(huì)按照該設(shè)定的值運(yùn)行了。下面,筆者結(jié)合上一節(jié)的代碼將n的初始值設(shè)為4,其代碼如下所示:
(gdb) b 7
Breakpoint 5 at 0x804847a: file test.c, line 7.
(gdb) r
Starting program: /home/yul/test
The sum of 1-m is 1275
Breakpoint 5, main () at test.c:7
7 for(i=1; i <= 50; i++)
(gdb) set n=4
(gdb) c
Continuing.
The sum of 1-50 is 1279
Program exited with code 031.
可以看到,最后的運(yùn)行結(jié)果確實(shí)比之前的值大了4。
注意 |
gdb使用時(shí)的注意點(diǎn): · 在gcc編譯選項(xiàng)中一定要加入“-g”。 · 只有在代碼處于“運(yùn)行”或“暫停”狀態(tài)時(shí)才能查看變量值。 · 設(shè)置斷點(diǎn)后程序在指定行之前停止。 |