调试(三)
使用gdb調(diào)試
我們將會(huì)使用GNU調(diào)試器,gdb,來(lái)調(diào)試這個(gè)程序。這是一個(gè)可以免費(fèi)得到并且可以用于多個(gè)Unix平臺(tái)的功能強(qiáng)大的調(diào)試器。他也是Linux系統(tǒng)上的默認(rèn)調(diào)試器。gdb已經(jīng)被移植到許多其他平臺(tái)上,并且可以用于調(diào)試嵌入式實(shí)時(shí)系統(tǒng)。
啟動(dòng)gdb
讓我們重新編譯我們的程序用于調(diào)試并且啟動(dòng)gdb。
$ cc -g -o debug3 debug3.c
$ gdb debug3
GNU gdb 5.2.1
Copyright 2002 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 “i586-suse-linux”...
(gdb)
gdb具有豐富的在線幫助,以及可以使用info程序進(jìn)行查看或是在Emacs中進(jìn)行查看的完整手冊(cè)。
(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
obscure — Obscure features
running — Running the program
stack — Examining the stack
status — Status inquiries
support — Support facilities
tracepoints — Tracing of program execution without stopping the program
user-defined — User-defined 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)
gdb本身是一個(gè)基于文本的程序,但是他確實(shí)了一些有助于重復(fù)任務(wù)的簡(jiǎn)化操作。許多版本具有一個(gè)命令行編輯歷史,從而我們可以在命令歷史中進(jìn)行滾動(dòng)并且再次執(zhí)行相同的命令。所有的版本都支持一個(gè)"空白命令",敲擊Enter會(huì)再次執(zhí)行上一條命令。當(dāng)我們使用step或是next命令在一個(gè)程序中分步執(zhí)行特殊有用。
運(yùn)行一個(gè)程序
我們可以使用run命令執(zhí)行這個(gè)程序。我們?yōu)閞un命令所指定的所有命令都作為參數(shù)傳遞給程序。在這個(gè)例子中,我們并不需要任何參數(shù)。
我們?cè)谶@里假設(shè)我們的系統(tǒng)與作者的類似,也產(chǎn)生了內(nèi)存錯(cuò)誤的錯(cuò)誤信息。如果不是,請(qǐng)繼續(xù)閱讀。我們就會(huì)發(fā)現(xiàn)當(dāng)我們自己的程序生成一個(gè)內(nèi)存錯(cuò)誤時(shí)應(yīng)怎么辦。如果我們并不沒(méi)有得到內(nèi)存錯(cuò)誤信息,但是我們?cè)陂喿x本書(shū)時(shí)希望運(yùn)行這個(gè)例子,我們可以拾起第一個(gè)內(nèi)存訪問(wèn)問(wèn)題已經(jīng)被修復(fù)的debug4.c。
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug3
Program received signal SIGSEGV, Segmentation fault.
0x080483c0 in sort (a=0x8049580, n=5) at debug3.c:23
23????? /* 23 */??????????????????????? if(a[j].key > a[j+1].key) {
(gdb)
如前面一樣,我們程序并沒(méi)有正確運(yùn)行。當(dāng)程序失敗時(shí),gdb會(huì)向我們顯示原因以及位置。現(xiàn)在我們可以檢測(cè)問(wèn)題背后的原因。
依據(jù)于我們的內(nèi)核,C庫(kù),以及編譯器選項(xiàng),我們所看到的程序錯(cuò)誤也許有所不同,例如,也許當(dāng)數(shù)組元素交換時(shí)是在25行,而不是數(shù)組元素比較時(shí)的23行。如果是這種情況,我們也許會(huì)看到如下的輸出:
Program received signal SIGSEGV, Segmentation fault.
0x8000613 in sort (a=0x8001764, n=5) at debug3.c:25
25????? /* 25 */??????????????????????????????? a[j] = a[j+1];
我們?nèi)匀豢梢宰裱缦碌膅db例子會(huì)話。
棧追蹤
程序已經(jīng)在源文件debug3.c的第23行處的sort函數(shù)停止。如果我們并沒(méi)有使用額外的調(diào)試信息來(lái)編譯這個(gè)程序,我們就不能看到程序在哪里失敗,也不能使用變量名來(lái)檢測(cè)數(shù)據(jù)。
我們可以通過(guò)使用backstrace命令來(lái)查看我們是如何到達(dá)這個(gè)位置的。
(gdb) backtrace
#0 0x080483c0 in sort (a=0x8049580, n=5) at debug3.c:23
#1 0x0804849b in main () at debug3.c:37
#2 0x400414f2 in __libc_start_main () from /lib/libc.so.6
(gdb)
這個(gè)是一個(gè)非常簡(jiǎn)單的程序,而且追蹤信息很短小,因?yàn)槲覀儾](méi)有在其他的函數(shù)內(nèi)部來(lái)調(diào)用許多函數(shù)。我們可以看到sort是由同一個(gè)文件debug3.c中37行處的main來(lái)調(diào)用的。通常,問(wèn)題會(huì)更為復(fù)雜,而我們可以使用backtrace來(lái)發(fā)現(xiàn)我們到達(dá)錯(cuò)誤位置的路徑。
backtrace命令可以簡(jiǎn)寫(xiě)為bt,而且為了與其他調(diào)試器兼容,where命令也具有相同的功能。
檢測(cè)變量
當(dāng)程序停止時(shí)由gdb所輸出的信息以及在棧追蹤中的信息向我們顯示了函數(shù)能數(shù)的值。
sort函數(shù)是使用一個(gè)參數(shù)a來(lái)調(diào)用的,而其值為0x8049580。這是數(shù)組的地址。依據(jù)于所使用的編譯器以及操作系統(tǒng),這個(gè)值在不同的操作系統(tǒng)也會(huì)不同。
所影響的行號(hào)23,是一個(gè)數(shù)組元素與另一個(gè)數(shù)組元素進(jìn)行比較的地方。
/* 23 */ if(a[j].key > a[j+1].key) {
我們可以使用調(diào)試器來(lái)檢測(cè)函數(shù)參數(shù),局部變量以及全局?jǐn)?shù)據(jù)的內(nèi)容。print命令可以向我們顯示變量以及其他表達(dá)式的內(nèi)容。
(gdb) print j
$1 = 4
在這里我們可以看到局部變量j的值為4。類似這樣由gdb命令所報(bào)告的所有值都會(huì)保存在偽變量中以備將來(lái)使用。在這里變量$1賦值為4以防止我們?cè)谝院笫褂谩R院蟮拿顚⑺麄兊慕Y(jié)果存儲(chǔ)為$2,$3,依次類推。
j的值為4的事實(shí)意味著程序試著執(zhí)行語(yǔ)句
if(a[4].key > a[4+1].key)
我們傳遞給sort的數(shù)組,array,只有5個(gè)元素,由0到4進(jìn)行索引。所以這條語(yǔ)句讀取并不存在的array[5]。循環(huán)變量j已經(jīng)讀取一個(gè)錯(cuò)誤的值。
如果我們嘗試這個(gè)例子,而我們程序在25行發(fā)生錯(cuò)誤,我們系統(tǒng)只有在交互元素時(shí)才會(huì)檢測(cè)到一個(gè)超過(guò)數(shù)組邊界的讀取,執(zhí)行
/* 25 */ a[j] = a[j+1];
此時(shí)將j設(shè)置為4,結(jié)果為
a[4] = a[4+1];
我們可以使用print通過(guò)表達(dá)式來(lái)查看所傳遞的數(shù)組元素。使用gdb,我們幾乎可以使用任何合法的C表達(dá)式來(lái)輸出變量,數(shù)組元素,以及指針的值。
(gdb) print a[3]
$2 = {data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}
(gdb)
gdb將命令的結(jié)果保存在一個(gè)偽變量中,$<number>。上一個(gè)結(jié)果總是為$,而之前的一個(gè)為$$。這可以使得在一個(gè)結(jié)果可以用在另一個(gè)命令中。例如,
(gdb) print j
$3 = 4
(gdb) print a[$-1].key
$4 = 1
列出程序
我們可以使用list命令在gdb內(nèi)查看程序源代碼。這會(huì)打印出當(dāng)前位置周?chē)牟糠执a。持續(xù)的使用list會(huì)輸出更多的代碼。我們也可以為list指定一個(gè)行號(hào)或是函數(shù)名作為一個(gè)參數(shù),而gdb就會(huì)顯示那個(gè)位置的代碼。
(gdb) list
18????? /* 18 */ int s = 1;
19????? /* 19 */
20????? /* 20 */ for(; i < n && s != 0; i++) {
21????? /* 21 */???????? s = 0;
22????? /* 22 */???????? for(j = 0; j < n; j++) {
23????? /* 23 */???????????????? if(a[j].key > a[j+1].key) {
24????? /* 24 */???????????????????????? item t = a[j];
25??? /* 25 */ a[j] = a[j+1];
26??? /* 26 */ a[j+1] = t;
27??? /* 27 */ s++;
(gdb)
我們可以看到在22行循環(huán)設(shè)置為當(dāng)變量j小于n時(shí)才會(huì)執(zhí)行。在這個(gè)例子中,n為5,所以j的最終值為4,總是小1。4會(huì)使得a[4]與a[5]進(jìn)行比較并且有可能進(jìn)行交換。這個(gè)問(wèn)題的解決方法就是修正循環(huán)的結(jié)束條件為j < n-1。
讓我們做出修改,將這個(gè)新程序稱之為debug4.c,重新編譯,并再次運(yùn)行。
/* 22 */ for(j = 0; j < n-1; j++) {
$ cc -g -o debug4 debug4.c
$ ./debug4
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
程序仍不能正常工作,因?yàn)樗敵隽艘粋€(gè)不正確的排序列表。下面我們使用gdb在程序運(yùn)行時(shí)分步執(zhí)行。
設(shè)置斷點(diǎn)
查找出程序在哪里失敗,我們需要能夠查看程序運(yùn)行他都做了什么。我們可以通過(guò)設(shè)置斷點(diǎn)在任何位置停止程序。這會(huì)使得程序停止并將控制權(quán)返回調(diào)試器。我們將能夠監(jiān)視變量并且允許程序繼續(xù)執(zhí)行。
在sort函數(shù)中有兩個(gè)循環(huán)。外層循環(huán),使用循環(huán)變時(shí)i,對(duì)于數(shù)組中的每一個(gè)元素運(yùn)行一次。內(nèi)層循環(huán)將其與列表中的下一個(gè)元素進(jìn)行交換。這具有將最小的元素交換到最上面的效果。在外層循環(huán)的每一次執(zhí)行之后,最大的元素應(yīng)位置底部。我們可通過(guò)在外層循環(huán)停止程序進(jìn)行驗(yàn)證并且檢測(cè)數(shù)組狀態(tài)。
有許多命令可以用于設(shè)置斷點(diǎn)。通過(guò)gdb的help breakpoint命令可以列表這些命令:
(gdb) help breakpoint
Making program stop at certain points.
List of commands:
awatch — Set a watchpoint for an expression
break — Set breakpoint at specified line or function
catch — Set catchpoints to catch events
clear — Clear breakpoint at specified line or function
commands — Set commands to be executed when a breakpoint is hit
condition — Specify breakpoint number N to break only if COND is true
delete — Delete some breakpoints or auto-display expressions
disable — Disable some breakpoints
enable — Enable some breakpoints
hbreak — Set a hardware assisted breakpoint
ignore — Set ignore-count of breakpoint number N to COUNT
rbreak — Set a breakpoint for all functions matching REGEXP
rwatch — Set a read watchpoint for an expression
tbreak — Set a temporary breakpoint
tcatch — Set temporary catchpoints to catch events
thbreak — Set a temporary hardware assisted breakpoint
watch — Set a watchpoint for an expression
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
讓我們?cè)?0行設(shè)置一個(gè)斷點(diǎn)并且運(yùn)行這個(gè)程序:
$ gdb debug4
(gdb) break 20
Breakpoint 1 at 0x804835d: file debug4.c, line 20.
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug4
Breakpoint 1, sort (a=0x8049580, n=5) at debug4.c:20
20????? /* 20 */??????? for(; i < n && s != 0; i++) {
我們可以輸出數(shù)組值并且使用cont可以使得程序繼續(xù)執(zhí)行。這個(gè)會(huì)使得程序繼續(xù)運(yùn)行直到遇到下一個(gè)斷點(diǎn),在這個(gè)例子中,直到他再次執(zhí)行到20行。在任何時(shí)候我們都可以有多個(gè)活動(dòng)斷點(diǎn)。
(gdb) print array[0]
$1 = {data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}
要輸出多個(gè)連續(xù)的項(xiàng)目,我們可以使用@<number>結(jié)構(gòu)使得gdb輸出多個(gè)數(shù)組元素。要輸出array的所有五個(gè)元素,我們可以使用
(gdb) print array[0]@5
$2 = {{data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}, {
??? data = “neil”, ‘/000’ <repeats 4091 times>, key = 4}, {
??? data = “john”, ‘/000’ <repeats 4091 times>, key = 2}, {
??? data = “rick”, ‘/000’ <repeats 4091 times>, key = 5}, {
??? data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}}
注意,輸出已經(jīng)進(jìn)行簡(jiǎn)單的處理從而使其更易于閱讀。因?yàn)檫@是第一次循環(huán),數(shù)組并沒(méi)有發(fā)生變量。當(dāng)我們?cè)试S程序繼續(xù)執(zhí)行,我們可以看到當(dāng)處理執(zhí)行時(shí)array的成功修改:
(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049580, n=4) at debug4.c:20
20????? /* 20 */??????? for(; i < n && s != 0; i++) {
(gdb) print array[0]@5
$3 = {{data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}, {
??? data = “john”, ‘/000’ <repeats 4091 times>, key = 2}, {
??? data = “neil”, ‘/000’ <repeats 4091 times>, key = 4}, {
??? data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}, {
??? data = “rick”, ‘/000’ <repeats 4091 times>, key = 5}}
(gdb)
我們可以使用display命令來(lái)設(shè)置gdb當(dāng)程序在斷點(diǎn)處停止時(shí)自動(dòng)顯示數(shù)組:
(gdb) display array[0]@5
1: array[0] @ 5 = {{data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}, {
??? data = “john”, ‘/000’ <repeats 4091 times>, key = 2}, {
??? data = “neil”, ‘/000’ <repeats 4091 times>, key = 4}, {
??? data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}, {
??? data = “rick”, ‘/000’ <repeats 4091 times>, key = 5}}
而且我們可以修改斷點(diǎn),從而他只是簡(jiǎn)單的顯示我們所請(qǐng)求的數(shù)據(jù)并且繼續(xù)執(zhí)行,而不是停止程序。在這樣做,我們可以使用commands命令。這會(huì)允許我們指定當(dāng)遇到一個(gè)斷點(diǎn)時(shí)執(zhí)行哪些調(diào)試器命令。因?yàn)槲覀円呀?jīng)指定了一個(gè)display命令,我們只需要設(shè)置斷點(diǎn)命令繼續(xù)執(zhí)行。
(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just “end”.
> cont
> end
現(xiàn)在我們?cè)试S程序繼續(xù),他會(huì)運(yùn)行完成,在每次運(yùn)行到外層循環(huán)時(shí)輸出數(shù)組的值。
(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049684, n=3) at debug4.c:20
20????? /* 20 */??????? for(; i < n && s != 0; i++) {
1: array[0] @ 5 = {{data = “john”, ‘/000’ <repeats 4091 times>, key = 2}, {
??? data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}, {
??? data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}, {
??? data = “neil”, ‘/000’ <repeats 4091 times>, key = 4}, {
??? data = “rick”, ‘/000’ <repeats 4091 times>, key = 5}}
Breakpoint 1, sort (a=0x8049684, n=2) at debug4.c:20
20????? /* 20 */??????? for(; i < n && s != 0; i++) {
1: array[0] @ 5 = {{data = “john”, ‘/000’ <repeats 4091 times>, key = 2}, {
??? data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}, {
??? data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}, {
??? data = “neil”, ‘/000’ <repeats 4091 times>, key = 4}, {
??? data = “rick”, ‘/000’ <repeats 4091 times>, key = 5}}
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 044.
(gdb)
gdb報(bào)告程序并沒(méi)有以通常的退出代碼退出。這是因?yàn)槌绦虮旧聿](méi)有調(diào)用exit也沒(méi)有由main返回一個(gè)值。在這種情況下,這個(gè)退出代碼是無(wú)意義的,而一個(gè)有意義的退出代碼應(yīng)由調(diào)用exit來(lái)提供。
這個(gè)程序看起來(lái)似乎外層循環(huán)次數(shù)并不是我們所期望的。我們可以看到循環(huán)結(jié)束條件所使用的參數(shù)值n在每個(gè)斷點(diǎn)處減小。這意味著循環(huán)并沒(méi)有執(zhí)行足夠的次數(shù)。問(wèn)題就在于30行處n的減小。
/* 30 */ n--;
這是一個(gè)利用在每一次外層循環(huán)結(jié)束時(shí)array的最大元素都會(huì)位于底部的事實(shí)來(lái)優(yōu)化程序的嘗試,所以就會(huì)有更少的排序。但是,正如我們所看到的,這是與外層循環(huán)的接口,并且造成了問(wèn)題。最簡(jiǎn)單的修正方法就是刪除引起問(wèn)題的行。讓我們通過(guò)使用調(diào)試器來(lái)應(yīng)用補(bǔ)丁測(cè)試這個(gè)修正是否有效。
使用調(diào)試進(jìn)行補(bǔ)丁
我們已經(jīng)看到了我們可以使用調(diào)試器來(lái)設(shè)置斷點(diǎn)與檢測(cè)變量的值。通過(guò)使用帶動(dòng)作的斷點(diǎn),我們可以在修改源代碼與重新編譯之前試驗(yàn)一個(gè)修正,稱之為補(bǔ)丁。在這個(gè)例子中,我們需要在30行設(shè)置斷點(diǎn),并且增加變量n。然后,當(dāng)30行執(zhí)行,這個(gè)值將不會(huì)發(fā)生變化。
讓我們從頭啟動(dòng)程序。首先,我們必須刪除我們的斷點(diǎn)與顯示。我們可以使用info命令來(lái)查看我們?cè)O(shè)置了哪些斷點(diǎn)與顯示:
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:?? y array[0] @ 5
(gdb) info break
Num Type??????????? Disp Enb Address??? What
1?? breakpoint????? keep y?? 0x0804835d in sort at debug4.c:20
???????? breakpoint already hit 4 times
???????? cont
我們可以禁止這些或是完全刪除他們。如果我們禁止他們,那么我們可以在以后需要他們時(shí)重新允許這些設(shè)置:
(gdb) disable break 1
(gdb) disable display 1
(gdb) break 30
Breakpoint 2 at 0x8048462: file debug4.c, line 30.
(gdb) commands 2
Type commands for when breakpoint 2 is hit, one per line.
End with a line saying just “end”.
>set variable n = n+1
>cont
>end
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug4
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????? /* 30 */??????????????? n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????? /* 30 */??????????????? n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????? /* 30 */??????????????? n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????? /* 30 */??????????????? n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????? /*? 30 */?????????????? n--;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 044.
(gdb)
這個(gè)程序運(yùn)行結(jié)束并且會(huì)輸出正確的結(jié)果。現(xiàn)在我們可以進(jìn)行修正并且繼續(xù)使用更多的數(shù)據(jù)進(jìn)行測(cè)試。
轉(zhuǎn)載于:https://www.cnblogs.com/dyllove98/archive/2009/05/09/2461958.html
總結(jié)
- 上一篇: CRM下午茶(22)-客户关系管理应用现
- 下一篇: 转http状态码