双色球基本走势图表图双色球基本走势图表图

使用GDB調試Linux軟件



作者:    文章來源:
發布日期:2007年01月07日

双色球基本走势图表图 www.pfynwn.com.cn 使用GDB調試Linux軟件
GNU調試器介紹



作者:David Seager    寫作時間:01 Feb 2001
翻譯:王銳             翻譯時間:5 November 2005



絕大部分Linux愛好者在shell中使用GNU調試器或者叫做gdb。Gdb讓您能夠看到一個程序的內部結構,指出變量的值,設置斷點并在源碼中進行單步執行。它是一個解決代碼中的問題的非常好的工具。在這篇文章中,我將告訴您這個工具有多么酷和多么有用。
編譯
在您開始之前,您要調試的程序必須先進行編譯,并把調試信息編譯進去。這樣gdb才能找到變量、代碼行和函數。要做到這一點,只要在使用gcc(或g++)編譯的時候額外加一個“-g”的屬性即可,如:
gcc -g eg.c -o eg



運行gdb
Gdb從shell運行,使用命令“gdb”,后面跟上程序名字作為參數,例如“gdb eg”,或者您可以使用file命令加載一個程序用于調試,如“file eg”。這兩種方法都假定您是在程序所在的文件下執行命令的。一旦加載成功,就可以在gdb中使用“run”命令來啟動程序了。



調試例子
如果沒有任何錯誤您的程序會完成執行,在某個點上gdb可以獲得控制權。但是如果出錯了呢?這種情況下gdb會取得控制權并中斷程序,允許您檢查每一樣東西的狀態并有希望找到原因。為了表現這一場景,我們使用一個例子程序:


#include
int wib(int no1, int no2)
{
  int result, diff;
  diff = no1 - no2;
  result = no1 / diff;
  return result;
}
int main(int argc, char *argv[])
{
  int value, div, result, i, total;
  value = 10;
  div = 6;
  total = 0;
  for(i = 0; i < 10; i++)
  {
    result = wib(value, div);
    total += result;
    div++;
    value--;
  }
  printf("%d wibed by %d equals %d ", value, div, total);
  return 0;
}


這個程序運行10次循環,使用函數wib()計算一個累加值,最后打印出結果。
把這段程序輸入到您最喜歡使用的文本編輯器中,注意空行數量也要一樣多,存為“eg1.c”,使用“gcc -g eg1.c -o eg1”編譯并使用“gdb eg1”啟動gdb。使用“run”運行這個程序會導致輸出如下消息:


Program received signal SIGFPE, Arithmetic exception.
0x80483ea in wib (no1=8, no2=8) at eg1.c:7
7         result = no1 / diff;
(gdb)  


Gdb指示程序在第7行獲得一個算術異常并打印出了行號和函數wib()的參數值,這些值非常有用。使用命令“list”看看第7行周圍的源代碼,“list”命令每次顯示10行代碼。再打一次“list”(或按下回車鍵,回車鍵表示重復前一次的命令)會把下一個10行代碼列出來。從gdb消息中可以看出在第7行進行變量“diff”除變量“no1”運算的時候出錯了。
使用“print”命令再加上變量名稱看看變量的值。我們可以通過輸入“print no1”和“print diff”查看“no1”和“diff”的值,結果如下:


(gdb) print no1
$5 = 8
(gdb) print diff
$2 = 0


Gdb顯示“no1”等于8而“diff”等于0。根據這些值和第7行代碼我們可以推出這個算術異常是由于除0引起的。列表顯示變量“diff”在第6行計算出來,我們可以計算一下這個值,使用“print no1-no2”。Gdb告訴我們wib函數的兩個參數都是8,那么我們可能想要檢查調用了wib()函數的main()函數以看看這件事是什么時候發生的。我們還可以允許我們的程序自然死亡,只要告訴gdb讓程序繼續執行就可以了,使用命令“continue”。


(gdb) continue
Continuing.
Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.


使用斷點
為了看到main()函數中在發生什么,我們可以在特定的行上或程序代碼的一個函數上設置斷點,gdb會在執行到那里時中斷。我們可以通過“break main”在main()函數中設置一個斷點,或者指定任何一個我們感興趣的函數名字。為了達到我們的目的,我們要使程序在執行到調用wib()函數前中斷。使用“list main”命令會打印出從main()函數開始的源代碼,再打一個回車鍵會在第21行看到對函數wib()的調用。我們使用“break 21”在第21行設置一個斷點。Gdb會有如下響應:


(gdb) break 21
Breakpoint 1 at 0x8048428: file eg1.c, line 21.


這表示它已經在我們請求的行設置了一個斷點?!皉un”命令會使程序執行到斷點處返回。gdb會產生一個消息顯示它在哪個斷點處中斷并且此處是程序中的哪個位置:


Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:21
21          result = wib(value, div);


