gdb 雜項心得
Published in
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 套件有附 scriptgdb-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
: 存下目前設的中斷點到檔案 FILEso FILE
: 載入之前設的中斷點dis 1–10
: 暫停使用 breakpoints 1–10。ena 1–10
: 恢復使用 breakpoints 1–10。
偶而會用到 conditional break。
移動
n
: 跳下行s
: 若有函式, 跳進去; 反之則同 nfin
: 執行到函式結尾, 返回上一層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
i thr
: 列出目前的 thread,建議事先用 prctl / pthread_setname_np 設定 thread name,比較好辨別thr NUM
: 切換 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 會莫明地一直收到 SIGSTOPh CMD
: 在 gdb 內查說明文件。比方說h b
會得知指令b
是break
的縮寫。
程式偵測是否被 gdb 監視中
兩個實用情境:
- 輸出嚴重錯誤的 log 時,可以檢查是否有 gdb 在監控,有的話不如直接中斷 gdb。Chromium 有實作跨平台的函式 BreakDebugger(),可以中斷 debugger。這是 POSIX 版的實作。
- 寫 multi-process / multi-thread 時,有個技巧是定期回報 heartbeat,確保 process/thread 正常運作,heartbeat 太久沒回報就觸發防護措施 (例如重啟 process)。使用 gdb 除錯時,可能在中斷點待太久而觸發安全防護機制。這時自動關閉防護措施比較方便。
Chromium 的函式 BeingDebugged()
有提供跨平台的偵測方法, POSIX 版的實作見這裡。
參考資料
- 《Debugging with GDB》: 待用一陣子後再來掃一遍,挑出有用的東西。