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
: 前七個的運算式$pc
program counter$sp
stack 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
: bdelete
: ddisable
: disnext
: nstep
: suntil
: ucontinue
: cjump
: jreturn
: retlist
: lbacktrace
: btinfo
: iprint
: pdisplay
: dispenvironment
: 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 modetui reg float
: show float registersb _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官方文件