天天看點

一文學會GDB操作指令

GDB快速指南

版本 2 (2009 6月 14)

這是一個非常快速的GDB指南,旨在讓你開始在終端的指令行下熟練使用GNU調試器gdb。

更多詳細資訊,檢視官方​​GDB 文檔​​!

此外,一個好的GNU GDB前端是資料顯示調試器​​DDD​​。

1.編譯

您必須告訴編譯器使用包含符号調試的資訊來編譯代碼。

下面介紹如何使用gcc,使用**-g**開關:

$ gcc -g hello.c -o hello    //編譯C代碼

$ g++ -g hello.cpp -o hello    //編譯C++代碼      

完成此操作後,您應該能夠在調試器中檢視生成的檔案清單,如果需要,可以添加編譯選項“-v”檢視具體編譯步驟。

1.1啟動調試器

首先:您可以在任何gdb提示符處輸入help并擷取更多資訊。

此外,還可以輸入quit以退出調試器。

最後,隻需點選RETURN将重複輸入最後一個指令。

有幾種方法可以啟動調試器(例如,如果您是 IDE,則可以使用不太人性化的特定模式啟動調試器),但在此我将提及其中兩種方法:vanilla的控制台模式和curses的 GUI 模式。雖然GUI 界面更友好,但還是讓我們快速介紹一個簡單的程式,并在控制台調試器中啟動一個名為hello的程式:

$ gdb hello
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-slackware-linux"...
(gdb) run
Starting program: /home/beej/hello 
Hello, world!

Program exited normally.
(gdb)      

最後一行是gdb提示符,等待您告訴它該怎麼做。鍵入r或run以運作程式。(gdb允許您縮短指令,除非縮短的指令會使它們的意思變得模棱兩可)。

使用 GUI 模式,可以輸入gdb-tui啟動調試器即可。

下面是您将看到的螢幕截圖,大緻包括:

一文學會GDB操作指令

所有gdb指令都可以正常的在 GUI 模式下工作,此外箭頭鍵和 pgup/pgdown 鍵将滾動源視窗。此外,還可以通過list指令準确定位顯示的檔案或函數,例如**,"list hello.c:5可以在視窗中顯示檔案​

​hello.c。​

​的第 5 行内容。定位方法同樣适用于斷點指令b**。

開啟對一個程式的調試有兩種方法:

一是,直接在指令行鍵入“gdb 可執行程式名”;

二是,先鍵入“gdb”啟動調試器,而後鍵入“file 可執行程式名”

如果需要向調試的程式傳遞參數,可在開始執行時,将它們作為參數傳遞給run指令。
$ gdb hello
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-slackware-linux"...
(gdb) run arg1 arg2
Starting program: /home/beej/hello arg1 arg2
Hello, world!

Program exited normally.
(gdb)      
  • 上例中顯示了将參數"arg1"和"arg2"傳遞給"hello"。

1.2斷點

啟動調試器直接運作程式并不是很有用,更多的時候我們需要停止執行并進入步進模式。

  • 在發出run指令之前,需要在要停止的位置設定斷點。您可以使用break或b指令,并指定一個位置(可以是函數名稱、行号或源檔案:行号)。
  • 下表是一些位置的示例,這些位置也可以被各種其他指令以及break使用:
斷點指令 含義
break 5 在目前檔案的第 5 行處中斷
break hello.c:5 在​

​hello.c​

​的 5 行中斷
break main 在main函數的開頭斷開

是以,對于此測試,讓我們在​

​main()​

​處設定一個斷點,然後啟動程式:

$ gdb hello
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-slackware-linux"...
(gdb) b main
Breakpoint 1 at 0x8048395: file hello.c, line 5.
(gdb) r
Starting program: /home/beej/hello 

Breakpoint 1, main () at hello.c:5
5    printf("Hello, world!\n");
(gdb)      

如您所見,我們到達了​

​main()​

​,執行已停止在我們設定的斷點。如果您在dumb終端模式下運作**,gdb**将輸出下一步執行的行。如果在GUI 模式下運作,它将接下來執行的行将在源視窗中高亮顯示。

  • 要列出目前斷點,請使用info指令,如下所示:“info breakpoints”(或較短的**“i b”):**
