GDB除錯彙總

查志強發表於2014-06-08

【原文:http://blog.chinaunix.net/uid-24174632-id-3435935.html

無論是多麼優秀的程式設計師,都難以保證自己在編寫程式碼時不會出現任何錯誤,因此除錯是軟體開發過程中的一個必不可少的組成部分。

      當程式完成編譯之後,它很可能無法正常執行,或者會徹底崩潰,或者不能實現預期的功能。此時如何通過除錯找到問題的癥結所在,就變成了擺在開發人員面前最嚴峻的問題。

      通常說來,軟體專案的規模越大,除錯起來就會越困難,越需要一個強大而高效的偵錯程式作為後盾。對於Linux程式設計師來講,目前可供 使用的偵錯程式非常多,GDB就是其中較為優秀的。 


 初識GDB 

      GDB 是自由軟體基金會(Free Software Foundation)的軟體工具之一。它的作用是協助程式設計師找到程式碼中的錯誤。如果沒有GDB的幫助,程式設計師要想跟蹤程式碼的執行流程,唯一的辦法就是新增大量的語句來產生特定的輸出。但這一手段本身就可能會引入新的錯誤,從而也就無法對那些導致程式崩潰的錯誤程式碼進行分析。

      GDB的出現減輕了開發人員的負擔,他們可以在程式執行的時候單步跟蹤自己的程式碼,或者通過斷點暫時中止程式的執行。此外,他們還能夠隨時察看變數和記憶體的當前狀態,並監視關鍵的資料結構是如何影響程式碼執行的。 

除錯方法 

       如果想對程式進行除錯,必須先在用GCC編譯原始碼時加上-g選項,以便產生GDB所需要的除錯符號資訊。例如,debugme.c是一個存在錯誤程式,可以使用如下的命令對其進行編譯,同時產生除錯符號:


    # gcc -g debugme.c -o debugme 

       如果願意的話,還可以在編譯時使用“-ggdb”選項來生成更多的除錯資訊。由於這些除錯資訊中的相當一部分是GDB所特有的,所以生成的程式碼將無法在其它偵錯程式中正常除錯。對於大多數情況來說,普通的-g選項就足夠了。

      需要注意的是,GCC雖然允許同時使用-g(除錯)和-o(優化)選項,但優化會影響最 終生成的程式碼,導致程式原始碼和二進位制程式碼之間的關係變得複雜起來如果不想為除錯製造障礙,建議不要將-g和-o選項一同使用,並且只在程式徹底除錯完 後才開始進行程式碼優化。這樣除錯過程將變得相對輕鬆和愉快。 

基本應用 

     現在可以啟動GDB來除錯已經生成的可執行程式debugme,命令如下: 

# gdb debugme 
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)

       如果一切正常,GDB將被啟動並在螢幕上輸出版權資訊,但如果使用了-q或--quiet選項則不會顯示它們。啟動GDB時另外一個有用的命令列選項是“-d dirname”,其中dirname是一個目錄名。該目錄名告訴GDB應該到哪裡去尋找原始碼。 
      一旦出現GDB的命令提示符(gdb),就表明GDB已經準備好接收來自使用者的各種除錯命令了。如果想在除錯環境下執行這個程式,可以使用GDB提供的 “run”命令,而程式在正常執行時所需的各種引數可以作為“run”命令的引數傳入,或者使用單獨的“set args”命令進行設定。如果在執行“run”命令時沒有給出任何引數,GDB將使用上一次“run”或“set args”命令指定的引數。如果想取消上次設定的引數,可以執行不帶任何引數的“set args”命令。下面嘗試在偵錯程式中執行這個程式: 

(gdb) run …… 
Program received signal SIGSEGV, Segmentation fault. 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2

      最後一行輸出表明程式在呼叫動態連結庫/lib/ld-linux.so.2中的_dl_fini() 函式時出現了錯誤,地址是0x4000c6ac。這些對除錯是非常重要的線索。另外還有一種資訊對除錯也很重要,就是錯誤發生時的函式呼叫層級關係,可以通過執行“backtrace”命令來獲得。
      在使用GDB除錯命令時,使用者可以不必輸入完整的命令名稱,使用任何惟一的縮寫都可以。例如 “backtrace”命令就可以縮寫成“back”甚至“bt”。GDB還支援很多常用的Shell命令編輯特徵,比如可以像在bash或tcsh中那 樣按Tab鍵補齊命令。如果相關命令不惟一的話,則列出所有可能的匹配項。此外來鍵盤上的方向鍵可用來翻動歷史命令。 

    GDB是一個原始碼級的偵錯程式,使用“list”命令可以檢視當前除錯物件的原始碼。該命令的通用格式為“list [m,n]”,表示顯示從m行開始到n行結束的程式碼段,而不帶任何引數的“list”命令將顯示最近10行原始碼。 

      設定斷點 
      在除錯有問題的程式碼時,在某一點停止執行往往很管用。這樣程式執行到此外時會暫時掛起,等待使用者的進一步輸入。GDB允許在幾種不同的程式碼結構上設定斷點,包括行號和函式名等,並且還允許設定條件斷點,讓程式只有在滿足一定的條件時才停止執行。要根據行號設定斷點,可以使用“ break linenum”命令要根據函式名設定斷點,則應該使用“break funcname”命令。 

      在以上兩種情況中,GDB將在執行指定的行號或進入指定的函式之前停止執行程式此時可以使用“print”顯示變數的值,或者使用“list”檢視將要執行的程式碼。對於由多個源文 件組成的專案,如果想在執行到非當前原始檔的某行或某個函式時停止執行,可以使用如下形式的命令: 

# break 20041126110727.htm:linenum 
# break 20041126110727.htm:funcname

      條件斷點允許當一定條件滿足時暫時停止程式的執行。它對於除錯來講非常有用。設定條件斷點的正確語法如下: 

break linenum if expr break funcname if expr

      其中expr是一個邏輯表示式。當該表示式的值為真時,程式將在該斷點處暫時掛起。例如,下面的命令將在debugme程式的第38行設定一個條件斷點。當程式執行到該行時,如果count的值等於3,就將暫時停止執行:
    
    (gdb) break 38 if count==3 

      設定斷點是除錯程式時最常用到的一種手段。它可以中斷程式的執行,給程式設計師一個單步跟蹤的機會。使用命令“ break main”在main函式上設定斷點可以在程式啟動時就開始進行跟蹤。 

       接下去使用“continue”命令繼續執行程式,直到遇到下一個斷點。如果在除錯時設定了很多斷點,可以隨時使用“info breakpoints”命令來檢視設定的斷點。此外,開發人員還可以使用“delete”命令刪除斷點,或者使用“disable”命令來使設定的斷點 暫時無效。被設定為無效的斷點在需要的時候可以用“enable”命令使其重新生效。 

      觀察變數 
      GDB 最有用的特性之一是能夠顯示被除錯程式中幾乎任何表示式、變數或陣列的型別和值,並且能夠用編寫程式所用的語言列印出任何合法表示式的值。檢視資料最簡單 的辦法是使用“print”命令,只需在“print”命令後面加上變數表示式,就可以列印出此變數表示式的當前值,示例如下: 

(gdb) print str 
$1 = 0x40015360 "Happy new year!/n"

      從輸出資訊中可以看出,輸入字串被正確地儲存在了字元指標str所指向的記憶體緩衝區中。除了給出變數 表示式的值外,“print”命令的輸出資訊中還包含變數標號($1)和對應的記憶體地址(0x40015360)。變數標號儲存著被檢查數值的歷史記錄,如果此後還想訪問這些值,就可以直接使用別名而不用重新輸入變數表示式。 

      如果想知道變數的型別,可以使用“whatis”命令,示例如下: 

(gdb) whatis str 
type = char *

      對於第一次除錯別人的程式碼,或者面對的是一個異常複雜的系統時,“whatis”命令的作用不容忽視。 

     單步執行 
     為了單步跟蹤程式碼,可以使用單步跟蹤命令“step”,它每次執行原始碼中的一行。 

     在GDB中可以使用許多方法來簡化操作,除了可以將“step”命令簡化為“s”之外,還可以直接輸入Enter鍵來重複執行前面一條命令。 

     除了可以用“step”命令來單步執行程式之外,GDB還提供了另外一條單步除錯命令“next”。兩者功能非常相似,差別在於如果將要被執行的程式碼行中包含函式呼叫,使用step命令將跟蹤進入函式體內,而使用next命令則不進入函式體內。 

    在進入下一部分之前,使用下面的命令退出GDB:
    (gdb) quit 

      分析核心(core)檔案 

      在程式發生崩潰時,有時可能無法直接執行GDB來進行除錯。比如程式可能是在另外一臺機器上執行的,或者因為程式對時間比較敏感,所以手動跟蹤除錯會產生無法接受的延遲等。遇到這些情況,就只能等到程式執行結束後才能判斷崩潰的原因了。這時需要用到Linux提供的core dump機制。當程式中出現記憶體操作錯誤時,會發生崩潰併產生核心檔案。使用GDB可以對產生的核心檔案進行分析,找出程式是在什麼時候崩潰的和在崩潰之 前程式都做了些什麼。當然,如果要用GDB來分析核心檔案,也必須在編譯時加上-g選項來產生除錯符號表。 

      在分析核心檔案之前必須確認系統是否允許生成核心檔案,很多Linux發行版在預設時禁止生成核心檔案。為了生成核心檔案,首先必須執行下面的命令:
    
    # ulimit -c unlimited 

      然後就可以生成核心檔案了。這裡仍以前面的debugme程式為例,再次執行下面命令將產生核心檔案: 

# ./debugme 
Enter a string to count words:Happy new year! 
The number of words is 3. 
Segmentation fault (core dumped)

      生成的核心檔名根據系統配置的不同會有所差異。要在GDB中分析核心檔案,除了要給出核心檔案的檔名外,還必須給出生成該核心檔案的可執行程式的名稱,示例如下: 

#gdb debugme core.547 
…… Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. ……

       從GDB的輸出資訊中可以看出,產生這個核心檔案的原因是因為程式收到了序號為11的訊號。如果想知道程式在崩潰之前執行到了哪裡,可以使用“backtrace”或“info stack”命令檢視一下堆疊的歷史記錄。示例如下: 

(gdb) info stack 
#0 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2 
#1 0x40057940 in exit () from /lib/libc.so.6 
#2 0x4004291f in _libc_start_main () from /lib/libc.so.6

      由上可知,程式崩潰時正處於_dl_fini()函式之中。但很多時候程式設計師感興趣的可能並不是這個, 而是exit()或_libc_start_main()函式,因為它們才可能是問題真正的癥結所在。GDB提供的“frame”命令可以用來在不同的調 用上下文中切換。例如下面的命令可以檢視exit()函式在執行時的狀況: 

(gdb) frame 1 
#1 0x40057940 in exit () from /lib/libc.so.6

      此外還可以用“up”或“down”命令在不同的函式呼叫上下文中切換。開發人員使用這三條命令可以很輕鬆地實現呼叫棧的遍歷。在分析核心檔案時,通過將遍歷棧的命令和檢查變數值的“print”命令結合起來,就能夠復原程式執行時的全部景象。 

      除錯其它程式 

      有時會遇到一種很特殊的除錯需求,對當前正在執行的其它程式進行除錯。這種情況有可能發生在那些無法直接在偵錯程式中執行的程式身上,例如有的程式只能在系統 啟動時執行。另外如果需要對程式產生的子程式進行除錯的話,也只能採用這種方式。GDB可以對正在執行的程式進行排程,它允許開發人員中斷程式並檢視其狀 態,之後還能讓這個程式正常地繼續執行。 

       GDB提供了兩種方式來除錯正在執行的程式:一種是在GDB命令列上指定程式的PID,另一種是在GDB中使用“attach”命令。例如,開發人員可以先啟動debugme程式,讓其開始等待使用者的輸入。示例如下: 

#./debugme 
Enter a string to count words:

      接下去在另一個虛擬控制檯中用下面的命令查出該程式對應的程式號: 