使用“print value”和“print div”會顯示對于第一次wib()調用來說變量是10和6,使用“print i”會顯示0。您會很高興地看到gdb顯示所有變量只需使用“info locals”,為您省了許多次敲擊鍵盤的動作。
從前面的研究知道問題發生在“value”和“div”相等的時候,所以使用“continue”繼續執行直到再一次到達斷點1。對于這次迭代“info locals”顯示value等于9,div等于7。
我們可以不使用continue而使用“next”,這個命令可以單步執行程序,這樣我們可以看看“value”和“div”是怎樣改變的。Gdb會有如下響應:


(gdb) next    
22          total += result;


多次使用會車鍵會顯示一個加和一個減,如下:


(gdb)
23          div++;
(gdb)
24          value--;


另外兩個回車把我們帶到21行,準備調用wib()?!癷nfo locals”顯示現在“div”等于“value”,預示著即將到來的問題。如果有興趣還可以通過單步執行進入到wib()函數內部再次看看這個除法錯誤,使用“step”命令就可以了(與“step”相對的是“next”命令,這個命令會跳過函數調用),接下來使用“next”獲得一個“result”的計算值。
既然我們已經完成了調試,可以通過“quit”命令退出gdb。因為程序還在運行,這個動作會終止程序,gdb會有提示以確認您想要終止。
更多斷點和檢查點
在上面的例子中我們在21行設置了一個斷點,因為我們對調用wib()函數前的“value”值什么時候等于“div”值感興趣。我們要連續執行程序兩次才能到達這個點,然而通過給斷點設置條件我們可以使gdb只在“value”確實等于“div”的時候中止。在定義斷點的時候設置條件,我們可以指定“break <line number> if <conditional expression>”。把eg1重新載入gdb并使用如下命令:


(gdb) break 21 if value==div
Breakpoint 1 at 0x8048428: file eg1.c, line 21.


如果一個斷點如1號斷點已經在21行定義好了,我們可以使用“condition”命令在這個斷點上設置條件:


(gdb) condition 1 value==div


使用“run”運行eg1.c,gdb會在“value”等于“div”時中斷,避免了手工打印“continue”直到它們相等為止。當調試C程序時斷點條件可以是任何合法的C表達式,事實上您用哪種語言哪種語言的合法表達式就可以使用。在條件中指定的變量必須在您設置斷點的行的變量作用域范圍內,否則這個表達式就沒有意義。
可以使用“condition”命令指定一個斷點斷點但是不加表達式從而使這個斷點成為非條件型表達式。例如“condition 1”設置斷點1為非條件型。
要看看當前定義了哪些斷點以及它們的條件,使用命令“info break”:


(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048428 in main at eg1.c:21
        stop only if value == div
        breakpoint already hit 1 time


要滿足什么條件,多少次命中,以及這個斷點是否是可用的在“Enb”列顯示出來??梢允褂妹睢癲isable <breakpoint number>”把斷點置為不可用,使用“enable <breakpoint number>”置為可用或者使用“delete <breakpoint number>”整個刪除,例如“disable 1”阻止了在第1號斷點上的中斷。
如果我們對于什么時候“value”等于“div”更感興趣,我們可以設置一個不同類型的斷點,叫做檢查點(watch)。一個檢查點會在特定的表達式改變了值時中斷程序執行,但是它必須是在表達式中的變量在作用范圍之內時設置。要在作用范圍內獲得“value”和“div”,我們可以在main()函數上設置一個斷點并且運行程序,當main()斷點命中后設置我們的檢查點。重啟啟動gdb并載入eg1,使用如下命令:


(gdb) break main
Breakpoint 1 at 0x8048402: file eg1.c, line 15.
(gdb) run
...
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:15
15        value = 10;


當“div”改變時要保持跟蹤,我們可以使用“watch div”,但是因為我們想要“div”等于“value”時中斷,所以使用如下命令:


(gdb) watch div==value
Hardware watchpoint 2: div == value


當“div==value”從0(false)到1(true)變化時繼續執行會導致Gdb中斷:


(gdb) continue
Continuing.
Hardware watchpoint 2: div == value
Old value = 0
New value = 1
main (argc=1, argv=0xbffff954) at eg1.c:19
19        for(i = 0; i < 10; i++)


“info locals”命令顯示“value”確實等于“div”,并且都是8。
使用“info watch”(這個命令等同于“info break”)列出定義好的檢查點,檢查點可以使用和斷點同樣的語法變為可用狀態、不可用狀態和刪除。
核心文件
在gdb下運行程序使得查找bug更加容易,但是在調試器之外程序經查會死掉,只留下一個核心文件。Gdb可以載入核心文件并讓您可以在它死掉前檢查程序狀態。
在gdb外運行我們的例子程序eg1會導致內核崩潰:


$ ./eg1
Floating point exception (core dumped)


要帶著一個核心文件啟動gdb,通過shell使用命令“gdb eg1 core”或者“gdb eg1 -c core”。Gdb會載入核心文件,eg1的程序列表,顯示程序怎樣終止的并提供一條消息,非常像我們之前在gdb下運行程序那樣。


...
Core was generated by `./eg1'.
Program terminated with signal 8, Floating point exception.
...
#0  0x80483ea in wib (no1=8, no2=8) at eg1.c:7
7         result = no1 / diff;


這時我們可以使用“info locals”、“print”、“info args”和“list”查看由除0導致的值。命令“info variables”會打印出所有程序變量的值,但是要費很長時間因為gdb從C庫開始打印變量而不是從我們的程序代碼開始。為了更容易地發現在叫做“wib()”函數中發生了什么,我們可以使用gdb的棧命令。
棧跟蹤
程序“call stack”是一個函數列表。每個函數和它的變量都指定了一個“frame”,而最近調用的函數則在0號frame中(叫做底層frame)。要打印棧,使用命令“bt”(是“backtrace”的縮寫)。


(gdb) bt
#0  0x80483ea in wib (no1=8, no2=8) at eg1.c:7
#1  0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21


這顯示了函數wib()是由main()在21行調用的(立即使用“list 21”可以證明這一點),wib()在0號frame中,而main()在1號frame中。因為wib()在0號frame中,這是程序內執行到發生算術錯誤的時候所在的函數。
當您使用“info locals”命令時gdb打印出當前frame的局部變量,默認情況下是中斷的函數(0號frame)。當前的frame使用命令“frame”打印出來。要查看main函數中的變量(在1號frame中),我們可以通過命令“frame 1”轉到1號frame下,然后再使用“info locals”:


(gdb) frame 1
#1  0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
21          result = wib(value, div);
(gdb) info locals
value = 8
div = 8
result = 4
i = 2
total = 6


這顯示了錯誤發生在第三次循環(i等于2)時,此時“value”等于“div”。
可以通過顯式指定frame號或命令“frame up”或“frame down”來轉換frame,使用“frame up”下上移,使用“frame down”向下移。要獲得更多關于一個frame的信息諸如它的地址和程序語言,您可以使用命令“info frame”。
Gdb棧命令在程序執行時起作用,就像核心文件一樣,所以對于復雜的程序您可以在它運行時跟蹤到這個程序是怎樣進入函數的。
綁定到其它進程上
為了調試核心文件或程序,gdb可以綁定到一個正在運行的進程上(這個進程的程序必須編譯進了調試信息)并進入到進程內。這是通過指定您想要綁定gdb的程序進程ID而不是核心文件名實現的,這里有個例子程序,這個例子程序會進行循環并休眠:


#include
int main(int argc, char *argv[])
{
  int i;
  for(i = 0; i < 60; i++)
  {
    sleep(1);
  }
  return 0;
}


使用“gcc -g eg2.c -o eg2”編譯并使用“./eg2 &”運行它。當它在后臺運行起來的時候把進程ID的信息找到,在這里是1283:


./eg2 &
[3] 1283


啟動gdb并指定您的pid,在這個例子中使用“gdb eg2 1283”。Gdb會尋找一個叫做“1283”的核心文件并且當沒有找到它時會綁定并進入到1283進程中,無論它在哪里運行(在這里可能是在sleep()中):


...
/home/seager/gdb/1283: No such file or directory.
Attaching to program: /home/seager/gdb/eg2, Pid 1283
...
0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
(gdb)  


這里所有的常用gdb命令都可以用。我們可以使用“backtrace”查看我們程序所在位置與main()的關系以及main()的frame號是多少,然后轉換到那個frame,并找出我們進入for循環多少次了:


(gdb) backtrace
#0  0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
#1  0x400a877d in __sleep (seconds=1) at ../sysdeps/unix/sysv/linux/sleep.c:78
#2  0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
(gdb) frame 2
#2  0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
7           sleep(1);
(gdb) print i
$1 = 50


當我們完成任務后可以使用“detach”命令離開而讓進程繼續執行或者使用“kill”命令終止這個進程。我們還可以在進程1283下綁定到eg2,首先使用“file eg2”載入文件,然后使用命令“attach 1283”綁定。
其它靈巧的跟蹤
Gdb允許您在不退出調試環境的情況下運行shell命令,使用“shell [commandline]”實現,對于在調試時修改源代碼是很有用的。
最后您可以在程序運行時修改變量值,使用“set”命令。再次在gdb下運行“eg1”,在第7行設置一個條件斷點(這是計算result的地方),使用命令“break 7 if diff==0”并運行程序。當gdb中斷執行后可以把“diff”置為非0以使程序能夠運行完成:


Breakpoint 1, wib (no1=8, no2=8) at eg1.c:7
7         result = no1 / diff;
(gdb) print diff
$1 = 0
(gdb) set diff=1
(gdb) continue
Continuing.
0 wibed by 16 equals 10
Program exited normally.


結論
GNU調試器放到任何程序員的武器庫中都是一個功能非常強大的工具。我只是講述了它可以做的一小部分工作,要找到更多的話,我建議您閱讀GNU調試器的手冊。

Copyright © 2002-2012 www.pfynwn.com.cn. All rights reserved.
JSP中文網    備案號:粵ICP備09171188號
成都恒??萍擠⒄褂邢薰?nbsp;   成都市一環路南二段6號新瑞樓三樓8號