(gdb) b main
Breakpoint 1 at 0x8048395: file hello.c, line 5.
(gdb) i b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x08048395 in main at hello.c:5      
  • 要清除斷點,請使用帶有斷點位置的clear指令,還可以使用delete指令按數字清除斷點。
  • 啟用/禁用斷點,請使用帶有斷點号的enable或disable指令,注意這兩個指令将斷點号作為參數,而不是斷點位置!斷點清單中的"Enb"列下可以看到斷點的啟用/禁用狀态。
(gdb) i b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x08048395 in main at hello.c:5
(gdb) disable 1
(gdb) i b
Num     Type           Disp Enb Address    What
1       breakpoint     keep n   0x08048395 in main at hello.c:5
(gdb) clear main
Deleted breakpoint 1 
(gdb)      

1.3步進指令

執行停止在斷點後,您可以告訴調試器執行一些操作。

  • 單步執行: next (或n )。此指令将您移動到目前函數中的下一個語句(或者,如果已退出函數的末尾,則傳回到函數的調用方)。
下面是一個示例:請記住**,gdb目前輸出的是前一個"(gdb)"前導符接下來要執行的行。例如,當我們在​

​printf()​

​行上運作next**時,結果是我們會看到printf的輸出。
(gdb) b main
Breakpoint 1 at 0x8048395: file hello.c, line 5.
(gdb) r
Starting program: /home/beej/hello 

Breakpoint 1, main () at hello.c:5
5   printf("Hello, world!\n");
(gdb) next
Hello, world!
7   return 0;
(gdb) next
8 }
(gdb) next
0xb7d6c6a5 in __libc_start_main () from /lib/libc.so.6
(gdb) next
Single stepping until exit from function __libc_start_main, 
which has no line number information.

Program exited normally.
(gdb)       
結尾的内容顯示,還有另一個庫函數​