# ps -ax | grep debugme 555 
pts/1 S 0:00 ./debugme

      得到程式的PID後,就可以使用GDB對其進行除錯了: 

# gdb debugme 555 
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) Attaching to program: /home/xiaowp/debugme, process 555 Reading symbols from /lib/libc.so.6...done. ……

      在上面的輸出資訊中,以Attaching to program開始的行表明GDB已經成功地附加在PID為555的程式上了。另外一種連線到其它程式的方法是先用file命令載入除錯時所需的符號表,然後再通過“attaché”命令進行連線: 

(gdb) file /home/xiaowp/debugme 
Reading symbols from /home/xiaowp/debugme...done. (gdb) attach 555 ……

      如果想知道程式現在執行到了哪裡,同樣可以使用“backtrace”命令。當然也可以使用“step”命令對程式進行單步除錯。 

      在完成除錯之後,不要忘記用detach命令斷開連線,讓被除錯的程式可以繼續正常執行: 

      GDB是Linux下一個最基本的偵錯程式,其功能非常豐富。完整地介紹GDB的功能可能需要幾百頁,本文只涵蓋了GDB的一些最常見的用法。作為一個合格的Linux程式設計師,花在GDB上的功夫和時間越多,從除錯中獲得的益處就越多。

 



****************************************************************************

      Linux 包含了一個叫 gdb 的 GNU 除錯程式. gdb 是一個用來除錯 C 和 C++ 程式的強力偵錯程式. 它使你能在程式執行時觀察程式的內部結構和記憶體的使用情況. 以下是 gdb 所提供的一些功能: 
  • 它使你能監視你程式中變數的值. 
  • 它使你能設定斷點以使程式在指定的程式碼行上停止執行. 
  • 它使你能一行行的執行你的程式碼. 
      在命令列上鍵入 gdb 並按Enter鍵就可以執行 gdb 了, 如果一切正常的話, gdb 將被啟動並且你將在螢幕上看到類似的內容: 
GDB is free software and you are welcome to 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. 
GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc. 
(gdb) 
       當你啟動 gdb 後, 你能在命令列上指定很多的選項. 你也可以以下面的方式來執行 gdb : gdb <fname> 
      
       當你用這種方式執行gdb , 你能直接指定想要除錯的程式. 這將告訴gdb裝入名為 fname 的可執行檔案. 你也可以用 gdb 去檢查一個因程式異常終止而產生的 core 檔案, 或者與一個正在執行的程式相連. 你可以參考 gdb 指南頁或在命令列上鍵入 gdb -h 得到一個有關這些選項的說明的簡單列表。

      為除錯編譯程式碼(Compiling Code for Debugging) 
      為了使 gdb 正常工作, 你必須使你的程式在編譯時包含除錯資訊. 除錯資訊包含你程式裡的每個變數的型別和在可執行檔案裡的地址對映以及原始碼的行號. gdb 利用這些資訊使原始碼和機器碼相關聯. 
      在編譯時用 -g 選項開啟除錯選項. 


gdb 基本命令 
      gdb 支援很多的命令使你能實現不同的功能. 這些命令從簡單的檔案裝入到允許你檢查所呼叫的堆疊內容的複雜命令, 表27.1列出了你在用 gdb 除錯時會用到的一些命令. 想了解 gdb 的詳細使用請參考 gdb 的指南頁. 
     
命令描述 
file 裝入想要除錯的可執行檔案. 
kill 終止正在除錯的程式. 
list 列出產生執行檔案的原始碼的一部分. 
next 執行一行原始碼但不進入函式內部. 
step 執行一行原始碼而且進入函式內部. 
run 執行當前被除錯的程式 
quit 終止 gdb 
watch 使你能監視一個變數的值而不管它何時被改變. 
break 在程式碼裡設定斷點, 這將使程式執行到這裡時被掛起. 
make 使你能不退出 gdb 就可以重新產生可執行檔案. 
shell 使你能不離開 gdb 就執行 UNIX shell 命令. 

      gdb 支援很多與 UNIX shell 程式一樣的命令編輯特徵. 你能象在 bash 或 tcsh裡那樣按 Tab 鍵讓 gdb 幫你補齊一個唯一的命令, 如果不唯一的話 gdb 會列出所有匹配的命令. 你也能用游標鍵上下翻動歷史命令. 

gdb 應用舉例 
       本節用一個例項教你一步步的用 gdb 除錯程式. 被除錯的程式相當的簡單, 但它展示了 gdb 的典型應用. 

      下面列出了將被除錯的程式. 這個程式被稱為 greeting , 它顯示一個簡單的問候, 再用反序將它列出.

  1. #include <stdio.h> 
  2. main () 
  3. { 
  4.      char my_string[] = "hello there"; 
  5.      my_print (my_string); 
  6.      my_print2 (my_string); 
  7. } 

  8. void my_print (char *string) 
  9. { 
  10.      printf ("The string is %s/n", string); 
  11. } 

  12. void my_print2 (char *string) 
  13. { 
  14.      char *string2; 
  15.      int size, i; 

  16.      size = strlen (string); 
  17.      string2 = (char *) malloc (size + 1); 
  18.      for (= 0; i < size; i++) 
  19.            string2[size - i] = string[i]; 
  20.      string2[size+1] = `/0'; 
  21.      printf ("The string printed backward is %s/n", string2); 
  22. }
用下面的命令編譯它: 
gcc -o test test.c 

這個程式執行時顯示如下結果: 
The string is hello there 
The string printed backward is 

輸出的第一行是正確的, 但第二行列印出的東西並不是我們所期望的. 我們所設想的輸出應該是: 
The string printed backward is ereht olleh 

由於某些原因, my_print2 函式沒有正常工作. 讓我們用 gdb 看看問題究竟出在哪兒, 先鍵入如下命令: 
gdb greeting 
-------------------------------------------------------------------------------- 
注意: 記得在編譯 greeting 程式時把除錯選項開啟. 
-------------------------------------------------------------------------------- 
如果你在輸入命令時忘了把要除錯的程式作為引數傳給 gdb , 你可以在 gdb 提示符下用 file 命令來載入它: 
(gdb) file greeting 

這個命令將載入 greeting 可執行檔案就象你在 gdb 命令列裡裝入它一樣. 
這時你能用 gdb 的 run 命令來執行 greeting 了. 當它在 gdb 裡被執行後結果大約會象這樣: 
(gdb) run 
Starting program: /root/greeting 
The string is hello there 
The string printed backward is 
Program exited with code 041 

這個輸出和在 gdb 外面執行的結果一樣. 問題是, 為什麼反序列印沒有工作? 為了找出癥結所在, 我們可以在 my_print2 函式的 for 語句後設一個斷點, 具體的做法是在 gdb 提示符下鍵入 list 命令三次, 列出原始碼: 
(gdb) list 

(gdb) list 

(gdb) list 
-------------------------------------------------------------------------------- 
技巧: 在 gdb 提示符下按回車健將重複上一個命令. 
-------------------------------------------------------------------------------- 

第一次鍵入 list 命令的輸出如下: 
1 #include <stdio.h> 

3 void main () 
4 { 
5      char my_string[] = "hello there"; 

7      my_print (my_string); 
8      my_print2 (my_string); 
9 } 
10 
如果按下回車, gdb 將再執行一次 list 命令, 給出下列輸出: 
11 my_print (char *string) 
12 { 
13 printf ("The string is %s/n", string); 
14 } 
15 
16 my_print2 (char *string) 
17 { 
18 char *string2; 
19 int size, i; 
20 
再按一次回車將列出 greeting 程式的剩餘部分: 
21 size = strlen (string); 
22 string2 = (char *) malloc (size + 1); 
23 for (i = 0; i < size; i++) 
24 string2[size - i] = string[i]; 
25 string2[size+1] = `/0'; 
26 printf ("The string printed backward is %s/n", string2); 
27 } 
根據列出的源程式, 你能看到要設斷點的地方在第24行, 在 gdb 命令列提示符下鍵入如下命令設定斷點: 
(gdb) break 24 
gdb 將作出如下的響應: 
Breakpoint 1 at 0x139: file greeting.c, line 24 
(gdb) 
現在再鍵入 run 命令, 將產生如下的輸出: 
Starting program: /root/greeting 
The string is hello there 
Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24 
24 string2[size-i]=string[i] 

你能通過設定一個觀察 string2[size - i] 變數的值的觀察點來看出錯誤是怎樣產生的, 做法是鍵入: 
(gdb) watch string2[size - i] 

gdb 將作出如下回應: 
Watchpoint 2: string2[size - i] 

現在可以用 next 命令來一步步的執行 for 迴圈了: 
(gdb) next 
經過第一次迴圈後, gdb 告訴我們 string2[size - i] 的值是 `h`. gdb 用如下的顯示來告訴你這個資訊: 
Watchpoint 2, string2[size - i] 
Old value = 0 `/000' 
New value = 104 `h' 
my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23 
23 for (i=0; i<size; i++) 

這個值正是期望的. 後來的數次迴圈的結果都是正確的. 當 i=10 時, 表示式 string2[size - i] 的值等於 `e`, size - i 的值等於 1, 最後一個字元已經拷到新串裡了. 
如 果你再把迴圈執行下去, 你會看到已經沒有值分配給 string2[0] 了, 而它是新串的第一個字元, 因為 malloc 函式在分配記憶體時把它們初始化為空(null)字元. 所以 string2 的第一個字元是空字元. 這解釋了為什麼在列印 string2 時沒有任何輸出了. 

現在找出了問題出在哪裡, 修正這個錯誤是很容易的. 你得把程式碼裡寫入 string2 的第一個字元的的偏移量改為 size - 1 而不是 size. 這是因為 string2 的大小為 12, 但起始偏移量是 0, 串內的字元從偏移量 0 到 偏移量 10, 偏移量 11 為空字元保留. 

為了使程式碼正常工作有很多種修改辦法. 一種是另設一個比串的實際大小小 1 的變數. 這是這種解決辦法的程式碼: 
#include <stdio.h> 
void main () 

      char my_string[] = "hello there"; 
      my_print (my_string); 
      my_print2 (my_string); 


my_print (char *string) 

      printf ("The string is %s/n", string); 


