调试(三)
使用gdb調試
我們將會使用GNU調試器,gdb,來調試這個程序。這是一個可以免費得到并且可以用于多個Unix平臺的功能強大的調試器。他也是Linux系統上的默認調試器。gdb已經被移植到許多其他平臺上,并且可以用于調試嵌入式實時系統。
啟動gdb
讓我們重新編譯我們的程序用于調試并且啟動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程序進行查看或是在Emacs中進行查看的完整手冊。
(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本身是一個基于文本的程序,但是他確實了一些有助于重復任務的簡化操作。許多版本具有一個命令行編輯歷史,從而我們可以在命令歷史中進行滾動并且再次執行相同的命令。所有的版本都支持一個"空白命令",敲擊Enter會再次執行上一條命令。當我們使用step或是next命令在一個程序中分步執行特殊有用。
運行一個程序
我們可以使用run命令執行這個程序。我們為run命令所指定的所有命令都作為參數傳遞給程序。在這個例子中,我們并不需要任何參數。
我們在這里假設我們的系統與作者的類似,也產生了內存錯誤的錯誤信息。如果不是,請繼續閱讀。我們就會發現當我們自己的程序生成一個內存錯誤時應怎么辦。如果我們并不沒有得到內存錯誤信息,但是我們在閱讀本書時希望運行這個例子,我們可以拾起第一個內存訪問問題已經被修復的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)
如前面一樣,我們程序并沒有正確運行。當程序失敗時,gdb會向我們顯示原因以及位置。現在我們可以檢測問題背后的原因。
依據于我們的內核,C庫,以及編譯器選項,我們所看到的程序錯誤也許有所不同,例如,也許當數組元素交換時是在25行,而不是數組元素比較時的23行。如果是這種情況,我們也許會看到如下的輸出:
Program received signal SIGSEGV, Segmentation fault.
0x8000613 in sort (a=0x8001764, n=5) at debug3.c:25
25????? /* 25 */??????????????????????????????? a[j] = a[j+1];
我們仍然可以遵循如下的gdb例子會話。
棧追蹤
程序已經在源文件debug3.c的第23行處的sort函數停止。如果我們并沒有使用額外的調試信息來編譯這個程序,我們就不能看到程序在哪里失敗,也不能使用變量名來檢測數據。
我們可以通過使用backstrace命令來查看我們是如何到達這個位置的。
(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)
這個是一個非常簡單的程序,而且追蹤信息很短小,因為我們并沒有在其他的函數內部來調用許多函數。我們可以看到sort是由同一個文件debug3.c中37行處的main來調用的。通常,問題會更為復雜,而我們可以使用backtrace來發現我們到達錯誤位置的路徑。
backtrace命令可以簡寫為bt,而且為了與其他調試器兼容,where命令也具有相同的功能。
檢測變量
當程序停止時由gdb所輸出的信息以及在棧追蹤中的信息向我們顯示了函數能數的值。
sort函數是使用一個參數a來調用的,而其值為0x8049580。這是數組的地址。依據于所使用的編譯器以及操作系統,這個值在不同的操作系統也會不同。
所影響的行號23,是一個數組元素與另一個數組元素進行比較的地方。
/* 23 */ if(a[j].key > a[j+1].key) {
我們可以使用調試器來檢測函數參數,局部變量以及全局數據的內容。print命令可以向我們顯示變量以及其他表達式的內容。
(gdb) print j
$1 = 4
在這里我們可以看到局部變量j的值為4。類似這樣由gdb命令所報告的所有值都會保存在偽變量中以備將來使用。在這里變量$1賦值為4以防止我們在以后使用。以后的命令將他們的結果存儲為$2,$3,依次類推。
j的值為4的事實意味著程序試著執行語句
if(a[4].key > a[4+1].key)
我們傳遞給sort的數組,array,只有5個元素,由0到4進行索引。所以這條語句讀取并不存在的array[5]。循環變量j已經讀取一個錯誤的值。
如果我們嘗試這個例子,而我們程序在25行發生錯誤,我們系統只有在交互元素時才會檢測到一個超過數組邊界的讀取,執行
/* 25 */ a[j] = a[j+1];
此時將j設置為4,結果為
a[4] = a[4+1];
我們可以使用print通過表達式來查看所傳遞的數組元素。使用gdb,我們幾乎可以使用任何合法的C表達式來輸出變量,數組元素,以及指針的值。
(gdb) print a[3]
$2 = {data = “alex”, ‘/000’ <repeats 4091 times>, key = 1}
(gdb)
gdb將命令的結果保存在一個偽變量中,$<number>。上一個結果總是為$,而之前的一個為$$。這可以使得在一個結果可以用在另一個命令中。例如,
(gdb) print j
$3 = 4
(gdb) print a[$-1].key
$4 = 1
列出程序
我們可以使用list命令在gdb內查看程序源代碼。這會打印出當前位置周圍的部分代碼。持續的使用list會輸出更多的代碼。我們也可以為list指定一個行號或是函數名作為一個參數,而gdb就會顯示那個位置的代碼。
(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行循環設置為當變量j小于n時才會執行。在這個例子中,n為5,所以j的最終值為4,總是小1。4會使得a[4]與a[5]進行比較并且有可能進行交換。這個問題的解決方法就是修正循環的結束條件為j < n-1。
讓我們做出修改,將這個新程序稱之為debug4.c,重新編譯,并再次運行。
/* 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}
程序仍不能正常工作,因為他輸出了一個不正確的排序列表。下面我們使用gdb在程序運行時分步執行。
設置斷點
查找出程序在哪里失敗,我們需要能夠查看程序運行他都做了什么。我們可以通過設置斷點在任何位置停止程序。這會使得程序停止并將控制權返回調試器。我們將能夠監視變量并且允許程序繼續執行。
在sort函數中有兩個循環。外層循環,使用循環變時i,對于數組中的每一個元素運行一次。內層循環將其與列表中的下一個元素進行交換。這具有將最小的元素交換到最上面的效果。在外層循環的每一次執行之后,最大的元素應位置底部。我們可通過在外層循環停止程序進行驗證并且檢測數組狀態。
有許多命令可以用于設置斷點。通過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.
讓我們在20行設置一個斷點并且運行這個程序:
$ 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++) {
我們可以輸出數組值并且使用cont可以使得程序繼續執行。這個會使得程序繼續運行直到遇到下一個斷點,在這個例子中,直到他再次執行到20行。在任何時候我們都可以有多個活動斷點。
(gdb) print array[0]
$1 = {data = “bill”, ‘/000’ <repeats 4091 times>, key = 3}
要輸出多個連續的項目,我們可以使用@<number>結構使得gdb輸出多個數組元素。要輸出array的所有五個元素,我們可以使用
(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}}
注意,輸出已經進行簡單的處理從而使其更易于閱讀。因為這是第一次循環,數組并沒有發生變量。當我們允許程序繼續執行,我們可以看到當處理執行時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命令來設置gdb當程序在斷點處停止時自動顯示數組:
(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}}
而且我們可以修改斷點,從而他只是簡單的顯示我們所請求的數據并且繼續執行,而不是停止程序。在這樣做,我們可以使用commands命令。這會允許我們指定當遇到一個斷點時執行哪些調試器命令。因為我們已經指定了一個display命令,我們只需要設置斷點命令繼續執行。
(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just “end”.
> cont
> end
現在我們允許程序繼續,他會運行完成,在每次運行到外層循環時輸出數組的值。
(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報告程序并沒有以通常的退出代碼退出。這是因為程序本身并沒有調用exit也沒有由main返回一個值。在這種情況下,這個退出代碼是無意義的,而一個有意義的退出代碼應由調用exit來提供。
這個程序看起來似乎外層循環次數并不是我們所期望的。我們可以看到循環結束條件所使用的參數值n在每個斷點處減小。這意味著循環并沒有執行足夠的次數。問題就在于30行處n的減小。
/* 30 */ n--;
這是一個利用在每一次外層循環結束時array的最大元素都會位于底部的事實來優化程序的嘗試,所以就會有更少的排序。但是,正如我們所看到的,這是與外層循環的接口,并且造成了問題。最簡單的修正方法就是刪除引起問題的行。讓我們通過使用調試器來應用補丁測試這個修正是否有效。
使用調試進行補丁
我們已經看到了我們可以使用調試器來設置斷點與檢測變量的值。通過使用帶動作的斷點,我們可以在修改源代碼與重新編譯之前試驗一個修正,稱之為補丁。在這個例子中,我們需要在30行設置斷點,并且增加變量n。然后,當30行執行,這個值將不會發生變化。
讓我們從頭啟動程序。首先,我們必須刪除我們的斷點與顯示。我們可以使用info命令來查看我們設置了哪些斷點與顯示:
(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
我們可以禁止這些或是完全刪除他們。如果我們禁止他們,那么我們可以在以后需要他們時重新允許這些設置:
(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)
這個程序運行結束并且會輸出正確的結果。現在我們可以進行修正并且繼續使用更多的數據進行測試。
轉載于:https://www.cnblogs.com/dyllove98/archive/2009/05/09/2461958.html
總結
                            
                        - 上一篇: CRM下午茶(22)-客户关系管理应用现
 - 下一篇: 转http状态码