最近工作上大量使用到 gdb,想說來整理一下一些常用的 gdb 使用方式,以及對應的場景;當然,這絕對不是 gdb 完整的功能介紹,只是目前我遇到比較多的使用方式而已。

檢視原始碼:

gdb 的文字介面可以顯示四種視窗:命令,原始碼,組合語言跟暫存器,與視窗相關的鍵盤組合:

  • Ctrl + x + a 它會分成上原始碼下命令兩個視窗
  • Ctrl + x + o 切換焦點所在的視窗,如果焦點視窗放在原始碼那邊,命令視窗的一些鍵會無效, 例如上下鍵會變成原始碼視窗的上下瀏覽原始碼,得用 Ctrl + P (previous) 跟 Ctrl + N (next) 才能瀏覽歷史命令。
  • 使用Ctrl + x + 1,Ctrl + x + 2 可以設定原始碼視窗分為一個或兩個,連續 Ctrl + x + 2 會依序在原始碼+組合語言、組合語言+暫存器、暫存器+組合語言三種組合中切換。

這功能可以直接看到現在執行到哪裡,特別是進到除錯熱區時,gdb 用 list 指令都會自動加10 行,要印出當下除錯的程式總有點難度, 用這個可以徹底排除這個問題;不過我也覺得還沒到除錯熱區的時候,配合編輯器來看原始碼即可。

快捷鍵

這個不止是 gdb,而是大部分 shell 都適用:
除了上述的 Ctrl + p, Ctrl + n,

  • Ctrl + l 可以清空目前的 buffer
  • Ctrl +w 刪除命令列上一個單字
  • Ctrl + u 刪除命令列全部內容

特別是 gdb 裡面 shell 下清空 buffer 的指令 clear 沒有辦法用,Ctrl + l 使用機會滿大的(前提是原始碼視窗沒開)。

中斷點設置:

有關gdb 裡面的四個點:

  1. breakpoint
  2. watchpoint
  3. catchpoint
  4. tracepoint

到現在我也只用過前兩個,99 %都是 breakpoint
breakpoint 又分為永久跟暫時,對應 break/tbreak 後面接要中斷的位置,可以用函數名稱或者 : 兩種格式。
還可以在 breakpoint 的後面加上條件,例如

break <function> if <var> == 10

就能開始 debug 某個狀況下的執行,這在除錯有迴圈或多次呼叫函式的程式時非常好用。
start 指令,自動設置 temporary breakpoint 在主程式開始處,C/C++ 就是 main。

watchpoint 用 watch/rwatch 指令設置,可以監看一個變數什麼時候被動過, 指令監看的目標可以是變數、記憶體位置(如 watch *0x12345678)或是幾個變數的運算也可以。
watch、rwatch、awatch 分別監看變數寫入、讀取跟讀寫。

無論是breakpoint watchpoint其實都是… breakpoint(watchpoint 有時叫 data breakpoint),可以用 info break 看到所有相關設定,還有例如它已經碰到幾遍之類。
有一個相關的技巧如下,可以計算某個函數究竟遇到幾次:

break foo
ignore <breakpoint num> 100000 //大到遠超過可能執行的數量
continue

等程式執行完就能用 info break 看到這個 breakpoint 被碰幾次。

另外 debug 時,可以利用save breakpoints 把設好的breakpoint 存到檔案裡,下次可以直接source它們, save 的 breakpoint 最好只設在函數名稱上面,行數可能在debug 時變化,下次source 時就會break在不同的地方了。
我習慣上會存成 gdbinit 這樣的檔案,一進 gdb 時 source gdbinit 即可。

修改執行:

執行中可以用 set 修改變數/記憶體位址內容。

set var <variable name>=<value>
set <memory location>=<value>

例如我們發現某個條件被跳掉會導致錯誤結果,可以在 gdb 內用 set 修改變數,使它符合條件,省去重新編譯的麻煩 (甚至一些狀況下,修改程式可能動到本來的邏輯,或很難使它符合條件)

跳出函數執行:

指令 finish,執行直到離開當下函數(當然遇到 breakpoint 還是會被擋)。

介面

上面的功能,有些因為最近比較常用 ddd 而非 gdb,也就沒有常用到。
另外強者我同學強強林有推一款 web-based 的 gdb gui ,據說 vscode 的 debug 功能也滿生猛的,也許找時間都來試試看。