5 個鮮為人知 GNU 偵錯程式(GDB)技巧

17 贊 回覆發表於2019-12-05

瞭解如何使用 gdb 的一些鮮為人知的功能來檢查和修復程式碼。

GNU 偵錯程式gdb)是一種寶貴的工具,可用於在開發程式時檢查正在執行的程式並解決問題。

你可以在特定位置(按函式名稱、行號等)設定斷點、啟用和禁用這些斷點、顯示和更改變數值,並執行所有偵錯程式希望執行的所有標準操作。但是它還有許多其它你可能沒有嘗試過的功能。這裡有五個你可以嘗試一下。

條件斷點

設定斷點是學習使用 GNU 偵錯程式的第一步。程式在達到斷點時停止,你可以執行 gdb 的命令對其進行檢查或更改變數,然後再允許該程式繼續執行。

例如,你可能知道一個經常呼叫的函式有時會崩潰,但僅當它獲得某個引數值時才會崩潰。你可以在該函式的開始處設定一個斷點並執行程式。每次碰到該斷點時都會顯示函式引數,並且如果未提供觸發崩潰的引數值,則可以繼續操作,直到再次呼叫該函式為止。當這個惹了麻煩的引數觸發崩潰時,你可以單步執行程式碼以檢視問題所在。

(gdb) break sometimes_crashes
Breakpoint 1 at 0x40110e: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5      fprintf(stderr,
(gdb) continue
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5      fprintf(stderr,
(gdb) continue

為了使此方法更具可重複性,你可以在你感興趣的特定呼叫之前計算該函式被呼叫的次數,並在該斷點處設定一個計數器(例如,continue 30 以使其在接下來的 29 次到達該斷點時忽略它)。

但是斷點真正強大的地方在於它們在執行時評估表示式的能力,這使你可以自動化這種測試。

break [LOCATION] if CONDITION

(gdb) break sometimes_crashes if !f
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5
5      fprintf(stderr,
(gdb)

條件斷點使你不必讓 gdb 每次呼叫該函式時都去問你要做什麼,而是讓條件斷點僅在特定表示式的值為 true 時才使 gdb 停止在該位置。如果執行到達條件斷點的位置,但表示式的計算結果為 false,偵錯程式會自動使程式繼續執行,而無需詢問使用者該怎麼做。

斷點命令

GNU 偵錯程式中斷點的一個甚至更復雜的功能是能夠編寫對到達斷點的響應的指令碼。斷點命令使你可以編寫一系列 GNU 偵錯程式命令,以在到達該斷點時執行。

我們可以使用它來規避在 sometimes_crashes 函式中我們已知的錯誤,並在它提供空指標時使其無害地從該函式返回。

我們可以使用 silent 作為第一行,以更好地控制輸出。否則,每次命中斷點時,即使在執行斷點命令之前,也會顯示堆疊幀。

(gdb) break sometimes_crashes
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if !f
 >frame
 >printf "Skipping call\n"
 >return 0
 >continue
 >end
>printf "Continuing\n"
>continue
>end
(gdb) run
Starting program: /home/twaugh/Documents/GDB/prog
warning: Loadable section ".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0  sometimes_crashes (f=0x0) at prog.c:5
5      fprintf(stderr,
Skipping call
[Inferior 1 (process 9373) exited normally]
(gdb)

轉儲二進位制記憶體

GNU 偵錯程式內建支援使用 x 命令以各種格式檢查記憶體,包括八進位制、十六進位制等。但是我喜歡並排看到兩種格式:左側為十六進位制位元組,右側為相同位元組表示的 ASCII 字元。

當我想逐位元組檢視檔案的內容時,經常使用 hexdump -Chexdump 來自 util-linux 軟體包)。這是 gdbx 命令顯示的十六進位制位元組:

(gdb) x/33xb mydata
0x404040 <mydata>   :    0x02    0x01    0x00    0x02    0x00    0x00    0x00    0x01
0x404048 <mydata+8> :    0x01    0x47    0x00    0x12    0x61    0x74    0x74    0x72
0x404050 <mydata+16>:    0x69    0x62    0x75    0x74    0x65    0x73    0x2d    0x63
0x404058 <mydata+24>:    0x68    0x61    0x72    0x73    0x65    0x75    0x00    0x05
0x404060 <mydata+32>:    0x00

如果你想讓 gdbhexdump 一樣顯示記憶體怎麼辦?這是可以的,實際上,你可以將這種方法用於你喜歡的任何格式。

通過使用 dump 命令以將位元組儲存在檔案中,結合 shell 命令以在檔案上執行 hexdump 以及define 命令,我們可以建立自己的新的 hexdump 命令來使用 hexdump 顯示記憶體內容。

(gdb) define hexdump
Type commands for definition of "hexdump".
End with a line saying just "end".
>dump binary memory /tmp/dump.bin $arg0 $arg0+$arg1
>shell hexdump -C /tmp/dump.bin
>end

這些命令甚至可以放在 ~/.gdbinit 檔案中,以永久定義 hexdump 命令。以下是它執行的例子:

(gdb) hexdump mydata sizeof(mydata)
00000000  02 01 00 02 00 00 00 01  01 47 00 12 61 74 74 72  |.........G..attr|
00000010  69 62 75 74 65 73 2d 63  68 61 72 73 65 75 00 05  |ibutes-charseu..|
00000020  00                                                |.|
00000021

行內反彙編

有時你想更多地瞭解導致崩潰的原因,而原始碼還不夠。你想檢視在 CPU 指令級別發生了什麼。

disassemble 命令可讓你檢視實現函式的 CPU 指令。但是有時輸出可能很難跟蹤。通常,我想檢視與該函式原始碼的特定部分相對應的指令。為此,請使用 /s 修飾符在反彙編中包括原始碼行。

(gdb) disassemble/s main
Dump of assembler code for function main:
prog.c:
11    {
   0x0000000000401158 <+0>:    push   %rbp
   0x0000000000401159 <+1>:    mov      %rsp,%rbp
   0x000000000040115c <+4>:    sub      $0x10,%rsp

12      int n = 0;
   0x0000000000401160 <+8>:    movl   $0x0,-0x4(%rbp)

13      sometimes_crashes(&n);
   0x0000000000401167 <+15>:    lea     -0x4(%rbp),%rax
   0x000000000040116b <+19>:    mov     %rax,%rdi
   0x000000000040116e <+22>:    callq  0x401126 <sometimes_crashes>
[...snipped...]

這裡,用 info 暫存器檢視所有 CPU 暫存器的當前值,以及用如 stepi 這樣命令一次執行一條指令,可以使你對程式有了更詳細的瞭解。

反向除錯

有時,你希望自己可以逆轉時間。想象一下,你已經達到了變數的監視點。監視點像是一個斷點,但不是在程式中的某個位置設定,而是在表示式上設定(使用 watch 命令)。每當表示式的值更改時,執行就會停止,並且偵錯程式將獲得控制權。

想象一下你已經達到了這個監視點,並且由該變數使用的記憶體已更改了值。事實證明,這可能是由更早發生的事情引起的。例如,記憶體已釋放,現在正在重新使用。但是它是何時何地被釋放的呢?

GNU 偵錯程式甚至可以解決此問題,因為你可以反向執行程式!

它通過在每個步驟中仔細記錄程式的狀態來實現此目的,以便可以恢復以前記錄的狀態,從而產生時間倒流的錯覺。

要啟用此狀態記錄,請使用 target record-full 命令。然後,你可以使用一些聽起來不太可行的命令,例如:

  • reverse-step,倒退到上一個原始碼行
  • *reverse-next,它倒退到上一個原始碼行,向後跳過函式呼叫
  • reverse-finish,倒退到當前函式即將被呼叫的時刻
  • reverse-continue,它返回到程式中的先前狀態,該狀態將(現在)觸發斷點(或其他導致斷點停止的狀態)

這是執行中的反向除錯的示例:

(gdb) b main
Breakpoint 1 at 0x401160: file prog.c, line 12.
(gdb) r
Starting program: /home/twaugh/Documents/GDB/prog
[...]

Breakpoint 1, main () at prog.c:12
12      int n = 0;
(gdb) target record-full
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:7
7      return *f;
(gdb) reverse-finish
Run back to call of #0  0x0000000000401154 in sometimes_crashes (f=0x0)
        at prog.c:7
0x0000000000401190 in main () at prog.c:16
16      sometimes_crashes(0);

這些只是 GNU 偵錯程式可以做的一些有用的事情。還有更多有待發現。你最喜歡 gdb 的哪個隱藏的、鮮為人知或令人吃驚的功能?請在評論中分享。


via: https://opensource.com/article/19/9/tips-gnu-debugger

作者:Tim Waugh 選題:lujun9972 譯者:wxy 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

5 個鮮為人知 GNU 偵錯程式(GDB)技巧

訂閱“Linux 中國”官方小程式來檢視

相關文章