my_print2 (char *string) 

     char *string2; 
     int size, size2, i; 

     size = strlen (string); 
     size2 = size -1; 
     string2 = (char *) malloc (size + 1); 
     for (i = 0; i < size; i++) 
          string2[size2 - i] = string[i]; 
     string2[size] = `/0'; 
     printf ("The string printed backward is %s/n", string2); 



在Linux下除錯程式一般用GDB來執行。
  這裡簡要介紹一下是否gdb除錯程式的方法:
  (1)進入gdb除錯:
  gdb + 已經編譯通過的可執行程式 -》 就進入除錯模式。
      例如:gdb MiddlePublisher
  (2)r + 執行時的引數 -》 開始執行可執行程式。例如 r -lxml2 -f refile
    (3)b + 斷點 -》設定除錯的斷點。兩種:
       一種是:b CMSTask.cpp:200 表示在CMSTask.cpp檔案的第200行設定斷點。      
       另一種:b TaskManager::buildPubWinTask 表示在執行buildPubWinTask這個函式的時候停止。
  (4)取消斷點:
        dis 1 表示取消第一個斷點
        dis 2 表示取消第二個斷點
  (5)檢視設定斷點資訊: info b
  (6)在斷點停止處檢視所在程式碼的詳細資訊:l
  (7)可以在gdb中直接編譯,然後再重新執行時,gdb會直接執行新編譯好的可執行程式。
       例如:直接在gdb下執行make後再重新執行。
  (8)跟進一個函式:s
   如果設定的斷點是在一個函式入口。到達該斷點時,鍵入s就可以進入該函式內部進行除錯。如果有多個函式就多次鍵入S來進入內部的函式。
  PS:
  1、在SecureCRT遠端登入介面上開啟多個視窗。在視窗之間切換時用:Alt+1,Alt+2.....表示切換到第1個,第2個視窗。
  2、同樣在在SecureCRT遠端登入介面上要貼上複製好的內容用:Shift+Insert。


檢查一切 
memcpy, strcpy, strcat sprintf 動態陣列下標。 
這種問題多半世記憶體訪問錯誤或者緩衝區溢位覆蓋堆疊造成的。 
除錯方法: 
gdb 除錯程式或者gdb除錯core檔案  
  
編譯時加入-g除錯選項,去掉-Ox選項 
使用gdb執行,如果中斷退出,使用bt命令檢視呼叫堆疊,如果不是可以 
通過thr n (n表示執行緒號,用 info thr檢視)切換,然後bt看堆疊 
以上方法在kernel 2.6+gdb 6中有問題 

一:列檔案清單     
  1.   List     
  (gdb)   list   line1,line2     
    
二:執行程式     
      要想執行準備除錯的程式,可使用run命令,在它後面可以跟隨發給該程式的任何引數,包括標準輸入和標準輸出說明符(<和>)和外殼萬用字元(*、?、[、])在內。     
      如果你使用不帶引數的run命令,gdb就再次使用你給予前一條run命令的引數,這是很有用的。     
      利用set   args   命令就可以修改傳送給程式的引數,而使用show   args   命令就可以檢視其預設引數的列表。     
    (gdb)set   args   –b   –x     
     (gdb)   show   args     
     backtrace命令為堆疊提供向後跟蹤功能。     
     backtrace命令產生一張列表,包含著從最近的過程開始的所以有效過程和呼叫這些過程的引數。     
    
三:顯示資料     
     利用print命令可以檢查各個變數的值。     
    (gdb)   print   p   (p為變數名)     
    whatis   命令可以顯示某個變數的型別     
    (gdb)   whatis   p     
    type   =   int   *     
    
      print是gdb的一個功能很強的命令,利用它可以顯示被除錯的語言中任何有效的表示式。表示式除了包含你程式中的變數外,還可以包含以下內容:     
  l   對程式中函式的呼叫     
  (gdb)   print   find_entry(1,0)     
  l   資料結構和其他複雜物件     
  (gdb)   print   *table_start     
  $8={e=reference=’/000’,location=0x0,next=0x0}     
  l   值的歷史成分     
  (gdb)print   $1   ($1為歷史記錄變數,在以後可以直接引用   $1   的值)     
  l   人為陣列     
     人為陣列提供了一種去顯示儲存器塊(陣列節或動態分配的儲存區)內容的方法。早期的除錯程式沒有很好的方法將任意的指標換成一個陣列。就像對待引數一樣,讓我們檢視記憶體中在變數h後面的10個整數,一個動態陣列的語法如下所示:     
  base@length      
  因此,要想顯示在h後面的10個元素,可以使用h@10 :     
  (gdb)print   h@10      
  $13=(-1,345,23,-234,0,0,0,98,345,10)     
    
四:斷點(breakpoint)     
  break命令(可以簡寫為b)可以用來在除錯的程式中設定斷點,該命令有如下四種形式:     
  l   break   line-number   使程式恰好在執行給定行之前停止。     
  l   break   function-name   使程式恰好在進入指定的函式之前停止。     
  l   break   line-or-function   if   condition   如果condition(條件)是真,程式到達指定行或函式時停止。     
  l   break   routine-name   在指定例程的入口處設定斷點     
    
  如果該程式是由很多原檔案構成的,你可以在各個原檔案中設定斷點,而不是在當前的原檔案中設定斷點,其方法如下:     
  (gdb)   break   filename:line-number     
  (gdb)   break   filename:function-name     
    
  要想設定一個條件斷點,可以利用break   if命令,如下所示:     
  (gdb)   break   line-or-function   if   expr     
  例:     
  (gdb)   break   46   if   testsize==100     
    
  從斷點繼續執行:countinue   命令     
  
五.斷點的管理     
    
   1. 顯示當前gdb的斷點資訊:     
  (gdb)   info   break     
  他會以如下的形式顯示所有的斷點資訊:     
  Num   Type   Disp   Enb   Address   What     
  1   breakpoint   keep   y   0x000028bc   in   init_random   at   qsort2.c:155     
  2   breakpoint   keep   y   0x0000291c   in   init_organ   at   qsort2.c:168     
  (gdb)     
  
  2. 刪除指定的某個斷點:     
  (gdb)   delete   breakpoint   1     
  該命令將會刪除編號為1的斷點,如果不帶編號引數,將刪除所有的斷點     
  (gdb)   delete   breakpoint     
  
   3.禁止使用某個斷點     
  (gdb)   disable   breakpoint   1     
  該命令將禁止斷點   1,同時斷點資訊的   (Enb)域將變為   n     
  
   4.允許使用某個斷點     
  (gdb)   enable   breakpoint   1     
  該命令將允許斷點   1,同時斷點資訊的   (Enb)域將變為   y     
  
   5.清除原檔案中某一程式碼行上的所有斷點     
  (gdb)clean   number     
  
  注:number為原檔案的某個程式碼行的行號     
  
六.變數的檢查和賦值     
  l   whatis:識別陣列或變數的型別     
  l   ptype:比whatis的功能更強,他可以提供一個結構的定義     
  l   set   variable:將值賦予變數     
  l   print   除了顯示一個變數的值外,還可以用來賦值     

七.單步執行     
  l   next     
  不進入的單步執行     
  l   step     
  進入的單步執行     
  如果已經進入了某函式,而想退出該函式返回到它的呼叫函式中,可使用命令finish     
  
八.函式的呼叫     
  l   call   name   呼叫和執行一個函式     
  (gdb)   call   gen_and_sork(   1234,1,0   )     
  (gdb)   call   printf(“abcd”)     
  $1=4     
  l   finish   結束執行當前函式,顯示其返回值(如果有的話)     
    
  
九.機器語言工具     
      有一組專用的gdb變數可以用來檢查和修改計算機的通用暫存器,gdb提供了目前每一臺計算機中實際使用的4個暫存器的標準名字:     
  l   $pc   :   程式計數器     
  l   $fp   :   幀指標(當前堆疊幀)     
  l   $sp   :   棧指標     
  l   $ps   :   處理器狀態     
    
  
十.訊號     
      gdb 通常可以捕捉到傳送給它的大多數訊號,通過捕捉訊號,它就可決定對於正在執行的程式要做些什麼工作。例如,按CTRL-C將中斷訊號傳送給gdb, 通常就會終止gdb。但是你或許不想中斷gdb,真正的目的是要中斷gdb正在執行的程式,因此,gdb要抓住該訊號並停止它正在執行的程式,這樣就可以 執行某些除錯操作。     
    
      Handle命令可控制訊號的處理,他有兩個引數,一個是訊號名,另一個是接受到訊號時該作什麼。幾種可能的引數是:     
  l   nostop   接收到訊號時,不要將它傳送給程式,也不要停止程式。     
  l   stop   接受到訊號時停止程式的執行,從而允許程式除錯;顯示一條表示已接受到訊號的訊息(禁止使用訊息除外)     
  l   print   接受到訊號時顯示一條訊息     
  l   noprint   接受到訊號時不要顯示訊息(而且隱含著不停止程式執行)     
  l   pass   將訊號傳送給程式,從而允許你的程式去處理它、停止執行或採取別的動作。     
  l   nopass   停止程式執行,但不要將訊號傳送給程式。     
      
      例如,假定你截獲SIGPIPE訊號,以防止正在除錯的程式接受到該訊號,而且只要該訊號一到達,就要求該程式停止,並通知你。要完成這一任務,可利用如下命令:     
  (gdb)   handle   SIGPIPE   stop   print     
  
      請注意,UNIX的訊號名總是採用大寫字母!你可以用訊號編號替代訊號名     
      如果你的程式要執行任何訊號處理操作,就需要能夠測試其訊號處理程式,為此,就需要一種能將訊號傳送給程式的簡便方法,這就是signal命令的任務。 該   命令的引數是一個數字或者一個名字,如SIGINT。假定你的程式已將一個專用的SIGINT(鍵盤輸入,或CTRL-C;訊號2)訊號處理程式設定成 採   取某個清理動作,要想測試該訊號處理程式,你可以設定一個斷點並使用如下命令:     
  (gdb)   signal   2     
  continuing   with   signal   SIGINT(2)     
      該程式繼續執行,但是立即傳輸該訊號,而且處理程式開始執行.     
    
十一.   原檔案的搜尋     
  search   text:該命令可顯示在當前檔案中包含text串的下一行。     
  Reverse-search   text:該命令可以顯示包含text   的前一行。     
    
十二.UNIX介面     
  shell   命令可啟動UNIX外殼,CTRL-D退出外殼,返回到   gdb.     
    
十三.命令的歷史     
  為了允許使用歷史命令,可使用   set   history   expansion   on   命令     
  (gdb)   set   history   expansion   on     
    
  小結:常用的gdb命令     
  backtrace   顯示程式中的當前位置和表示如何到達當前位置的棧跟蹤(同義詞:where)     
  breakpoint   在程式中設定一個斷點     
  cd   改變當前工作目錄     
  clear   刪除剛才停止處的斷點     
  commands   命中斷點時,列出將要執行的命令     
  continue   從斷點開始繼續執行     
  delete   刪除一個斷點或監測點;也可與其他命令一起使用     
  display   程式停止時顯示變數和表達時     
  down   下移棧幀,使得另一個函式成為當前函式     
  frame   選擇下一條continue命令的幀     
  info   顯示與該程式有關的各種資訊     
  jump   在源程式中的另一點開始執行     
  kill   異常終止在gdb   控制下執行的程式     
  list   列出相應於正在執行的程式的原檔案內容     
  next   執行下一個源程式行,從而執行其整體中的一個函式     
  print   顯示變數或表示式的值     
  pwd   顯示當前工作目錄     
  pype   顯示一個資料結構(如一個結構或C++類)的內容     
  quit   退出gdb     
  reverse-search   在原始檔中反向搜尋正規表示式     
  run   執行該程式     
  search   在原始檔中搜尋正規表示式     
  set   variable   給變數賦值     
  signal   將一個訊號傳送到正在執行的程式     
  step   執行下一個源程式行,必要時進入下一個函式     
  undisplay   display命令的反命令,不要顯示錶達式     
  until   結束當前迴圈     
  up   上移棧幀,使另一函式成為當前函式     
  watch   在程式中設定一個監測點(即資料斷點)     
  whatis   顯示變數或函式型別     

  **************************************************** ********************   
GNU的偵錯程式稱為gdb,該程式是一個互動式工具,工作在字元模式。     
      在X  Window   系統中,有一個gdb的前端圖形工具,稱為xxgdb。gdb   是功能強大的除錯程式,可完成如下的除錯任務:     
    *   設定斷點;     
    *   監視程式變數的值;     
    *   程式的單步執行;     
    *   修改變數的值。     
    在可以使用   gdb   除錯程式之前,必須使用   -g   選項編譯原始檔。可在makefile   中如下定義CFLAGS變數:     
       CFLAGS   =   -g     
       執行   gdb   除錯程式時通常使用如下的命令:     
       gdb   progname     
    
    在   gdb   提示符處鍵入help,將列出命令的分類,主要的分類有:     
    *   aliases:命令別名     
    *   breakpoints:斷點定義;     
    *   data:資料檢視;     
    *   files:指定並檢視檔案;     
    *   internals:維護命令;     
    *   running:程式執行;     
    *   stack:呼叫棧檢視;     
    *   statu:狀態檢視;     
    *   tracepoints:跟蹤程式執行。     
    鍵入   help   後跟命令的分類名,可獲得該類命令的詳細清單。     
    
gdb的常用命令     
     命令解釋     
    break   NUM   在指定的行上設定斷點。     
    bt   顯示所有的呼叫棧幀。該命令可用來顯示函式的呼叫順序。     
    clear   刪除設定在特定原始檔、特定行上的斷點。其用法為clear  FILENAME:NUM     
    continue   繼續執行正在除錯的程式。該命令用在程式由於處理訊號或斷點而  導致停止執行時。     
    display   EXPR   每次程式停止後顯示錶達式的值。表示式由程式定義的變數組成。     
    file   FILE   裝載指定的可執行檔案進行除錯。     
    help   NAME   顯示指定命令的幫助資訊。     
    info   break   顯示當前斷點清單,包括到達斷點處的次數等。     
    info   files   顯示被除錯檔案的詳細資訊。     
    info   func   顯示所有的函式名稱。     
    info   local   顯示當函式中的區域性變數資訊。     
    info   prog   顯示被除錯程式的執行狀態。     
    info   var   顯示所有的全域性和靜態變數名稱。     
    kill   終止正被除錯的程式。     
    list   顯示原始碼段。     
    make   在不退出   gdb   的情況下執行   make   工具。     
    next   在不單步執行進入其他函式的情況下,向前執行一行原始碼。     
    print   EXPR   顯示錶達式   EXPR   的值。     
    
  ******gdb使用範例************************     
  -----------------     
  清單   一個有錯誤的   C   源程式   bugging.c     
  程式碼:     
    
  -----------------     
  1 #include   <stdio.h>  
  2     
  3 static   char   buff   [256];     
  4 static   char*   string;     
  5 int   main   ()     
  6 {     
  7   printf   ("Please   input   a   string:   ");     
  8   gets   (string);       
  9     printf   ("/nYour   string   is:   %s/n",   string);     
  10   }     
  
  -----------------     
    上面這個程式非常簡單,其目的是接受使用者的輸入,然後將使用者的輸入列印出來。該程式使用了一個未經過初始化的字串地址string,因此,編譯並執行之後,將出現   Segment   Fault   錯誤:     
  $   gcc   -o   bugging   -g   bugging.c     
  $   ./bugging     
  Please   input   a   string:   asfd     
  Segmentation   fault   (core   dumped)     
  為了查詢該程式中出現的問題,我們利用   gdb,並按如下的步驟進行:     
  1.執行   gdb   bugging   命令,裝入   bugging   可執行檔案;     
  2.執行裝入的   bugging   命令   run;     
  3.使用   where   命令檢視程式出錯的地方;     
  4.利用   list   命令檢視呼叫   gets   函式附近的程式碼;     
  5.唯一能夠導致   gets   函式出錯的因素就是變數   string。用print命令檢視  string   的值;     
  6.在   gdb   中,我們可以直接修改變數的值,只要將   string   取一個合法的指標值就可以了,為此,我們在第8行處設定斷點   break   8;     
  7.程式重新執行到第   8行處停止,這時,我們可以用   set   variable   命令修改  string   的取值;     
  8.然後繼續執行,將看到正確的程式執行結果     
  回覆   更多評論 
  
#   re: 程式除錯的利器GDB 2008-07-27 03:22 聶文龍
gdb除錯程式已經run了

動態的增加斷點
 
ctrl-z掛起 設定短點 fg喚醒
  回覆   更多評論 
  
#   re: 程式除錯的利器GDB 2008-07-27 03:22 聶文龍
d 空格加斷點num,是去斷點的,c是跳到下一個斷點 

跳出函式,相當於step over :finish. 
  回覆   更多評論 
  
#   re: 程式除錯的利器GDB 2008-07-27 03:43 聶文龍

gdb對於多執行緒程式的除錯有如下的支援:

  • 執行緒產生通知:在產生新的執行緒時, gdb會給出提示資訊

(gdb) r
Starting program: /root/thread
[New Thread (LWP 12900)]
[New Thread (LWP 12907)]—以下三個為新產生的執行緒 
[New Thread (LWP 12908)]
[New Thread (LWP 12909)]

  • 檢視執行緒:使用info threads 可以檢視執行的執行緒。

(gdb) info threads
4 Thread (LWP 12940 ) 0xffffe002 in ?? ()
3 Thread (LWP 12939) 0xffffe002 in ?? ()
2 Thread (LWP 12938) 0xffffe002 in ?? ()
* 1 Thread (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
      注意,行首的藍色 文字為gdb分配的執行緒號,對執行緒進行切換時,使用該該號碼,而不是上文標出的綠色數字。

另外,行首的紅色星號標識了當前活動的執行緒

  • 切換執行緒:使用 thread THREADNUMBER 進行切換, T HREADNUMBER 為上文提到的執行緒號。下例顯示將活動執行緒從 1 切換至 4。

(gdb) info threads
4 Thread (LWP 12940) 0xffffe002 in ?? ()
3 Thread (LWP 12939) 0xffffe002 in ?? ()
2 Thread (LWP 12938) 0xffffe002 in ?? ()
* 1 Thread (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb) thread 4
[Switching to thread 4 (Thread (LWP 12940))]#0 0xffffe002 in ?? ()
(gdb) info threads
* 4 Thread (LWP 12940) 0xffffe002 in ?? ()
3 Thread (LWP 12939) 0xffffe002 in ?? ()
2 Thread (LWP 12938) 0xffffe002 in ?? ()
1 Thread (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)


      以上即為使用gdb提供的對多執行緒進行除錯的一些基本命令。另外,gdb也提供對執行緒的斷點設定以及對指定或所有執行緒釋出命令的命令。

初次接觸gdb下多執行緒的除錯,往往會忽視gdb中活動執行緒的概念。一般來講,在使用gdb除錯的時候,只有一個執行緒為活動執行緒,如果希望得到其他的執行緒的輸出結果,必須使用thread命令切換至指定的執行緒,才能對該執行緒進行除錯或觀察輸出結果。

  回覆   更多評論 
  
#   re: 程式除錯的利器GDB 2008-07-27 03:47 聶文龍
      在linux環境下除錯多執行緒,總覺得不像.NET那麼方便。這幾天就為找一個死鎖的bug折騰好久,介紹一下用過的方法吧。

      多執行緒如果dump,多為段錯誤,一般都涉及記憶體非法讀寫。可以這樣處理,使用下面的命令開啟系統開關,讓其可以在死掉的時候生成core檔案。   
ulimit -c unlimited 
這樣的話死掉的時候就可以在當前目錄看到core.pid(pid為程式號)的檔案。接著使用gdb:
gdb ./bin ./core.pid 
進去後,使用bt檢視死掉時棧的情況,在使用frame命令。

      還有就是裡面某個執行緒停住,也沒死,這種情況一般就是死鎖或者涉及訊息接受的超時問題(聽人說的,沒有遇到過)。遇到這種情況,可以使用:
gcore pid (除錯程式的pid號)
      
      手動生成core檔案,在使用pstack(linux下好像不好使)檢視堆疊的情況。如果都看不出來,就仔細檢視程式碼,看看是不是在if,return,break,continue這種語句操作是忘記解鎖,還有巢狀鎖的問題,都需要分析清楚了。

最後,說一句,靜心看程式碼,捶胸頓足是沒有用的。
  回覆   更多評論 
  
#   re: 程式除錯的利器GDB  2008-07-27 04:03 聶文龍


GDB 概述 
————

      GDB 是 GNU 開源 組織釋出 的一個強大的 UNIX 下的程式除錯工具。或許,各位比較喜歡那種圖形介面方式的,像VC 、 BCB 等 IDE 的除錯,但如果你是在 UNIX 平臺下做軟體,你會發現 GDB 這個除錯工具有比 VC 、 BCB 的圖形化偵錯程式更強大的功能。所謂 “ 寸有所長,尺有所短 ” 就是這個道理。

一般來說, GDB 主要幫忙你完成下面四個方面的功能:

    1 、啟動你的程式,可以按照你的自定義的要求隨心所欲的執行程式。 
    2 、可讓被除錯的程式在你所指定的調置的斷點處停住。(斷點可以是條件表示式) 
    3 、當程式被停住時,可以檢查此時你的程式中所發生的事。 
    4 、動態的改變你程式的執行環境。

從上面看來, GDB 和一般的除錯工具沒有什麼兩樣,基本上也是完成這些功能,不過在細節上,你會發現 GDB 這個除錯工具的強大,大家可能比較習慣了圖形化的除錯工具,但有時候,命令列的除錯工具卻有著圖形化工具所不能完成的功能。讓我們一一看來。


一個除錯示例 
——————

源程式: tst.c

     1 #include <stdio.h>
     2
     3 int func(int n)
     4 {
     5         int sum=0,i;
     6         for(i=0; i<n; i++)
     7         {
     8                 sum+=i;
     9         }
    10         return sum;
    11 }
    12
    13
    14 int main()
    15 {
    16         int i;
    17         long result = 0;
    18         for(i=1; i<=100; i++)
    19         {
    20                 result += i;
    21         }
    22
    23        printf("result[1-100] = %d /n", result );
    24        printf("result[1-250] = %d /n", func(250) );
    25 }

編譯生成執行檔案:( Linux 下) 
    hchen/test> cc -g tst.c -o tst

使用 GDB 除錯:

hchen/test> gdb tst  <---------- 啟動 GDB
GNU gdb 5.1.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 "i386-suse-linux"...
(gdb) l     <-------------------- l 命令相當於 list ,從第一行開始例出原碼。 
1        #include <stdio.h>
2
3        int func(int n)
4        {
5                int sum=0,i;
6                for(i=0; i<n; i++)
7                {
8                        sum+=i;
9                }
10               return sum;
(gdb)       <-------------------- 直接回車表示,重複上一次命令 
11       }
12
13
14       main()
15       {
16               int i;
17               long result = 0;
18               for(i=1; i<=100; i++)
19               {
20                       result += i;   
(gdb) break 16    <-------------------- 設定斷點,在源程式第 16 行處。 
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func  <-------------------- 設定斷點,在函式 func() 入口處。 
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break  <-------------------- 檢視斷點資訊。 
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048496 in main at tst.c:16
2   breakpoint     keep y   0x08048456 in func at tst.c:5
(gdb) r           <--------------------- 執行程式, run 命令簡寫 
Starting program: /home/hchen/test/tst

Breakpoint 1, main () at tst.c:17    <---------- 在斷點處停住。 
17               long result = 0;
(gdb) n          <--------------------- 單條語句執行, next 命令簡寫。 
18               for(i=1; i<=100; i++)
(gdb) n
20                       result += i;
(gdb) n
18               for(i=1; i<=100; i++)
(gdb) n
20                       result += i;
(gdb) c          <--------------------- 繼續執行程式, continue 命令簡寫。 
Continuing.
result[1-100] = 5050       <---------- 程式輸出。

Breakpoint 2, func (n=250) at tst.c:5
5                int sum=0,i;
(gdb) n
6                for(i=1; i<=n; i++)
(gdb) p i        <--------------------- 列印變數 i 的值, print 命令簡寫。 
$1 = 134513808
(gdb) n
8                        sum+=i;
(gdb) n
6                for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8                        sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6                for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt        <--------------------- 檢視函式堆疊。 
#0  func (n=250) at tst.c:5
#1  0x080484e4 in main () at tst.c:24
#2  0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish    <--------------------- 退出函式。 
Run till exit from #0  func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24              printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c     <--------------------- 繼續執行。 
Continuing.
result[1-250] = 31375    <---------- 程式輸出。

Program exited with code 027. <-------- 程式退出,除錯結束。 
(gdb) q     <--------------------- 退出 gdb 。 
hchen/test>

好了,有了以上的感性認識,還是讓我們來系統地認識一下 gdb 吧。

 


使用 GDB 
————

一般來說 GDB 主要除錯的是 C/C++ 的程式。要除錯 C/C++ 的程式,首先在編譯時,我們必須要把除錯資訊加到可執行檔案中。使用編譯器( cc/gcc/g++ )的 -g 引數可以做到這一點。如:

    > cc -g hello.c -o hello
    > g++ -g hello.cpp -o hello


      如果沒有 -g ,你將看不見程式的函式名、變數名,所代替的全是執行時的記憶體地址。當你用 -g 把除錯資訊加入之後,併成功編譯目的碼以後,讓我們來看看如何用 gdb 來除錯他。

     啟動 GDB 的方法有以下幾種:

    1 、 gdb <program>
       program 也就是你的執行檔案,一般在當然目錄下。

    2 、 gdb <program> core
       用 gdb 同時除錯一個執行程式和 core 檔案, core 是程式非法執行後 core dump 後產生的檔案。

    3 、 gdb <program> <PID>
       

      如果你的程式是一個服務程式,那麼你可以指定這個服務程式執行時的程式 ID 。 gdb 會自動 attach 上去,並除錯他。 program 應該在 PATH 環境變數中搜尋得到。

 

      GDB 啟動時,可以加上一些 GDB 的啟動開關,詳細的開關可以用 gdb -help 檢視。我在下面只例舉一些比較常用的引數:

    -symbols <file>
    -s <file>
    從指定檔案中讀取符號表。

    -se file
    從指定檔案中讀取符號表資訊,並把他用在可執行檔案中。

    -core <file>
    -c <file>
    除錯時 core dump 的 core 檔案。

    -directory <directory>
    -d <directory>
    加入一個原始檔的搜尋路徑。預設搜尋路徑是環境變數中 PATH 所定義的路徑。


GDB 的命令概貌 
———————

     啟動 gdb 後,就你被帶入 gdb 的除錯環境中,就可以使用 gdb 的命令開始除錯程式了, gdb 的命令可以使用 help命令來檢視,如下所示:

    /home/hchen> gdb
    GNU gdb 5.1.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 "i386-suse-linux".
    (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 的命令很多, gdb 把之分成許多個種類。 help 命令只是例出 gdb 的命令種類,如果要看種類中的命令,可以使用 help <class> 命令,如: help breakpoints ,檢視設定斷點的所有命令。也可以直接 help <command>來檢視命令的幫助。


      gdb 中,輸入命令時,可以不用打全命令,只用打命令的前幾個字元就可以了,當然,命令的前幾個字元應該要標誌著一個唯一的命令,在 Linux 下,你可以敲擊兩次 TAB 鍵來補齊命令的全稱,如果有重複的,那麼 gdb 會把其例出來。 
   
     示例一:在進入函式 func 時,設定一個斷點。可以敲入 break func ,或是直接就是 b func
    (gdb) b func
    Breakpoint 1 at 0x8048458: file hello.c, line 10.
 
    示例二:敲入 b 按兩次 TAB 鍵,你會看到所有 b 打頭的命令: 
    (gdb) b
    backtrace  break      bt
    (gdb)

    示例三:只記得函式的字首,可以這樣: 
    (gdb) b make_ < 按 TAB 鍵 >
    (再按下一次 TAB 鍵,你會看到 : ) 
    make_a_section_from_file     make_environ
    make_abs_section             make_function_type
    make_blockvector             make_pointer_type
    make_cleanup                 make_reference_type
    make_command                 make_symbol_completion_list
    (gdb) b make_
    GDB 把所有 make 開頭的函式全部例出來給你檢視。

    示例四:除錯 C++ 的程式時,有可以函式名一樣。如: 
    (gdb) b 'bubble( M-?
    bubble(double,double)    bubble(int,int)
    (gdb) b 'bubble(
    你可以檢視到 C++ 中的所有的過載函式及引數。(注: M-? 和 “ 按兩次 TAB 鍵 ” 是一個意思)

要退出 gdb 時,只用發 quit 或命令簡稱 q 就行了。

 

GDB 中執行 UNIX 的 shell 程式 
————————————

      在gdb 環境中,你可以執行 UNIX 的 shell 的命令,使用 gdb 的 shell 命令來完成:

    shell <command string>
     呼叫 UNIX 的 shell 來執行 <command string> ,環境變數 SHELL 中定義的 UNIX 的 shell 將會被用來執行 <command string> ,如果 SHELL 沒有定義,那就使用 UNIX 的標準 shell : /bin/sh 。(在 Windows中使用 Command.com 或 cmd.exe )

    還有一個 gdb 命令是 make : 
    make <make-args>
    可以在 gdb 中執行 make 命令來重新 build 自己的程式。這個命令等價於 “ shell make <make-args> ”。


在 GDB 中執行程式 
————————

當以 gdb <program> 方式啟動 gdb 後, gdb 會在 PATH 路徑和當前目錄中搜尋 <program> 的原始檔。如要確認gdb 是否讀到原始檔,可使用 l 或 list 命令,看看 gdb 是否能列出原始碼。

在 gdb 中,執行程式使用 r 或是 run 命令。程式的執行,你有可能需要設定下面四方面的事。

1 、程式執行引數。 
    set args 可指定執行時引數。(如: set args 10 20 30 40 50 ) 
    show args 命令可以檢視設定好的執行引數。

2 、執行環境。 
    path <dir> 可設定程式的執行路徑。 
    show paths 檢視程式的執行路徑。 
    set environment varname [=value] 設定環境變數。如: set env USER=hchen
    show environment [varname] 檢視環境變數。

3 、工作目錄。 
    cd <dir> 相當於 shell 的 cd 命令。 
    pwd 顯示當前的所在目錄。

4 、程式的輸入輸出。 
    info terminal 顯示你程式用到的終端的模式。 
    使用重定向控制程式輸出。如: run > outfile
    tty 命令可以指寫輸入輸出的終端裝置。如: tty /dev/ttyb


除錯已執行的程式 
————————

兩種方法: 
1 、在 UNIX 下用 ps 檢視正在執行的程式的 PID (程式 ID ),然後用 gdb <program> PID 格式掛接正在執行的程式。 
2 、先用 gdb <program> 關聯上原始碼,並進行 gdb ,在 gdb 中用 attach 命令來掛接程式的 PID 。並用detach 來取消掛接的程式。

 

暫停 / 恢復程式執行 
—————————

除錯程式中,暫停程式執行是必須的, GDB 可以方便地暫停程式的執行。你可以設定程式的在哪行停住,在什麼條件下停住,在收到什麼訊號時停往等等。以便於你檢視執行時的變數,以及執行時的流程。


當程式被 gdb 停住時,你可以使用 info program 來檢視程式的是否在執行,程式號,被暫停的原因。


在 gdb 中,我們可以有以下幾種暫停方式:斷點( BreakPoint )、觀察點( WatchPoint )、捕捉點(CatchPoint )、訊號( Signals )、執行緒停止( Thread Stops )。如果要恢復程式執行,可以使用 c 或是continue 命令。


一、設定斷點( BreakPoint ) 
   
    我們用 break 命令來設定斷點。正面有幾點設定斷點的方法: 
   
    break <function>
        在進入指定函式時停住。 C++ 中可以使用 class::function 或 function(type,type) 格式來指定函式名。

    break <linenum>
        在指定行號停住。

    break +offset
    break -offset
        在當前行號的前面或後面的 offset 行停住。 offiset 為自然數。

    break filename:linenum
        在原始檔 filename 的 linenum 行處停住。

    break filename:function
        在原始檔 filename 的 function 函式的入口處停住。

    break *address
        在程式執行的記憶體地址處停住。

    break
        break 命令沒有引數時,表示在下一條指令處停住。

    break ... if <condition>
        ... 可以是上述的引數, condition 表示條件,在條件成立時停住。比如在迴圈境體中,可以設定 break if i=100 ,表示當 i 為 100 時停住程式。

    檢視斷點時,可使用 info 命令,如下所示:(注: n 表示斷點號) 
    info breakpoints [n]
    info break [n]
   

二、設定觀察點( WatchPoint ) 
   
    觀察點一般來觀察某個表示式(變數也是一種表示式)的值是否有變化了,如果有變化,馬上停住程式。我們有下面的幾種方法來設定觀察點: 
   
    watch <expr>
        為表示式(變數) expr 設定一個觀察點。一量表示式值有變化時,馬上停住程式。 
       
    rwatch <expr>
        當表示式(變數) expr 被讀時,停住程式。 
       
    awatch <expr>
        當表示式(變數)的值被讀或被寫時,停住程式。 
   
    info watchpoints
        列出當前所設定了的所有觀察點。


三、設定捕捉點( CatchPoint )

      你可設定捕捉點來補捉程式執行時的一些事件。如:載入共享庫(動態連結庫)或是 C++ 的異常。設定捕捉點的格式為: 
   
    catch <event>
        當 event 發生時,停住程式。 event 可以是下面的內容: 
        1 、 throw 一個 C++ 丟擲的異常。( throw 為關鍵字) 
        2 、 catch 一個 C++ 捕捉到的異常。( catch 為關鍵字) 
        3 、 exec 呼叫系統呼叫 exec 時。( exec 為關鍵字,目前此功能只在 HP-UX 下有用) 
        4 、 fork 呼叫系統呼叫 fork 時。( fork 為關鍵字,目前此功能只在 HP-UX 下有用) 
        5 、 vfork 呼叫系統呼叫 vfork 時。( vfork 為關鍵字,目前此功能只在 HP-UX 下有用) 
        6 、 load 或 load <libname> 載入共享庫(動態連結庫)時。( load 為關鍵字,目前此功能只在HP-UX 下有用) 
        7 、 unload 或 unload <libname> 解除安裝共享庫(動態連結庫)時。( unload 為關鍵字,目前此功能只在 HP-UX 下有用)

    tcatch <event>
        只設定一次捕捉點,當程式停住以後,應點被自動刪除。


四、維護停止點

上面說了如何設定程式的停止點, GDB 中的停止點也就是上述的三類。在 GDB 中,如果你覺得已定義好的停止點沒有用了,你可以使用 delete 、 clear 、 disable 、 enable 這幾個命令來進行維護。

    clear
        清除所有的已定義的停止點。

    clear <function>
    clear <filename:function>
        清除所有設定在函式上的停止點。

    clear <linenum>
    clear <filename:linenum>
        清除所有設定在指定行上的停止點。

    delete [breakpoints] [range...]
        刪除指定的斷點, breakpoints 為斷點號。如果不指定斷點號,則表示刪除所有的斷點。 range 表示斷點號的範圍(如: 3-7 )。其簡寫命令為 d 。


比刪除更好的一種方法是 disable 停止點, disable 了的停止點, GDB 不會刪除,當你還需要時, enable 即可,就好像回收站一樣。

    disable [breakpoints] [range...]
        disable 所指定的停止點, breakpoints 為停止點號。如果什麼都不指定,表示 disable 所有的停止點。簡寫命令是 dis.

    enable [breakpoints] [range...]
        enable 所指定的停止點, breakpoints 為停止點號。

    enable [breakpoints] once range...
        enable 所指定的停止點一次,當程式停止後,該停止點馬上被 GDB 自動 disable 。

    enable [breakpoints] delete range...
        enable 所指定的停止點一次,當程式停止後,該停止點馬上被 GDB 自動刪除。

 

五、停止條件維護

前面在說到設定斷點時,我們提到過可以設定一個條件,當條件成立時,程式自動停止,這是一個非常強大的功能,這裡,我想專門說說這個條件的相關維護命令。一般來說,為斷點設定一個條件,我們使用 if 關鍵詞,後面跟其斷點條件。並且,條件設定好後,我們可以用 condition 命令來修改斷點的條件。(只有 break 和 watch 命令支援 if, catch 目前暫不支援 if )

    condition <bnum> <expression>
        修改斷點號為 bnum 的停止條件為 expression 。

    condition <bnum>
        清除斷點號為 bnum 的停止條件。


還有一個比較特殊的維護命令 ignore ,你可以指定程式執行時,忽略停止條件幾次。

    ignore <bnum> <count>
        表示忽略斷點號為 bnum 的停止條件 count 次。

 

六、為停止點設定執行命令

我們可以使用 GDB 提供的 command 命令來設定停止點的執行命令。也就是說,當執行的程式在被停止住時,我們可以讓其自動執行一些別的命令,這很有利行自動化除錯。對基於 GDB 的自動化除錯是一個強大的支援。


    commands [bnum]
    ... command-list ...
    end

    為斷點號 bnum 指寫一個命令列表。當程式被該斷點停住時, gdb 會依次執行命令列表中的命令。

    例如:

        break foo if x>0
        commands
        printf "x is %d/n",x
        continue
        end
        斷點設定在函式 foo 中,斷點條件是 x>0 ,如果程式被斷住後,也就是,一旦 x 的值在 foo 函式中大於0 , GDB 會自動列印出 x 的值,並繼續執行程式。

如果你要清除斷點上的命令序列,那麼只要簡單的執行一下 commands 命令,並直接在打個 end 就行了。


七、斷點選單

在 C++ 中,可能會重複出現同一個名字的函式若干次(函式過載),在這種情況下, break <function> 不能告訴GDB 要停在哪個函式的入口。當然,你可以使用 break <function(type)> 也就是把函式的引數型別告訴 GDB ,以指定一個函式。否則的話, GDB 會給你列出一個斷點選單供你選擇你所需要的斷點。你只要輸入你選單列表中的編號就可以了。如:

    (gdb) b String::after
    [0] cancel
    [1] all
    [2] file:String.cc; line number:867
    [3] file:String.cc; line number:860
    [4] file:String.cc; line number:875
    [5] file:String.cc; line number:853
    [6] file:String.cc; line number:846
    [7] file:String.cc; line number:735
    > 2 4 6
    Breakpoint 1 at 0xb26c: file String.cc, line 867.
    Breakpoint 2 at 0xb344: file String.cc, line 875.
    Breakpoint 3 at 0xafcc: file String.cc, line 846.
    Multiple breakpoints were set.
    Use the "delete" command to delete unwanted
     breakpoints.
    (gdb)

可見, GDB 列出了所有 after 的過載函式,你可以選一下列表編號就行了。 0 表示放棄設定斷點, 1 表示所有函式都設定斷點。


八、恢復程式執行和單步除錯

當程式被停住了,你可以用 continue 命令恢復程式的執行直到程式結束,或下一個斷點到來。也可以使用 step 或next 命令單步跟蹤程式。

    continue [ignore-count]
    c [ignore-count]
    fg [ignore-count]
        恢復程式執行,直到程式結束,或是下一個斷點到來。 ignore-count 表示忽略其後的斷點次數。continue , c , fg 三個命令都是一樣的意思。


    step <count>
        單步跟蹤,如果有函式呼叫,他會進入該函式。進入函式的前提是,此函式被編譯有 debug 資訊。很像 VC等工具中的 step in 。後面可以加 count 也可以不加,不加表示一條條地執行,加表示執行後面的 count 條指令,然後再停住。

    next <count>
        同樣單步跟蹤,如果有函式呼叫,他不會進入該函式。很像 VC 等工具中的 step over 。後面可以加count 也可以不加,不加表示一條條地執行,加表示執行後面的 count 條指令,然後再停住。

    set step-mode
    set step-mode on
        開啟 step-mode 模式,於是,在進行單步跟蹤時,程式不會因為沒有 debug 資訊而不停住。這個引數有很利於檢視機器碼。

    set step-mod off
        關閉 step-mode 模式。

    finish
        執行程式,直到當前函式完成返回。並列印函式返回時的堆疊地址和返回值及引數值等資訊。

    until 或 u
        當你厭倦了在一個迴圈體內單步跟蹤時,這個命令可以執行程式直到退出迴圈體。

    stepi 或 si
    nexti 或 ni
        單步跟蹤一條機器指令!一條程式程式碼有可能由數條機器指令完成, stepi 和 nexti 可以單步執行機器指令。與之一樣有相同功能的命令是 “ display/i $pc ” ,當執行完這個命令後,單步跟蹤會在打出程式程式碼的同時打出機器指令(也就是彙編程式碼)


九、訊號( Signals )


      訊號是一種軟中斷,是一種處理非同步事件的方法。一般來說,作業系統 都支援許多訊號。尤其是 UNIX ,比較重要應用程式 一般都會處理訊號。 UNIX 定義了許多訊號,比如 SIGINT 表示中斷字元訊號,也就是 Ctrl+C 的訊號,SIGBUS 表示硬體故障的訊號; SIGCHLD 表示子程式狀態改變訊號; SIGKILL 表示終止程式執行的訊號,等等。訊號量程式設計是 UNIX 下非常重要的一種技術 


      GDB 有能力在你除錯程式的時候處理任何一種訊號,你可以告訴 GDB 需要處理哪一種訊號。你可以要求 GDB 收到你所指定的訊號時,馬上停住正在執行的程式,以供你進行除錯。你可以用 GDB 的 handle 命令來完成這一功能。

    handle <signal> <keywords...>
      在 GDB 中定義一個訊號處理。訊號 <signal> 可以以 SIG 開頭或不以 SIG 開頭,可以用定義一個要處理訊號的範圍(如: SIGIO-SIGKILL ,表示處理從 SIGIO 訊號到 SIGKILL 的訊號,其中包括 SIGIO , SIGIOT, SIGKILL 三個訊號),也可以使用關鍵字 all 來標明要處理所有的訊號。一旦被除錯的程式接收到訊號,執行程式馬上會被 GDB 停住,以供除錯。其 <keywords> 可以是以下幾種關鍵字的一個或多個。

        nostop
            當被除錯的程式收到訊號時, GDB 不會停住程式的執行,但會打出訊息告訴你收到這種訊號。 
        stop
            當被除錯的程式收到訊號時, GDB 會停住你的程式。 
        print
            當被除錯的程式收到訊號時, GDB 會顯示出一條資訊。 
        noprint
            當被除錯的程式收到訊號時, GDB 不會告訴你收到訊號的資訊。 
        pass
        noignore
            當被除錯的程式收到訊號時, GDB 不處理訊號。這表示, GDB 會把這個訊號交給被除錯程式會處理。 
        nopass
        ignore
            當被除錯的程式收到訊號時, GDB 不會讓被除錯程式來處理這個訊號。


        info signals
        info handle
        檢視有哪些訊號在被 GDB 檢測中。


十、執行緒( Thread Stops )


      如果你程式是多執行緒的話,你可以定義你的斷點是否在所有的執行緒上,或是在某個特定的執行緒。 GDB 很容易幫你完成這一工作。

    break <linespec> thread <threadno>
    break <linespec> thread <threadno> if ...
      linespec 指定了斷點設定在的源程式的行號。 threadno 指定了執行緒的 ID ,注意,這個 ID 是 GDB 分配的,你可以通過 “ info threads ” 命令來檢視正在執行程式中的執行緒資訊。如果你不指定 thread <threadno> 則表示你的斷點設在所有執行緒上面。你還可以為某執行緒指定斷點條件。如: 
   
        (gdb) break frik.c:13 thread 28 if bartab > lim

      當你的程式被 GDB 停住時,所有的執行執行緒都會被停住。這方便你你檢視執行程式的總體情況。而在你恢復程式執行時,所有的執行緒也會被恢復執行。那怕是主程式在被單步除錯時。


檢視棧資訊 
—————

      當程式被停住了,你需要做的第一件事就是檢視程式是在哪裡停住的。當你的程式呼叫了一個函式,函式的地址,函式引數,函式內的區域性變數都會被壓入 “ 棧 ” ( Stack )中。你可以用 GDB 命令來檢視當前的棧中的資訊。

     下面是一些檢視函式呼叫棧資訊的 GDB 命令:

    backtrace
    bt
        列印當前的函式呼叫棧的所有資訊。如: 
       
        (gdb) bt
        #0  func (n=250) at tst.c:6
        #1  0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30
        #2  0x400409ed in __libc_start_main () from /lib/libc.so.6
       
        從上可以看出函式的呼叫棧資訊: __libc_start_main --> main() --> func()
       
    backtrace <n>
    bt <n>
        n 是一個正整數,表示只列印棧頂上 n 層的棧資訊。

    backtrace <-n>
    bt <-n>
        -n 表一個負整數,表示只列印棧底下 n 層的棧資訊。 
       
      如果你要檢視某一層的資訊,你需要在切換當前的棧,一般來說,程式停止時,最頂層的棧就是當前棧,如果你要檢視棧下面層的詳細資訊,首先要做的是切換當前棧。

    frame <n>
    f <n>
        n 是一個從 0 開始的整數,是棧中的層編號。比如: frame 0 ,表示棧頂, frame 1 ,表示棧的第二層。 
   
    up <n>
        表示向棧的上面移動 n 層,可以不打 n ,表示向上移動一層。 
       
    down <n>
        表示向棧的下面移動 n 層,可以不打 n ,表示向下移動一層。 
       

      上面的命令,都會列印出移動到的棧層的資訊。如果你不想讓其打出資訊。你可以使用這三個命令: 
   
            select-frame <n> 對應於 frame 命令。 
            up-silently <n> 對應於 up 命令。 
            down-silently <n> 對應於 down 命令。

   
      檢視當前棧層的資訊,你可以用以下 GDB 命令:

           frame 或 f
      會列印出這些資訊:棧的層編號,當前的函式名,函式引數值,函式所在檔案及行號,函式執行到的語句。 
   
          info frame
          info f
      這個命令會列印出更為詳細的當前棧層的資訊,只不過,大多數都是執行時的內內地址。比如:函式地址,呼叫函式的地址,被呼叫函式的地址,目前的函式是由什麼樣的程式語言寫成的、函式引數地址及值、區域性變數的地址等等。如: 
            (gdb) info f
            Stack level 0, frame at 0xbffff5d4:
             eip = 0x804845d in func (tst.c:6); saved eip 0x8048524
             called by frame at 0xbffff60c
             source language c.
             Arglist at 0xbffff5d4, args: n=250
             Locals at 0xbffff5d4, Previous frame's sp is 0x0
             Saved registers:
              ebp at 0xbffff5d4, eip at 0xbffff5d8
             
     info args
        列印出當前函式的引數名及其值。 
    
     info locals
        列印出當前函式中所有區域性變數及其值。 
       
     info catch
        列印出當前的函式中的異常處理資訊。


檢視源程式 
—————

一、顯示原始碼

    GDB 可以列印出所除錯程式的原始碼,當然,在程式編譯時一定要加上 -g 的引數,把源程式資訊編譯到執行檔案中。不然就看不到源程式了。當程式停下來以後, GDB 會報告程式停在了那個檔案的第幾行上。你可以用 list 命令來列印程式的原始碼。還是來看一看檢視原始碼的 GDB 命令吧。 
   
    list <linenum>
        顯示程式第 linenum 行的周圍的源程式。 
   
    list <function>
        顯示函式名為 function 的函式的源程式。 
       
    list
        顯示當前行後面的源程式。 
   
    list -
        顯示當前行前面的源程式。

一般是列印當前行的上 5 行和下 5 行,如果顯示函式是是上 2 行下 行,預設是 10 行,當然,你也可以定製顯示的範圍,使用下面命令可以設定一次顯示源程式的行數。

    set listsize <count>
        設定一次顯示原始碼的行數。 
       
    show listsize
        檢視當前 listsize 的設定。 
       

list 命令還有下面的用法:

    list <first>, <last>
        顯示從 first 行到 last 行之間的原始碼。 
   
    list , <last>
        顯示從當前行到 last 行之間的原始碼。 
       
    list +
        往後顯示原始碼。 
       

一般來說在 list 後面可以跟以下這們的引數:

    <linenum>   行號。 
    <+offset>   當前行號的正偏移量。 
    <-offset>   當前行號的負偏移量。 
    <filename:linenum>  哪個檔案的哪一行。 
    <function>  函式名。 
    <filename:function> 哪個檔案中的哪個函式。 
    <*address>  程式執行時的語句在記憶體中的地址。 
   

二、搜尋原始碼

不僅如此, GDB 還提供了原始碼搜尋的命令:

    forward-search <regexp>
    search <regexp>
        向前面搜尋。

    reverse-search <regexp>
        全部搜尋。 
       
其中, <regexp> 就是正規表示式,也主一個字串的匹配模式,關於正規表示式,我就不在這裡講了,還請各位檢視相關資料。


三、指定原始檔的路徑

某些時候,用 -g 編譯過後的執行程式中只是包括了原始檔的名字,沒有路徑名。 GDB 提供了可以讓你指定原始檔的路徑的命令,以便 GDB 進行搜尋。

    directory <dirname ... >
    dir <dirname ... >
        加一個原始檔路徑到當前路徑的前面。如果你要指定多個路徑, UNIX 下你可以使用 “ : ” , Windows下你可以使用 “ ; ” 。 
    directory
        清除所有的自定義的原始檔搜尋路徑資訊。 
   
    show directories
        顯示定義了的原始檔搜尋路徑。 
       

四、原始碼的記憶體

你可以使用 info line 命令來檢視原始碼在記憶體中的地址。 info line 後面可以跟 “ 行號 ” , “ 函式名 ” , “檔名 : 行號 ” , “ 檔名 : 函式名 ” ,這個命令會列印出所指定的原始碼在執行時的記憶體地址,如:

        (gdb) info line tst.c:func
        Line 5 of "tst.c" starts at address 0x8048456 <func+6> and ends at 0x804845d <func+13>.

還有一個命令( disassemble )你可以檢視源程式的當前執行時的機器碼,這個命令會把目前記憶體中的指令 dump 出來。如下面的示例表示檢視函式 func 的彙編程式碼。

        (gdb) disassemble func
        Dump of assembler code for function func:
        0x8048450 <func>:       push   ?p
        0x8048451 <func+1>:     mov    %esp,?p
        0x8048453 <func+3>:     sub    $0x18,%esp
        0x8048456 <func+6>:     movl   $0x0,0xfffffffc(?p)
        0x804845d <func+13>:    movl   $0x1,0xfffffff8(?p)
        0x8048464 <func+20>:    mov    0xfffffff8(?p),?x
        0x8048467 <func+23>:    cmp    0x8(?p),?x
        0x804846a <func+26>:    jle    0x8048470 <func+32>
        0x804846c <func+28>:    jmp    0x8048480 <func+48>
        0x804846e <func+30>:    mov    %esi,%esi
        0x8048470 <func+32>:    mov    0xfffffff8(?p),?x
        0x8048473 <func+35>:    add    ?x,0xfffffffc(?p)
        0x8048476 <func+38>:    incl   0xfffffff8(?p)
        0x8048479 <func+41>:    jmp    0x8048464 <func+20>
        0x804847b <func+43>:    nop
        0x804847c <func+44>:    lea    0x0(%esi,1),%esi
        0x8048480 <func+48>:    mov    0xfffffffc(?p),?x
        0x8048483 <func+51>:    mov    ?x,?x
        0x8048485 <func+53>:    jmp    0x8048487 <func+55>
        0x8048487 <func+55>:    mov    ?p,%esp
        0x8048489 <func+57>:    pop    ?p
        0x804848a <func+58>:    ret
        End of assembler dump.

 

檢視執行時資料 
——————— 

   
    在你除錯程式時,當程式被停住時,你可以使用 print 命令(簡寫命令為 p ),或是同義命令 inspect 來檢視當前程式的執行資料。 print 命令的格式是: 
   
    print <expr>
    print /<f> <expr>
        <expr> 是表示式,是你所除錯的程式的語言的表示式( GDB 可以除錯多種程式語言), <f> 是輸出的格式,比如,如果要把表示式按 16 進位制的格式輸出,那麼就是 /x 。
 

       
   
一、表示式

    print 和許多 GDB 的命令一樣,可以接受一個表示式, GDB 會根據當前的程式執行的資料來計算這個表示式,既然是表示式,那麼就可以是當前程式執行中的 const 常量、變數、函式等內容。可惜的是 GDB 不能使用你在程式中所定義的巨集。 
   
    表示式的語法應該是當前所除錯的語言的語法,由於 C/C++ 是一種大眾型的語言,所以,本文中的例子都是關於C/C++ 的。(而關於用 GDB 除錯其它語言的章節,我將在後面介紹) 
   
    在表示式中,有幾種 GDB 所支援的操作符,它們可以用在任何一種語言中。 
   
    @
        是一個和陣列有關的操作符,在後面會有更詳細的說明。 
       
    ::
        指定一個在檔案或是一個函式中的變數。 
       
    {<type>} <addr>
        表示一個指向記憶體地址 <addr> 的型別為 type 的一個物件。 
       
       
二、程式變數

    在 GDB 中,你可以隨時檢視以下三種變數的值: 
        1 、全域性變數(所有檔案可見的) 
        2 、靜態全域性變數(當前檔案可見的) 
        3 、區域性變數(當前 Scope 可見的) 

       
    如果你的區域性變數和全域性變數發生衝突(也就是重名),一般情況下是區域性變數會隱藏全域性變數,也就是說,如果一個全域性變數和一個函式中的區域性變數同名時,如果當前停止點在函式中,用 print 顯示出的變數的值會是函式中的區域性變數的值。如果此時你想檢視全域性變數的值時,你可以使用 “ :: ” 操作符: 
   
        file::variable
    function::variable
    可以通過這種形式指定你所想檢視的變數,是哪個檔案中的或是哪個函式中的。例如,檢視檔案 f2.c 中的全域性變數 x 的值: 
   
    gdb) p 'f2.c'::x
   
    當然, “ :: ” 操作符會和 C++ 中的發生衝突, GDB 能自動識別 “ :: ” 是否 C++ 的操作符,所以你不必擔心在除錯 C++ 程式時會出現異常。 
   
    另外,需要注意的是,如果你的程式編譯時開啟了優化選項,那麼在用 GDB 除錯被優化過的程式時,可能會發生某些變數不能訪問,或是取值錯誤碼的情況。這個是很正常的,因為優化程式會刪改你的程式,整理你程式的語句順序,剔除一些無意義的變數等,所以在 GDB 除錯這種程式時,執行時的指令和你所編寫指令就有不一樣,也就會出現你所想象不到的結果。對付這種情況時,需要在編譯程式時關閉編譯優化。一般來說,幾乎所有的編譯器都支援編譯優化的開關,例如, GNU 的 C/C++ 編譯器 GCC ,你可以使用 “ -gstabs ” 選項來解決這個問題。關於編譯器的引數,還請檢視編譯器的使用說明文件。 
   

三、陣列

    有時候,你需要檢視一段連續的記憶體空間的值。比如陣列的一段,或是動態分配的資料的大小。你可以使用 GDB 的“ @ ” 操作符, “ @ ” 的左邊是第一個記憶體的地址的值, “ @ ” 的右邊則你你想檢視記憶體的長度。例如,你的程式中有這樣的語句: 
    
        int *array = (int *) malloc (len * sizeof (int));
       
    於是,在 GDB 除錯過程中,你可以以如下命令顯示出這個動態陣列的取值:

        p *array@len

    @ 的左邊是陣列的首地址的值,也就是變數 array 所指向的內容,右邊則是資料的長度,其儲存在變數 len中,其輸出結果,大約是下面這個樣子的: 
   
        (gdb) p *array@len
        $1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}

    如果是靜態陣列的話,可以直接用 print 陣列名,就可以顯示陣列中所有資料的內容了。

   
四、輸出格式

    一般來說, GDB 會根據變數的型別輸出變數的值。但你也可以自定義 GDB 的輸出的格式。例如,你想輸出一個整數的十六進位制,或是二進位制來檢視這個整型變數的中的位的情況。要做到這樣,你可以使用 GDB 的資料顯示格式: 
   
    x  按十六進位制格式顯示變數。 
    d  按十進位制格式顯示變數。 
    u  按十六進位制格式顯示無符號整型。 
    o  按八進位制格式顯示變數。 
    t  按二進位制格式顯示變數。 
    a  按十六進位制格式顯示變數。 
    c  按字元格式顯示變數。 
    f  按浮點數格式顯示變數。

        (gdb) p i
        $21 = 101   
       
        (gdb) p/a i
        $22 = 0x65
       
        (gdb) p/c i
        $23 = 101 'e'
       
        (gdb) p/f i
        $24 = 1.41531145e-43
       
        (gdb) p/x i
        $25 = 0x65
       
        (gdb) p/t i
        $26 = 1100101


五、檢視記憶體

    你可以使用 examine 命令(簡寫是 x )來檢視記憶體地址中的值。 x 命令的語法如下所示: 
   
    x/<n/f/u> <addr>
   
    n 、 f 、 u 是可選的引數。 
   
    n 是一個正整數,表示顯示記憶體的長度,也就是說從當前地址向後顯示幾個地址的內容。 
    f 表示顯示的格式,參見上面。如果地址所指的是字串,那麼格式可以是 s ,如果地十是指令地址,那麼格式可以是 i 。 
    u 表示從當前地址往後請求的位元組數,如果不指定的話, GDB 預設是 4 個 bytes 。 u 引數可以用下面的字元來代替, b 表示單位元組, h 表示雙位元組, w 表示四位元組, g 表示八位元組。當我們指定了位元組長度後, GDB 會從指記憶體定的記憶體地址開始,讀寫指定位元組,並把其當作一個值取出來。 
   
    <addr> 表示一個記憶體地址。

    n/f/u 三個引數可以一起使用。例如: 
   
    命令: x/3uh 0x54320 表示,從記憶體地址 0x54320 讀取內容, h 表示以雙位元組為一個單位, 3 表示三個單位, u 表示按十六進位制顯示。 
   
六、自動顯示

    你可以設定一些自動顯示的變數,當程式停住時,或是在你單步跟蹤時,這些變數會自動顯示。相關的 GDB 命令是display 。 
   
    display <expr>
    display/<fmt> <expr>
    display/<fmt> <addr>
   
    expr 是一個表示式, fmt 表示顯示的格式, addr 表示記憶體地址,當你用 display 設定好了一個或多個表示式後,只要你的程式被停下來, GDB 會自動顯示你所設定的這些表示式的值。 
   
    格式 i 和 s 同樣被 display 支援,一個非常有用的命令是: 
   
        display/i $pc
   
    $pc 是 GDB 的環境變數,表示著指令的地址, /i 則表示輸出格式為機器指令碼,也就是彙編。於是當程式停下後,就會出現原始碼和機器指令碼相對應的情形,這是一個很有意思的功能。 
   
    下面是一些和 display 相關的 GDB 命令: 
   
    undisplay <dnums...>
    delete display <dnums...>
    刪除自動顯示, dnums 意為所設定好了的自動顯式的編號。如果要同時刪除幾個,編號可以用空格分隔,如果要刪除一個範圍內的編號,可以用減號表示(如: 2-5 ) 
   
    disable display <dnums...>
    enable display <dnums...>
    disable 和 enalbe 不刪除自動顯示的設定,而只是讓其失效和恢復。 
   
    info display
    檢視 display 設定的自動顯示的資訊。 GDB 會打出一張表格,向你報告當然除錯中設定了多少個自動顯示設定,其中包括,設定的編號,表示式,是否 enable 。


七、設定顯示選項

    GDB 中關於顯示的選項比較多,這裡我只例舉大多數常用的選項。

    set print address
    set print address on
        開啟地址輸出,當程式顯示函式資訊時, GDB 會顯出函式的引數地址。系統預設為開啟的,如: 
       
        (gdb) f
        #0  set_quotes (lq=0x34c78 "<<", rq=0x34c88 ">>")
            at input.c:530
        530         if (lquote != def_lquote)


    set print address off
        關閉函式的引數地址顯示,如: 
       
        (gdb) set print addr off
        (gdb) f
        #0  set_quotes (lq="<<", rq=">>") at input.c:530
        530         if (lquote != def_lquote)

    show print address
        檢視當前地址顯示選項是否開啟。 
       
    set print array
    set print array on
        開啟陣列顯示,開啟後當陣列顯示時,每個元素佔一行,如果不開啟的話,每個元素則以逗號分隔。這個選項預設是關閉的。與之相關的兩個命令如下,我就不再多說了。 
       
    set print array off
    show print array

    set print elements <number-of-elements>
        這個選項主要是設定陣列的,如果你的陣列太大了,那麼就可以指定一個 <number-of-elements> 來指定資料顯示的最大長度,當到達這個長度時, GDB 就不再往下顯示了。如果設定為 0 ,則表示不限制。 
       
    show print elements
        檢視 print elements 的選項資訊。 
       
    set print null-stop <on/off>
        如果開啟了這個選項,那麼當顯示字串時,遇到結束符則停止顯示。這個選項預設為 off 。 
       
    set print pretty on
        如果開啟 printf pretty 這個選項,那麼當 GDB 顯示結構體時會比較漂亮。如:

            $1 = {
              next = 0x0,
              flags = {
                sweet = 1,
                sour = 1
              },
              meat = 0x54 "Pork"
            }

    set print pretty off
        關閉 printf pretty 這個選項, GDB 顯示結構體時會如下顯示: 
       
            $1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"}
           
    show print pretty
        檢視 GDB 是如何顯示結構體的。 
       
   
    set print sevenbit-strings <on/off>
        設定字元顯示,是否按 “ /nnn ” 的格式顯示,如果開啟,則字串或字元資料按 /nnn 顯示,如 “ /065 ” 。 
   
    show print sevenbit-strings
        檢視字元顯示開關是否開啟。 
       
    set print union <on/off>
        設定顯示結構體時,是否顯式其內的聯合體資料。例如有以下資料結構: 
       
        typedef enum {Tree, Bug} Species;
        typedef enum {Big_tree, Acorn, Seedling} Tree_forms;
        typedef enum {Caterpillar, Cocoon, Butterfly}
                      Bug_forms;
       
        struct thing {
          Species it;
          union {
            Tree_forms tree;
            Bug_forms bug;
          } form;
        };
       
        struct thing foo = {Tree, {Acorn}};

        當開啟這個開關時,執行 p foo 命令後,會如下顯示: 
            $1 = {it = Tree, form = {tree = Acorn, bug = Cocoon}}
       
        當關閉這個開關時,執行 p foo 命令後,會如下顯示: 
            $1 = {it = Tree, form = {...}}

    show print union
        檢視聯合體資料的顯示方式 
       
    set print object <on/off>
        在 C++ 中,如果一個物件指標指向其派生類,如果開啟這個選項, GDB 會自動按照虛方法呼叫的規則顯示輸出,如果關閉這個選項的話, GDB 就不管虛擬函式表了。這個選項預設是 off 。 
   
    show print object
        檢視物件選項的設定。 
       
    set print static-members <on/off>
        這個選項表示,當顯示一個 C++ 物件中的內容是,是否顯示其中的靜態資料成員。預設是 on 。 
   
    show print static-members
        檢視靜態資料成員選項設定。 
       
    set print vtbl <on/off>
        當此選項開啟時, GDB 將用比較規整的格式來顯示虛擬函式表時。其預設是關閉的。 
       
    show print vtbl
        檢視虛擬函式顯示格式的選項。 
       
        
八、歷史記錄

    當你用 GDB 的 print 檢視程式執行時的資料時,你每一個 print 都會被 GDB 記錄下來。 GDB 會以 $1, $2, $3 ..... 這樣的方式為你每一個 print 命令編上號。於是,你可以使用這個編號訪問以前的表示式,如 $1 。這個功能所帶來的好處是,如果你先前輸入了一個比較長的表示式,如果你還想檢視這個表示式的值,你可以使用歷史記錄來訪問,省去了重複輸入。 
   
   
九、 GDB 環境變數

    你可以在 GDB 的除錯環境中定義自己的變數,用來儲存一些除錯程式中的執行資料。要定義一個 GDB 的變數很簡單隻需。使用 GDB 的 set 命令。 GDB 的環境變數和 UNIX 一樣,也是以 $ 起頭。如: 
   
    set $foo = *object_ptr
   
    使用環境變數時, GDB 會在你第一次使用時建立這個變數,而在以後的使用中,則直接對其賦值。環境變數沒有型別,你可以給環境變數定義任一的型別。包括結構體和陣列。 
   
    show convenience
        該命令檢視當前所設定的所有的環境變數。 
       
    這是一個比較強大的功能,環境變數和程式變數的互動使用,將使得程式除錯更為靈活便捷。例如: 
   
        set $i = 0
        print bar[$i++]->contents
   
    於是,當你就不必, print bar[0]->contents, print bar[1]->contents 地輸入命令了。輸入這樣的命令後,只用敲回車,重複執行上一條語句,環境變數會自動累加,從而完成逐個輸出的功能。 
   
   
十、檢視暫存器

    要檢視暫存器的值,很簡單,可以使用如下命令: 
   
    info registers
        檢視暫存器的情況。(除了浮點暫存器) 
   
    info all-registers
        檢視所有暫存器的情況。(包括浮點暫存器) 
   
    info registers <regname ...>
        檢視所指定的暫存器的情況。 
       
    暫存器中放置了程式執行時的資料,比如程式當前執行的指令地址( ip ),程式的當前堆疊地址( sp )等等。你同樣可以使用 print 命令來訪問暫存器的情況,只需要在暫存器名字前加一個 $ 符號就可以了。如: p $eip 。



改變程式的執行 
———————

    一旦使用 GDB 掛上被除錯程式,當程式執行起來後,你可以根據自己的除錯思路來動態地在 GDB 中更改當前被除錯程式的執行線路或是其變數的值,這個強大的功能能夠讓你更好的除錯你的程式,比如,你可以在程式的一次執行中走遍程式的所有分支。 
  
一、修改變數值

    修改被除錯程式執行時的變數值,在 GDB 中很容易實現,使用 GDB 的 print 命令即可完成。如: 
   
        (gdb) print x=4
   
    x=4 這個表示式是 C/C++ 的語法,意為把變數 x 的值修改為 4 ,如果你當前除錯的語言是 Pascal ,那麼你可以使用 Pascal 的語法: x:=4 。 
   
    在某些時候,很有可能你的變數和 GDB 中的引數衝突,如: 
   
        (gdb) whatis width
        type = double
        (gdb) p width
        $4 = 13
        (gdb) set width=47
        Invalid syntax in expression.

    因為, set width 是 GDB 的命令,所以,出現了 “ Invalid syntax in expression ” 的設定錯誤,此時,你可以使用 set var 命令來告訴 GDB , width 不是你 GDB 的引數,而是程式的變數名,如: 
   
        (gdb) set var width=47
       
    另外,還可能有些情況, GDB 並不報告這種錯誤,所以保險起見,在你改變程式變數取值時,最好都使用 set var 格式的 GDB 命令。 
   

二、跳轉執行

    一般來說,被除錯程式會按照程式程式碼的執行順序依次執行。 GDB 提供了亂序執行的功能,也就是說, GDB 可以修改程式的執行順序,可以讓程式執行隨意跳躍。這個功能可以由 GDB 的 jump 命令來完: 
   
    jump <linespec>
    指定下一條語句的執行點。 <linespce> 可以是檔案的行號,可以是 file:line 格式,可以是 +num 這種偏移量格式。表式著下一條執行語句從哪裡開始。 
   
    jump <address>
    這裡的 <address> 是程式碼行的記憶體地址。 

   
    注意, jump 命令不會改變當前的程式棧中的內容,所以,當你從一個函式跳到另一個函式時,當函式執行完返回時進行彈棧操作時必然會發生錯誤,可能結果還是非常奇怪的,甚至於產生程式 Core Dump 。所以最好是同一個函式中進行跳轉。 
   
    熟悉彙編的人都知道,程式執行時,有一個暫存器用於儲存當前程式碼所在的記憶體地址。所以, jump 命令也就是改變了這個暫存器中的值。於是,你可以使用 “ set $pc ” 來更改跳轉執行的地址。如: 
   
    set $pc = 0x485


三、產生訊號量

    使用 singal 命令,可以產生一個訊號量給被除錯的程式。如:中斷訊號 Ctrl+C 。這非常方便於程式的除錯,可以在程式執行的任意位置設定斷點,並在該斷點用 GDB 產生一個訊號量,這種精確地在某處產生訊號非常有利程式的除錯。 
   
    語法是: signal <singal> , UNIX 的系統訊號量通常從 1 到1
 5 。所以 <singal> 取值也在這個範圍。 

   
    single 命令和 shell 的 kill 命令不同,系統的 kill 命令發訊號給被除錯程式時,是由 GDB 截獲的,而single 命令所發出一訊號則是直接發給被除錯程式的。 
   

四、強制函式返回

    如果你的除錯斷點在某個函式中,並還有語句沒有執行完。你可以使用 return 命令強制函式忽略還沒有執行的語句並返回。 
   

    return
    return <expression>
    使用 return 命令取消當前函式的執行,並立即返回,如果指定了 <expression> ,那麼該表示式的值會被認作函式的返回值。 

五、強制呼叫函式

    call <expr>
    表示式中可以一是函式,以此達到強制呼叫函式的目的。並顯示函式的返回值,如果函式返回值是 void ,那麼就不顯示。 
   
    另一個相似的命令也可以完成這一功能 —— print , print 後面可以跟表示式,所以也可以用他來呼叫函式,print 和 call 的不同是,如果函式返回 void , call 則不顯示, print 則顯示函式返回值,並把該值存入歷史資料中。

 

 

在不同語言中使用 GDB 
——————————

GDB 支援下列語言: C, C++, Fortran, PASCAL, Java, Chill, assembly, 和 Modula-2 。一般說來,GDB 會根據你所除錯的程式來確定當然的除錯語言,比如:發現檔名字尾為 “ .c ” 的, GDB 會認為是 C 程式。檔名字尾為 “ .C, .cc, .cp, .cpp, .cxx, .c++ ” 的, GDB 會認為是 C++ 程式。而字尾是 “ .f, .F ”的, GDB 會認為是 Fortran 程式,還有,字尾為如果是 “ .s, .S ” 的會認為是組合語言。

也就是說, GDB 會根據你所除錯的程式的語言,來設定自己的語言環境,並讓 GDB 的命令跟著語言環境的改變而改變。比如一些 GDB 命令需要用到表示式或變數時,這些表示式或變數的語法,完全是根據當前的語言環境而改變的。例如 C/C++ 中對指標的語法是 *p ,而在 Modula-2 中則是 p^ 。並且,如果你當前的程式是由幾種不同語言一同編譯成的,那到在除錯過程中, GDB 也能根據不同的語言自動地切換語言環境。這種跟著語言環境而改變的功能,真是體貼開發人員的一種設計。


下面是幾個相關於 GDB 語言環境的命令:

    show language
        檢視當前的語言環境
。如果 GDB 不能識為你所除錯的程式語言,那麼, C 語言被認為是預設的環境。 
       
    info frame
        檢視當前函式的程式語言。 

       
    info source
        檢視當前檔案的程式語言。 

   
如果 GDB 沒有檢測出當前的程式語言,那麼你也可以手動設定當前的程式語言。使用 set language 命令即可做到。

    當 set language 命令後什麼也不跟的話,你可以檢視 GDB 所支援的語言種類: 
   
        (gdb) set language
        The currently understood settings are:
       
        local or auto    Automatic setting based on source file
        c                Use the C language
        c++              Use the C++ language
        asm              Use the Asm language
        chill            Use the Chill language
        fortran          Use the Fortran language
        java             Use the Java language
        modula-2         Use the Modula-2 language
        pascal           Use the Pascal language
        scheme           Use the Scheme language
       
    於是你可以在 set language 後跟上被列出來的程式語言名,來設定當前的語言環境。 
   
   

後記 
——

    GDB 是一個強大的命令列除錯工具。大家知道命令列的強大就是在於,其可以形成執行序列,形成指令碼。 UNIX 下的軟體 全是命令列的,這給程式開發提代供了極大的便利,命令列軟體的優勢在於,它們可以非常容易的整合在一起,使用幾個簡單的已有工具的命令,就可以做出一個非常強大的功能。


相關文章