gdb簡介
簡介
只要使用GNU計畫出產的編譯器(如gcc),就可以用gdb來進行除錯,包含 C/C++/Objective-C/Fortran/Java 等語言。
基本知識
function stack → frame
- C語言在進入一個函式前,會將原本所在的函式的資訊(變數值、函式名、指標)儲存至堆疊(stack)裡,等到回傳(return)後再從這些堆疊裡復原函式資訊
- 目前正在執行的函式就是 frame 0,而呼叫frame 0的函式就是frame 1,以此類推
除錯程序
- 載入程式
- 設中斷點
- 執行程式,程式跑到中斷點會停下來
- 在堆疊間跳躍,並檢查變數值
載入程式
- 編譯時加入 -g可加入除錯資訊。這些除錯訊息會影響程式大小和執行速度,所以一般在發佈應用程式時是不會以-g參數編譯的。- 可以在事後利用 strip 指令清掉應用程式裡的除錯資訊。
 
- 編譯時最好不要開optimization(-O),不然優化後的程式的執行順序會改變,較難debug。
執行gdb [program]即可開始除錯
| gdb程式參數 | 簡寫 | 說明 | 
|---|---|---|
| -symbols [file_name] | -s | 讀取檔案中的除錯表 | 
| -exec [file_name] | -e | 除錯一個執行檔 | 
| -core [file_name] | -c | 讀入一個core dump檔案 | 
| -pid [process_id] | -p | 啟動attach模式,除錯一個執行中的程式 | 
| -directory [directory_name] | -d | 將資料夾加入原始碼的搜尋路徑 | 
| -readnow | -r | 一次讀取完所有的符號表,這會讓啟動gdb的時間變長,但在執行往後的除錯動作會較快速。 | 
| -quiet/-silent | -q | 安靜模式,啟動時gdb將不會顯示版權頁。 | 
| -cd directory_name | 改變程式執行的目錄 | |
| --args | 這個參數要當作命令列的最後一個參數,其後跟隨的參數都會被視為「傳給要除錯的程式的參數」 | 
設中斷點
[breakpoint] 可以是 file+line number, function name, memory address
| 指令 | 停止時機 | 
|---|---|
| break | gdb執行下一個指令後停止 | 
| break [breakpoint] | 在指定行數、函式開始前、或指定位址停止 | 
| break [breakpoint] if [condition] | 只有在符合條件時停止 | 
| break [offset] | 在目前程式停止位置的offset行 停止offset負數時,為前offset行,反之 | 
| break [filename]:[linenum] | 指定檔案的指定行停止 | 
| rbreak [RE] | 符合正規表示式的函式停止 如 rbreak ., 這樣每個函式開頭都有中斷點了 | 
| tbreak | 只會生效一次,能放的參數與break相同 | 
| disable [breakpoint number] | 暫時關閉指定中斷點,若無指定則全部關閉 | 
| enable [breakpoint number] | 取消暫時關閉狀態 | 
| 指令 | 說明 | 
|---|---|
| info break | 列出目前所有的中斷點 | 
| condition [breakpoint number] [condition] | 設中斷點的條件,如果條件為true才中斷 | 
| commands [breakpoint number] [command] | 設定遇到指定中斷點時要自動執行的指令 | 
| clear [breakpoint] | 刪除指定中斷點 | 
| delete [breakpoint number] | 刪除指定中斷點 | 
執行程式
| 指令 | 說明 | 
|---|---|
| file [filename] | 開啟檔案 (等同於 gdb filename) | 
| run | 執行程式 (可加參數) | 
| kill | 終止程式 | 
| set | 設定特定參數(或變數) 如: set environment CFLAGS=-g | 
| unset | 取消特定參數 如: unset environment | 
| show | 顯示特定參數 如: show environment | 
| set/show args | 設定/顯示 命令列的參數 | 
| attach [PID] | 載入正在執行的程式以進行除錯。PID 可由 ps 指令取得 | 
| detach [PID] | 釋放已 attach 的程式 | 
在堆疊間跳躍
| 主要指令 | 說明 | 
|---|---|
| next | 執行當前函式的一個指令 若指令為呼叫函式,則直接跑完,不會進入frame中 | 
| step | 執行函式中的一個指令 若指令為呼叫函式,會進入新的frame中 | 
| until | 直接跑完迴圈(for, while...) | 
| continue | 繼續執行,直到下一個中斷點或是程式停止 | 
| return | 視同該 frame 已執行完畢 等同插入C語言指令 return; | 
※註:若該函式的除錯資訊沒有編進執行檔裡的話,那step也不會跳進這個函式裡,而是單純的將它看作一行程式碼(如同next的作用),如標準函式庫(如stdio.h)提供的函式。
| 指令 | 說明 | 
|---|---|
| backtrace | 堆疊追蹤。會顯示出所有的 frame 的資訊 = info frame | 
| frame | 顯示現在的行數、函式、及其所傳送的參數 | 
| frame [frame number] | 切換到指定的frame(以印出區域變數) | 
| up | 回到上一層frame,也就是原本的frame被呼叫的地方,並顯示其 stack 資訊 up 3: 回到上三層frame(0 → 3) | 
| down | 到下一層frame | 
| finish | 執行完目前的frame | 
| jump [location] | 直接跳到指定位置(行數,函式…) | 
列出原始碼
| 指令 | 說明 | 
|---|---|
| list(第一次) | 列出現在執行的位置上下5行 | 
| list(第二次以後) | 繼續印出之後的程式碼(類似page down) | 
| list - | 印出上一次list的程式碼的前一段程式碼(類似page up) | 
| list a,b | 印出第 a ~ b 行 | 
| list [filename]:[number] | 列出某檔案的第幾行,檔案名可省略 | 
| list [function] | 列出某函數的程式碼 | 
| show listsize | 顯示現在一次印出幾行 | 
| set listsize [num] | 設定一次印出幾行 | 
檢查變數值
可以顯示某些資訊以利於debug
print, display
| 1 | (gdb) print i | 
輸入 print/格式字元 [variable] 可以指定型態,與printf不同的以粗體表示
| x | 格式字元 | 
|---|---|
| d | 整數 | 
| u | 無號整數(unsigned) | 
| o | 八進位 | 
| t | 二進位 | 
| a | 位址 | 
| c | 字元 | 
| f | 浮點數 | 
| 指令 | 說明 | 
|---|---|
| whatis [variable] | 顯示指定變數的型態 | 
| print arr[1]@5 | 印出變數arr[1]和之後的變數,共印出5個(arr[1]~arr[5]) | 
| print *arr@3 | 印出陣列arr的前3個變數(arr[0]~arr[2]) | 
| display [variable] | 每次中斷時會顯示指定變數值 | 
變數
執行 print 指令後,gdb 產生臨時變數(如$1)來記錄
可以直接利用 $1 來取用這個變數
用於 print 及 display 的參數名稱
- $7: 第七個運算式
- $: 前一個的運算式
- $$: 前二個的運算式
- $$7: 前七個的運算式
- $pcprogram counter
- $spstack pointer
設定新變數
取代冗長路徑的變數,如在深層資料結構中的變數
(註: 此變數為 pass by reference, 修改新變數的值也會修改原本的變數)
| 1 | set $newv = model->dataset->vector->data | 
info: 檢視詳細資訊
| 指令 | 說明 | 
|---|---|
| info break | 列出目前所有的中斷點 | 
| info line | 查看程式目前運行的行數 | 
| info frame | 詳細的frame資訊 | 
| info args | 顯示傳給目前執行函式的參數值 | 
| info locals | 顯示目前執行函式內所有區域變數的值 | 
| info reg | 顯示暫存器(register)的值 | 
| info all-reg | 顯示暫存器的值,包括數學運算暫存器 | 
| info handle | 列出目前處理 signal 的設定 | 
| info share | 顯示共享函式庫資訊 | 
其他指令
- quit結束 =- q
- shell [command]在shell上執行指令
- Enter鍵: 重複上一個命令
信號(signal)處理
handle [signal] [operation]
- 預設operation為 stop,print,noignore,也就是遇到 signal 時,GDB 會先攔截,並暫停程式
- 必要時可以改為 nostop,noprint,讓程式本身去處理 signal
- 若下達 ignore則是讓程式忽略此 signal
thread處理
| 指令 | 說明 | 
|---|---|
| thread | 查看目前在哪個 thread | 
| thread [num] | 切換至 第num個 thread | 
| thread apply all [command] | 對所有 thread 執行指令 | 
Python 整合
可於gdb中執行python,並執行操作,如存取breakpoint
- python print(gdb.breakpoints())
- python gdb.execute()
- python gdb.parse_and_eval()
反向執行
GDB 反向調試(Reverse Debugging)
- reverse-continue反向執行程式
- reverse-step
- reverse-stepi反向執行程式到上一條機器指令
- reverse-next反向執行到上一次被執行的源代碼行,但是不進入函式
- reverse-nexti反向執行到上一條機器指令,除非這條指令用來返回一個函式調用、整個函式將會被反向執行
- reverse-finish反向執行程式回到調用目前函式的地方
- set exec-direction [forward | reverse]設置程序執行方向,即可用一般的step和continue來反向執行
指令簡寫
- break: b
- delete: d
- disable: dis
- next: n
- step: s
- until: u
- continue: c
- jump: j
- return: ret
- list: l
- backtrace: bt
- info: i
- print: p
- display: disp
- environment: env
可多次使用簡寫
- info break=- i b
CppCon 2015: Greg Law "Give me 15 minutes & I'll change your view of GDB"
Youtube連結
Github連結
- Ctrl-x a: text ui mode(tui)- Ctrl-p: 得到前一個指令
 
- Ctrl-x 2: switch to assembly mode/register group mode
- tui reg float: show float registers
- b _exit.c:32: 在程式結束之前停止
- command [breakpoint number] [command]: 遇到breakpoint時,所自動執行的指令
- x [memory address]: jump to memory location
使用感想
- 遇到segmentation fault的時候,一定會使用
- 可知道出問題的位置
- 通常再print看看就知道原因了
 
- 平常還是用printf- 殺雞焉用牛刀?
 
- 如果程式有完整的錯誤處理和記錄檔(log),可減少用到gdb的頻率
參考資料
- 除錯程式:gdb
- Linux 除錯利器-GDB簡介
- 工欲善其事,必先利其器:GDB基本教學
- 用Open Source工具開發軟體: 新軟體開發關念
- GDB官方文件