gdb 雜項心得

fcamel
fcamel的程式開發心得
6 min readSep 17, 2016

對初學者來說,最好有個針對常用情境的簡單指南,之後有閒再看落落長的教學。這裡列一下最近常用的功能, 會持續更新。

前置動作

  • gcc/g++ 編譯時要加 -g,可和 -O2 共用。不過要留意除錯時可能無法讀取區域變數 (value optimized out) 或流程與程式碼對不起來,因為編譯器最佳化改變了流程 (例如簡單的函式被 inline 展開)。
  • -O2 -g 的結果不易除錯,但 -O0 -g 又無法重制一樣的錯誤,可嘗試 -Og。這會打開不影響 gdb 除錯的最佳化參數。
  • gdb、strace 都是用 ptrace 來監控另一個 process 的情況,Ubuntu 基於安全考量,即使 effective uid 相同, 預設只能對後代 process 用 ptrace。取消這個限制的作法: echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope sysctl。希望每次重開機都生效的話,要改 /etc/sysctl.d/10-ptrace.conf
  • Ubuntu 上要觀察用到的函式庫 X,需要裝 X-dbg 版 (如 libjpeg62 → libjpeg62-dbg)。
  • 若原始碼的位置和當初編 binary 時的位置不同 (常有的事),可用 set substitute-path from to 做路徑字串取代或用 directory 指定原始碼搜尋的位置。
  • 可用 objdump --source FILE 確認是否真的有編到 -g。有的話可以在輸出裡看到程式碼。
  • 加速 gdb 載入 debug symbol 的時間: 若是用 gold/lld link 的話,可在 link 時加上 --gdb-index 加上 gdb index,載入速度會快上非常多。ld.bfd (Linux 預設的 linker) 不支援這參數,不過事先沒產生 gdb index 也沒關係,可以在產生 shared library / executable 後,再產生 gdb index 然後寫回檔案。Ubuntu 上的 gdb 套件有附 script gdb-add-index 作這件事。細節見官網《18.5 Index Files Speed Up GDB》

雖說通篇我都寫 gdb,但是 cgdb 好用許多,推薦使用。

執行方式

從頭執行

  • bash> gdb --args PROGRAM PROGRAM-ARG1 …
  • gdb> start # 進入 main 後停下來

檢查掛點原因 (參考《在 Linux 上允許產生 core dump》確保有產生 core dump):

  • bash> gdb PROGRAM CORE
  • gdb> bt 20 # 看掛掉的 call stack 最底層 20 個 function call

通常載入 PROGRAM 讀 debug symbol 較花時間,進 gdb 後可用 core CORE 看不同的 core dump。

若希望從頭重來,有設好中斷點就用 r,沒有則繼續用 start,不用離開 gdb, 可簡省載入 PROG 的時間。

設中斷點

  • b LOC: 設中斷點,或用 cgdb 直接在程式視窗按空白鍵。LOC 可以是行數 (e.g. 12)、檔案:行數 (e.g. hello.c:5)、namespace::class:method、指令記憶體位址 (e.g. *0x0000555555555630)。詳見《Specify Location — Debugging with GDB》
  • i b:列出全部中斷點
  • d NUM: 移除編號 NUM 的中斷點
  • save breakpoints FILE: 存下目前設的中斷點到檔案 FILE
  • so FILE: 載入之前設的中斷點
  • dis 1–10: 暫停使用 breakpoints 1–10。
  • ena 1–10: 恢復使用 breakpoints 1–10。

偶而會用到 conditional break

移動

  • n: 跳下行
  • s: 若有函式, 跳進去; 反之則同 n
  • fin: 執行到函式結尾, 返回上一層
  • until LOC: 執行到 LOC 再停, 我以前都傻傻的先設中斷再按 c …
  • c: 執行到下個中斷點
  • 跳過下一行程式 (ref.),記得先設中斷點,不然跳過去後不會停:
b +1
j +1
  • ret:不執行函式剩下的程式,直接返回上層 frame

ps. 按 Enter 可重覆上個指令, 在移動指令時和切 stack frame (後述) 時特別好用。

在 call stack 之間移動

  • up: 往上移一個
  • do: 往下移一個
  • f N: 跳到 stack frame N

觀察值

  • p EXPRESSION: 印出 EXPRESSION 的值, 可是變數、函式等
  • 印出 smart pointer 的值 (ref.): 得先取出裡面的 pointer 再取出它的 member function / field
  • whatis VAR: 看型別
  • ptype VAR: 看型別的宣告內容,了解有那些欄位可讀
  • p P@N: 印出位置 P 開始 N 個變數的值
  • p *P@N: 印出對位置 P 開始 N 個變數取值後的值
  • p *argv@argc: 在 main() 函式裡執行這指令, 會印出命令列參數內容

thread

進階指令

  • commands BREAKPOINT_ID: 定義在進入 breakpoint 後, 執行一系列指令, 比方 “p some_var; c”。
  • define NEW_CMD: 類似函式, 定義由一堆指令組成的新指令。
  • Python API: 這裡有我自訂的 backtrace,詳細說明見這裡

其它指令

  • set var X = …:執行期間改變變數 X, 以在執行期驗證小修改是否有效, 簡省編譯時間
  • handle SIGSTOP nostop noprint: 收到 SIGSTOP 時不要停且不要輸出訊息, 在 Android 環境有時 gdb 會莫明地一直收到 SIGSTOP
  • h CMD: 在 gdb 內查說明文件。比方說 h b 會得知指令 bbreak 的縮寫。

程式偵測是否被 gdb 監視中

兩個實用情境:

  • 輸出嚴重錯誤的 log 時,可以檢查是否有 gdb 在監控,有的話不如直接中斷 gdb。Chromium 有實作跨平台的函式 BreakDebugger(),可以中斷 debugger。這是 POSIX 版的實作
  • 寫 multi-process / multi-thread 時,有個技巧是定期回報 heartbeat,確保 process/thread 正常運作,heartbeat 太久沒回報就觸發防護措施 (例如重啟 process)。使用 gdb 除錯時,可能在中斷點待太久而觸發安全防護機制。這時自動關閉防護措施比較方便。

Chromium 的函式 BeingDebugged() 有提供跨平台的偵測方法, POSIX 版的實作見這裡。

參考資料

--

--