​(__libc_start_main()​

​​在調用​

​main()​

​函數!但因為它不是使用調試資訊編譯的,是以我們看不到源代碼,但我們仍然可以通過next單步執行,并且程式會正常退出。

!!!請注意,next指令遇到函數調用時會直接一步執行完函數調用,然後傳回到目前函數中的下一行。但如果您想要進入并逐行調試該函數,該怎麼辦?使用step (或s ) 指令執行此操作。它的工作原理與next基本一樣,除了它會進入到函數内部。

  • 繼續 :continue(或c):假設您厭倦了單步執行,隻是希望程式再次運作,可以使用該指令繼續執行。如果程式正在運作,但您忘記了設定斷點,該怎麼辦?您可以點選CTRL-C,這将停止程式,無論它碰巧在哪裡,并傳回到"(gdb)"提示。此時,您可以在某處設定一個适當的斷點,并continue到該斷點。
  • 重複 :RETURN,隻要點選RETURN将重複輸入的最後一個指令,這将避免一遍又一遍的鍵入next。

1.4 變量

  • 重複顯示變量:display(disp): 如果希望在調試過程中每次都檢查一些變量,則可以用該指令設定某些變量,但僅當變量目前在作用域中時才能顯示它們。如果變量在作用域中,每次執行代碼時,都會顯示變量的值。

為了清楚起見,以下輸出缺少行之間的源代碼輸出,但在 GUI 模式下是看到的。

(gdb) b main
Breakpoint 1 at 0x8048365: file hello.c, line 5.
(gdb) r
Starting program: /home/beej/hello 

Breakpoint 1, main () at hello.c:5
(gdb) disp i
1: i = -1207447872
(gdb) next
1: i = 1
(gdb) next
1: i = 2
(gdb) next
1: i = 4
(gdb)      
  • 取消重複顯示變量:undisplay(undisp)。上面"i"左側的數字是變量的顯示編号,使用此數字作為參數的undisplay指令,就可以取消對相應變量設定。
  • 顯示已設定的重複顯示變量:info display。如果您忘記了變量對應的顯示數字,可以鍵入info display重新檢視它們。
(gdb) b main
Breakpoint 1 at 0x8048365: file hello.c, line 5.
(gdb) r
Starting program: /home/beej/hello 

Breakpoint 1, main () at hello.c:5
(gdb) display i
1: i = -1207447872
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  i
(gdb) undisplay 1
(gdb)      
  • 單次顯示變量:print(p)。如果隻想一次性知道變量的值,可以鍵入“print或(p) 變量”。例如,我們看到"i"的值是 40:
(gdb) print i
$1 = 40
(gdb)      

"$"與數字的組合意味着什麼?對初學者來說它并不重要。

  • 格式化單次顯示變量:printf。一個友善的格式化輸出指令,例如:
(gdb) printf "%d\n", i
40
(gdb) printf "%08X\n", i
00000028
(gdb)      

2.雜項指令

雖然以下指令并非常用,但它足夠有趣,列出一二。

2.1堆棧操作

  • 回溯指令:backtrace(或bt) 。顯示目前函數調用堆棧,目前函數位于頂部,調用方按其調用順序顯示在下方:
(gdb) backtrace
#0  subsubfunction () at hello.c:5
#1  0x080483a7 in subfunction () at hello.c:10
#2  0x080483cf in main () at hello.c:16
(gdb)      

鍵入** help stack**,了解詳細資訊。

2.2其他步進方法

  • finish :要退出目前函數并傳回到調用函數,請使用指令。
  • stepi : 步進到某個語句。
  • advance:繼續運作,直到指定的某臨時斷點。​

    ​advance 位置标記​

    ​。

    下面是一個從目前位置前進到子函數處的示例:

Breakpoint 1, main () at hello.c:15
15    printf("Hello, world!\n");
(gdb) advance subsubfunction
Hello, world!
subsubfunction () at hello.c:5
5    printf("Deepest!\n");
(gdb)      

2.3 跳轉到代碼的任意部分

jump指令的工作方式與**“continue”**完全一樣,不同的是它需要一個位置作為參數。(有關位置的詳細資訊,請參閱上面的"breakpoints"部分。但如果需要在跳轉目标處停止,應先在那裡設定斷點。

2.4 在運作時更改變量的值

set variable 指派表達式 :在運作期間更改變量的值。您也可以在set後使用帶有括号表達式的形式。

Breakpoint 1, main () at hello.c:15
15    int i = 10;
(gdb) print i
$1 = -1208234304
(gdb) set (i = 20)
(gdb) print i
$2 = 20
(gdb) set variable i = 40
(gdb) print i
$3 = 40
(gdb)      

這與jump指令一起可以幫助您在不重新啟動程式的情況下重複代碼部分。

2.5 硬體觀察點

  • watch 變量名:硬體觀察點是一個特殊的斷點,每當表達式更改時都會觸發。通常用在你想知道何時變量被更改(寫入)的。
Breakpoint 1, main () at hello.c:5
5    int i = 1;
(gdb) watch i
Hardware watchpoint 2: i
(gdb) continue
Continuing.
Hardware watchpoint 2: i

Old value = -1208361280
New value = 2
main () at hello.c:7
7    while (i < 100) {
(gdb) continue
Continuing.
Hardware watchpoint 2: i

Old value = 2
New value = 3
main () at hello.c:7
7    while (i < 100) {
(gdb)      
!!!請注意**,watch**可以将表達式作為它的參數,是以您可以将變量名或者更複雜的東西放在表達式中,例如 ​

​*(p+5)​

​​或​

​a[5]​

​​。我甚至嘗試過條件表達式,如​

​i > 10​

​,但結果是不确定的。
  • 您可以擷取帶有info break 或 info watch的監視點清單,并且可以使用delete指令按編号删除它們。
  • 最後,可以使用rwatch來檢測何時讀取變量,并且可以使用watch來檢測何時讀取或寫入變量。

2.6 附加到正在運作的程序

如果程式已經啟動,并且想要停止并調試它,首先您需要程序 ID (可以從 Unix 的ps指令擷取它)。然後,用attach指令加 PID 附加到(并中斷)正在運作的程式。

首先,運作不帶參數的gdb。

如下圖所示,我附加到了運作的程序3490,但它告訴我它位于稱為​

​__nanosleep_nocancel()​

​​的函數裡。但這不稀奇,因為我在我的代碼中調用了​

​sleep()。​

​實際上,請求backtrace正好可以顯示此調用堆棧。是以finish幾次,就可以回到main函數。
$ gdb
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-slackware-linux".
(gdb) attach 3490
Attaching to process 3490
Reading symbols from /home/beej/hello...done.
Reading symbols from /lib/libsafe.so.2...done.
Loaded symbols for /lib/libsafe.so.2
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/libdl.so.2...done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xb7eab21b in __nanosleep_nocancel () from /lib/libc.so.6
(gdb) backtrace 
#0  0xb7eab21b in __nanosleep_nocancel () from /lib/libc.so.6
#1  0xb7eab05f in sleep () from /lib/libc.so.6
#2  0x080483ab in main () at hello.c:10
(gdb) finish
Run till exit from #0  0xb7eab21b in __nanosleep_nocancel ()
   from /lib/libc.so.6
0xb7eab05f in sleep () from /lib/libc.so.6
(gdb) finish
Run till exit from #0  0xb7eab05f in sleep () from /lib/libc.so.6
0x080483ab in main () at hello.c:10
10         sleep(1);
(gdb) list
5   {
6      int i = 1;
7   
8      while (i < 60) {
9         i++;
10         sleep(1);
11      }
12   
13      return 0;
14   }
(gdb) print i
$1 = 19
(gdb) quit
The program is running.  Quit anyway (and detach it)? (y or n)      

‎請注意,當我回到‎​

​‎main() ‎​

​​時‎,我将列印‎​

​‎i‎​

​‎的值,并且它的值是 19,因為在這種情況下,程式已運作了 19 秒,‎‎并且我‎‎每秒獲得一次增量。‎一旦我們退出調試器并脫離程式,程式将恢複正常運作。‎

結合set variable指令‎一起使用,你将獲得更多的調試選項!

2.7 ‎使用核心轉儲進行事後分析‎

‎假設您生成并運作了一個程式,并且由于某種原因使用了核心轉儲:‎

$ cc -g -o foo foo.c
$ ./foo
Segmentation fault (core dumped)      

‎這意味着核心檔案(崩潰時記憶體快照)已建立名稱為"core"。如果您沒有擷取核心檔案(即,它隻顯示"分段錯誤",并且未建立核心檔案),則可能是 ulimit 設定得太低,可以嘗試在 shell 提示符下鍵入‎‎ulimit -c。‎

‎您也可以使用‎‎-c‎‎選項啟動‎‎gdb‎‎以指定核心檔案:

$ gdb -tui -c core foo      

‎而且,如果在 TUI 模式下,您将看到一個資訊屏,告訴您程式退出的原因(“信号 11,分段錯誤”),并且高亮顯示違規行,在dumb模式下,有問題的行用下劃線列印出來。

在此示例中,我将導緻問題的變量列印了出來。事實上,它是 NULL:

一文學會GDB操作指令

‎即使您沒有所有源代碼,從程式崩潰點擷取‎‎backtrace‎‎通常也很有用。‎

2.8 視窗函數

‎在 TUI 模式下,您可以通過指令info win擷取現有視窗的清單。然後,您可以使用focus (or fs)指令更改聚焦到哪個視窗。focus‎‎以視窗名稱或"prev"或"next"作為參數。

有效的視窗名稱有:“SRC”(源視窗)、“CMD”(指令視窗)、“REGS”(注冊視窗)和"ASM"(程式集視窗)。當 SRC 視窗具有焦點時,箭頭鍵将移動源代碼;當 CMD 視窗具有焦點時,箭頭鍵将在指令曆史記錄中選擇上一個和下一個指令。(移動 SRC 視窗單行和單個頁面的指令為‎‎+‎‎、 - ‎‎、<‎‎ 和 ‎ >‎‎)。

(gdb) info win
        SRC     (36 lines)  <has focus>
        CMD     (18 lines)
(gdb) fs next
Focus set to CMD window.
(gdb) info win
        SRC     (36 lines)
        CMD     (18 lines)  <has focus>
(gdb) fs SRC
Focus set to SRC window.
(gdb)      

‎(視窗名稱區分大小寫。)‎

‎winheight(‎‎或‎‎wh)‎‎指令:設定視窗的高度,但有時工作的不是很好。‎

2.9 寄存器和彙編視窗

‎在 TUI 模式下,layout‎‎指令控制視窗的顯示與否。此外‎‎,tui reg‎‎指令打開寄存器視窗。‎

指令 描述
layout src ‎标準布局 — 頂部顯示SRC視窗,底部顯示CMD視窗‎
layout asm 頂部顯示ASM視窗,底部顯示CMD視窗‎
layout split 三個視窗:SRC在頂部,ASM在中間,CMD在底部
layout reg 将REG視窗顯示在SRC或ASM視窗的上面
tui reg general 顯示通用寄存器
tui reg float 顯示浮點寄存器
tui reg system 顯示"系統"寄存器
tui reg next 顯示下一頁REG視窗 , 這一點很重要,因為可能有一些不屬于"通用"、"浮點"或"系統"的寄存器頁面

下面是一個在"split"模式下顯示SRC和ASM視窗:

一文學會GDB操作指令

英特爾機器可以顯示兩種風格的彙編代碼:Intel和 AT&T。您可以通過指令set disassembly-flavor設定在拆解視窗中顯示哪種風格。flavor的有效值為"intel"和"att"。如果已打開了ASM視窗,則必須先關閉它并重新打開它(例如,先執行layout src指令,然後執行 layout split指令)。

要在dumb模式下顯示寄存器,請鍵入info registers,它預設僅顯示整數寄存器的資訊,或鍵入info all-registers指令顯示所有整數、浮點和系統寄存器。

2.10 編寫前端

GDB 支援"機器接口解釋器"或 GDB/MI。可以通過在gdb指令行上添加**–interpreter**選項打開它。

基本上,您将啟動gdb并讀取指令和結果(結合管道使用更友善),非常簡單。

​​有關所有詳細資訊,請參閱 GDB 文檔​​。

3. 快速參考

指令參數以斜體表示。可選參數位于方括号中。所有指令都可以縮寫,直到它們變得模棱兩可。

這個清單是非常不完整的,隻顯示在本教程中談論的東西!

指令 描述
幫助指令
helpcommand 擷取有關特定指令的幫助
aproposkeyword 搜尋特定關鍵字的幫助
啟動和退出
gdb[-tui] [-c core] [exename]

在可執行檔案或獨立檔案上啟動gdb;指定"-tui"以啟動 TUI GUI;

指定"-c core檔案名稱"以檢視發生崩潰的位置。

run[arg1] [arg2] […] 使用給定的指令行參數運作目前加載的程式
quit 退出調試器
fileexename 按名稱加載可執行檔案
斷點和觀察點
breaklocation 在特點位置、行号或檔案處設定斷點(例如"main",“5"或"hello.c:23”)
watchexpression 當變量被寫入時産生中斷
rwatchexpression 當變量被讀取時産生中斷
awatchexpression 當變量被寫入或讀取時産生中斷
info break 顯示斷點和觀察點資訊和數字
info watch 同上
clearlocation 從特定位置處清除斷點
deletenum 通過數字清除斷點或觀察點
步進和運作
next 運作到此函數的下一行
step 如果可能,将步進到此行上的函數的内部
stepi 單步執行一條彙編指令
continue 從此處繼續運作
CTRL-C 停止程式運作
finish 運作到函數結束
advancelocation 前進到某個位置(e.g. “somefunction”, “5”, or “hello.c:23”)
jumplocation 跳到某個位置繼續運作
檢查和修改變量
displayexpression 在程式的每一步中顯示變量或表達式的值, 當然前提是表達式必須在目前作用域中有意義
info display 顯示目前顯示的表達式及其編号的清單
undisplaynum 停止顯示由其編号辨別的表達式
printexpression 列印變量或表達式的值
printfformatstrexpressionlist 使用​

​printf()​

​​執行一些格式化的輸出,(​

​printf "i = %d, p = %s\n", i, p​

​)
set variableexpression 給變量指派,(​

​set variable x=20​

​)
set (expression) 同上
視窗指令
info win 顯示目前視窗資訊
focuswinname 将焦點設定為特定視窗
fswinname 同上
layouttype 設定視窗類型(“src”、“asm”、“split"或"reg”)
tui regtype 設定寄存器視窗類型(“general”, “float”, “system”, or “next”)
winheightval 設定視窗高度(利用“+”或“-”相對值或絕對值)
whval 同上
set disassembly-flavorflavor 設定彙編指令格式(Intel或att)
其它指令
RETURN 點選RETURN重複上一次指令
backtrace 追溯目前指令的函數嵌套調用情況
bt 同上
attachpid 通過 PID 連接配接到已運作的程序
info registers 顯示整數寄存器資訊

繼續閱讀