gdb入門

簡介

只要使用GNU計畫出產的編譯器(如gcc),就可以用gdb來進行除錯,包含 C/C++/Objective-C/Fortran/Java 等語言。

基本知識

function stack → frame

  • 正在執行的函式就是 frame 0,而呼叫該函式的函式就是 frame 1,以此類推
  • 在進入一個frame之前,它會將原本所在的frame的變數值及資料(函式名、指標)儲存至堆疊(stack)裡,等到回傳後再從這些stack裡把這些變數值取回來

除錯程序

  1. 載入程式
  2. 設中斷點(程式跑到中斷點會停下來)
  3. 執行程式
  4. 在堆疊間跳躍
  5. 檢查變數值

1.載入程式

編譯時加入 -g 可加入除錯資訊。這些除錯訊息會使檔案大小差10倍之多,所以一般在發佈應用程式時是不會以-g參數編譯的。可以在事後利用 strip 指令清掉應用程式裡的除錯資訊。編譯時最好不要開optimization(-O),不然優化後的程式的執行順序會改變,較難debug。

一般情況下,用gdb [program]即可開始除錯

gdb shell參數 簡寫 說明
-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 這個參數要當作命令列的最後一個參數,其後跟隨的參數
都會被視為「傳給要除錯的程式的參數」

2.設中斷點

[breakpoint] 可以是 file+line number, function, memory address

指令 停止時機
break gdb執行下一個指令後停止
break [breakpoint] 在指定行數、函式開始前、或指定位址停止
break [breakpoint] if [condition] 只有在符合條件時停止
break [offset] 在目前程式停止位置的offset行停止
offset負數時,為前offset行,反之
break [filename]:[linenum] 指定檔案的指定行停止
rbreak [RE] 符合正規表示式的函式停止
rbreak ., 這樣每個函式開頭都有中斷點了
tbreak [args] 只會生效一次, args能放的參數與break相同
disable [breakpoint number] 暫時關閉指定中斷點,若無指定則全部關閉
enable [breakpoint number] 取消暫時關閉狀態
指令 說明
info break 列出目前所有的中斷點
condition [breakpoint number] [condition] 設中斷點的條件,如果條件為true才中斷
commands [breakpoint number] [command] 設定遇到指定中斷點時要自動執行的指令
clear [breakpoint] 刪除指定中斷點
delete [breakpoint number] 刪除指定中斷點

3.執行程式

指令 說明
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 的程式

4.在堆疊間跳躍

主要指令 說明
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] 設定一次印出幾行

5.檢查變數值

可以顯示某些資訊以利於debug

1
2
(gdb) print i
$1 = 6078 # i = 6078

輸入 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
2
set $newv = model->dataset->vector->data  
p *($newv++)

info: 檢視詳細資訊

指令 說明
info break 列出目前所有的中斷點
info line 查看程式目前運行的行數
info frame 詳細的frame資訊
info args 顯示傳給目前執行函式的參數值
info locals 顯示目前執行函式內所有區域變數的值
info reg 顯示暫存器(register)的值
info all-reg 顯示暫存器的值,包括數學運算暫存器
info handle 列出目前處理 signal 的設定
info share 顯示共享函式庫資訊

其他指令

信號(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 整合

  • 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

其他指令

  • quit 結束 = q
  • help [command] 查看該指令的說明 = h
  • shell [command] 在shell上執行指令
  • Enter鍵: 重複上一個命令

CppCon 2015: Greg Law “Give me 15 minutes & I’ll change your view of GDB”

https://www.youtube.com/watch?v=PorfLSr3DDI
https://github.com/CppCon/CppCon2015/blob/master/Lightning%20Talks%20and%20Lunch%20Sessions/Give%20Me%2015%20Minutes%20and%20I'll%20Change%20Your%20View%20of%20GDB/Give%20Me%2015%20Minutes%20and%20I'll%20Change%20Your%20View%20of%20GDB%20-%20Greg%20Law%20-%20CppCon%202015.pdf

  • 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官方文件