GDB調試(gdb調試命令大全)
約定:對gdb的命令,如果有縮寫形式,會在第一次出現的時候小括號內給出縮寫,比如運行命令寫成run(r);本文中尖括號< >用來表達一類實體,比如

GDB簡介
編譯的時候加上-g參數,編譯器就會在目標文件中添加調試信息(關于編譯鏈接可參閱《從四個問題透析Linux下C++編譯&鏈接》),對應的strip命令可以去除調試信息。通過objdump/readelf等工具可以看到目標文件中有很多包含“debug”字符的section。這些section里保存了調試信息,目前ELF文件采用DWARF 3(Debug With Arbitrary Record Format)標準的調試信息格式。
使用GDB你可以:
1. 自定義程序運行方式
2. 讓程序停止在你指定的位置:設置斷點
3. 在停止點查看當前程序的狀態:變量、寄存器的值
4. 動態改變程序的狀態
通常GDB命令都會有一個簡短的表達,比如設置斷點的break命令可以簡寫為b,方便減少輸入,本文中對第一次出現的命令都會在括號內給出對應的簡短表達。回車在GDB相當于重復上一個命令。
啟動GDB運行程序
運行GDB調試a.out程序有以下幾種方式:
方式一:直接運行gdb,然后在gdb中執行“file a.out”加載程序。
方式二:gdb
方式三:gdb
方式四:gdb
方式五:對運行中的a.out,可以先按方式二啟動,然后在gdb中中心“attach 19475”調試運行中的a.out
進入到GDB后,可以通過help命令來獲取幫助,GDB對命令做了分類,要獲取詳細說明可以查看help的相關輸出。
啟動GDB后, 就可以運行a.out了,本例比較簡單直接執行run(r)命令即可,但對于稍微復雜點的程序可能需要做一些額外的設置工作:
1. 設置運行參數:通過“set args
2. 設置運行環境:
通過“path
通過“set environment [=value]”設置環境變量,比如要設置用戶名可以“set environment USER=wanggaofei”,show environment可以查看所有的環境變量。
通過cd命令可以更改目錄,pwd顯示當前所在目錄
準備就緒后就可以真正開始用GDB來調試程序了。
暫停、恢復程序運行
調試程序,首先是要讓程序是某些感興趣的點上停下來,GDB有以下幾種方式通知GDB暫停程序的運行:斷點、觀察點、捕捉點(GDB中這三種都統稱為斷點breakpoints)、信號、線程停止。
斷點
設置斷點:break(b)命令
break:在下一條指令上設置斷點,GDB是基于機器指令工作。
break
break
break
break
break +
break -
break *
:在指定的虛擬地址上設置斷點break
tbreak
查看斷點:
info breakpoints [n]:其中n是斷點序號,是可選參數,不提供則顯示所有斷點
刪除斷點:delete(d)
delete [break_num_list] [range]:break_num_list是可選參數,可以是一個斷點序號的列表,用空格分開,range可以是一個范圍例如1-5,刪除編號區間[1,5]的斷點,如果不提供任何參數則刪除所有的斷點。
clear
禁用斷點:disable(dis)
有的時候你想臨時讓斷點不起作用,又不想刪除斷點,否則過一會還要再設置這個斷點,這時候可以暫時禁用斷點。
disable [break_num_list] [range]:參數和delete的參數意義相同
啟用斷點:enable
當你想再次啟用斷點時可以enable它。
enable [break_num_list] [range]:參數意義同disable,enable有不少子命令,具體參考help enable
條件維護:condition
condition
condition
隨著調試的進行,你可能需要修改停止條件,比如在for循環中,剛開始你會在循環變量等于N的時候停住程序,查看相關變量,發現沒問題后,你會選取一個更大的M,讓循環變量等于M的時候停住,看看有沒有問題,這時候就需要更改條件,這就是condition大顯身手的時候。
斷點命令:commands
commands [break_num]
command_list
end
通常在斷點處都是為了查看某些變量的值,如果能在斷點處自動打印這些值,豈不爽歪歪?commands就是用來干這個的,省的你動手。如下示例
commands 1 slient printf “i is %d\n”, i end
在觸發斷點1時打印變量i的值,slient是讓GDB安靜的觸發斷點,不要打印一些沒用的信息。
恢復執行:
continue [ignore_count]:continue(c)命令恢復程序運行直到下一個斷點或者結束,參數ignore_count是個數字,代表忽略之后的斷點次數。
step [count]:單步跟蹤,碰到函數會進入,count參數相當于執行count次step的效果,對單步跟蹤,有各選項step-mode可以通過set命令設置其為on或者off,設置為on后,對沒有debug信息的函數會停止在函數的第一條指令上。否則step會跳過該函數。
next [count]:單步跟蹤,跟step的區別是碰到函數時不會進入函數,count效果同step中參數。
finish:運行程序直到函數完成,打印返回的堆棧地址和返回值及參數信息。
util [break_args]:until(u)不帶參數跳出循環,break_args同clear中參數。
stepi(si)、nexti(ni),這里的i代表指令級別,其他和step,next相同
觀察點
觀察點用來觀察某個表達式的值是否發生了變化,如果有變化,則馬上暫停程序。觀察點和斷點的一個顯著區別是觀察點由于是觀察表達式的值,而表達式中變量是有作用域的,當離開作用域時觀察點自動刪除,但斷點是和代碼綁定,只要代碼不變斷點就一直存在。
設置觀察點:
watch
rwatch
awatch
查看觀察點:
info watchpoints:info breakpoints也可以把觀察點列出,但這個命令會把所有breakpoints都列出來。
刪除觀察點:
通過delete命令
捕捉點
捕捉點用來捕捉程序運行中的一些事件,比如加載共享庫或者異常
catch
tcatch
信號
信號是unix/linux下的一項重要技術,GDB可以讓你在收到指定信號時采取行動
處理信號:
handle
stop:收到該信號時,GDB會停住程序
nostop:收到信號時,GDB不會停住程序,但是會打印消息告訴你收到該信號
print:收到信號時,打印一條消息
noprint:收到信號時,GDB不會高告訴你收到信號
pass/noignore:收到信號時,GDB不做處理,讓程序的信號處理程序接手
nopass/ignore:收到信號時,GDB不會讓程序看到整個信號
查詢信號處理情況:
info signals
info handle
線程
info threads:顯示所有線程
thread
break
thead apply
set scheduler-locking off|on|step:默認是off,也就是調試的時候所有線程都會執行;on表示只有當前線程執行;step表示在step單步執行的話只有當前線程執行,只有在next跨過函數的時候其他線程可能運行
查看棧信息
程序停住后,你可以查看程序的當前狀態,比如目前程序現在執行到哪了?是執行了哪條路徑的代碼?GDB通過幾個命令幫助你分析棧信息,以及在棧間切換。
backtrace [n]:backtrace(bt)命令打印當前調用棧的信息,n為可選參數,既可以是整數也可以是負數,表示只打印棧頂上n層的棧信息或棧底n層信息。
frame [n]:frame(f)切換幀,n為一個從0開始的數,表示棧中的層次編號,0代表棧頂。
up [n]:向棧的上面移動n層
down [n]:向棧的下面移動n層
info frame:打印詳細的棧信息,主要以程序的虛擬地址信息為主
info args:打印當前函數參數和對應值
info locals:打印當前函數局部變量和對應值
查看源代碼
在查看棧信息的同時,你可能會對源代碼感興趣,以幫助你更好的理解程序的來龍去脈(如果你用的是Emacs編輯器,這種需求就會大大減少,因為Emacs和GDB配合的非常好),GDB提供了相應的命令來顯示和查找源代碼。
顯示源碼:
list [list_args]:list(l)顯示源代碼,list_args類似break中的break_args參數,可以是行號,函數等,詳細參考help list。有一個參數listsize控制一次顯示源代碼的行數,可以通過show listsize顯示該值,通過set listsize
查找源碼:
forward-search
search
reverse-search
指定源代碼搜索路徑:
directory
show directories:顯示當前源碼搜索路徑。
顯示源代碼虛擬地址:
info line [line_args]:顯示源碼虛擬地址,line_args和前面的list_args類似,詳細參考help info line。
disassemble:反匯編代碼,細節查看help disassemble
檢查和設置變量
調試最終要查看程序運行的狀態,通過觀察當前各個變量或者表達式的值來判斷程序當前是否符合預期,如果不符合預期,及時分析原因,從而排查bug。GDB提供了相關命令查看和設置變量。
查看變量類型:
ptype type_name|expression:type_name可以是一個類型名,比如結構體或者類名,expression可以是某個變量
whatis expression:可以理解為精簡版ptype,ptype會展開所有類型定義,whatis則不會
打印表達式:
print [/
x
按十六進制格式顯示變量
d
按十進制格式顯示變量
u
按無符號十進制顯示變量
o
按八進制格式顯示變量
t
按二進制格式顯示變量
a
address和x效果差不多
c
按字符格式顯示變量
f
按浮點格式顯示變量
打印數組:
print *pArr@10:pArr是指向數組的指針,10表示要打印的元素的個數
通過“::”打印文件、函數或者C++類的變量:
print main::value
打印內存:
x [/
x /10dw pArr:表示從內存地址pArr開始打印10個元素,每個元素占用4字節(w控制),以十進制顯示(d控制)
自動打印:
要是在每次程序停住的時候,能自動幫你打印變量的值,可以大大減少手工輸入,display就可以做到。
display [/format]
undisplay
delete display
disable display
enable display
歷史記錄:
用GDB的print命令查看狀態時,GDB會以$1,$2這樣的編號標記之前的表達式,這些編號稱為值歷史。對于那種很長的表達式,通過值歷史查看可以省去很多輸入
設置變量:
調試的過程中,可能需要人為的設置變量的值,從而可以快速的了解,當變量是這個值的時候,程序是什么表現,通過set命令可以很簡單的實現。
set value=11:設置變量value的值為11
方便變量:
有時候想挨個打印數組的值,如果GDB能提供一個變量作為數組的下標,隨著循環的進行變量值也隨著變化,這樣查看數組元素的值就非常方便了。
(gdb) set $i = 0 (gdb) p arr[$i++]
$i就是方便變量,后面通過回車就可以不斷打印arr中的值。
查看寄存器:
有時候可能會關心寄存器中的值,比如在core dump后,想查看下當時現場。
info registers [register_name]:查看寄存器,如果給出具體的register_name則只顯示指定寄存器的值
info all-registers:比上面顯示更多的寄存器值,比如浮點寄存器
改變程序的運行
在用GDB不斷調試的過程中,你慢慢已經掌握了程序的執行脈絡,這時候你肯定希望按照自己的調試策略來改變程序的路徑,有了這個能力,在調試中對程序就可以為所欲為,一次走完程序的所有路徑。
修改變量:
上節在設置變量中提到可以通過set命令來設置變量的值,但當你代碼中的變量和GDB中的參數名字一樣時,需要如下設置。
(gdb) set var width=80
另外通過print命令也可以方便的設置變量
(gdb) print value=11
跳轉執行:
jump
產生信號:
在前面信號一節中只提到了處理信號,我們也可以在GDB中隨時產生一個信號。
signal
強制函數返回:
return [
強制調用函數:
call
開發常見問題
調試是一種事后補救措施,最好是盡可能避免調試,或者盡可能將調試的工作壓縮在開發階段,在線上出問題和調試,那種酸爽只有經歷過的人才知道。因此開發階段務必加強對測試的重視,代碼都需要單元測試來覆蓋,各個模塊集成測試,線上的性能和穩定性壓測等都必不可少。
下面我們針對開發過程中常見的問題做一個梳理:
問題一:編譯問題
在寫一個稍微大一點的cpp時,由于括號沒有匹配導致很奇怪的報錯,這個時候可以采用二分法來注釋代碼,從而快速定位問題發生的區域。這給我們一個啟示,在寫代碼的時候注意保持良好的輸入習慣:在輸入括號的時候先把左右括號都輸完整,再在中間填代碼;在寫一個新函數的時候首先把return語句寫上;在寫if語句的時候最好else語句也先填上,后面如果用衛語句的話,直接刪掉else就好了,這樣不會漏掉邏輯;調用函數的時候也立刻寫代碼假設函數返回出錯的處理。
問題二:段錯誤
寫C、C++代碼最常見的問題是對內存的不當處理,最常見的莫過于段錯誤,典型的如訪問不存在的內存地址、訪問了不允許訪問的地址(試圖往只讀的位置寫數據)。常見產生的原因:1. 訪問空指針;2. 內存越界訪問;3. 棧溢出;4. 地址保護。
空指針:我們先來看一下64位Linux下運行時虛擬地址的分布情況如圖,可以看到有效的虛擬地址是從0X400000開始的,對任何低于該地址的虛擬地址都是非法的,因此訪問空指針(地址為0X0)會引發段錯誤,另外在調試過程中有一些地址雖然不是0地址,比如查看某個對象的成員,但實際上this指針已經是0地址,但由于訪問成員的時候加上了地址偏移,這種地址和0地址沒什么區別。
內存越界:并非所有的越界訪問都會導致段錯誤,因為Linux系統分配內存都以頁(一個頁通常是4K大小)的方式進行,當你有內存越界時,雖然超出了你代碼預期的內存空間,但如果還在當前頁面內,你訪問的內存空間還是一個有效的空間,并不會引發段錯誤。對這類問題最好在單元測試中用4.8.5以上的gcc打開地址消毒,或者用valgrind進行檢測。
棧溢出:當在棧上分配很大的數組時很容易導致棧溢出,對于較大內存的使用最好是通過動態內存分配來獲取。
地址保護:在mmap做內存映射時,如果嘗試往只讀的映射區寫入數據會導致段錯誤。
問題三:總線錯誤
在開發中出發總線錯誤的兩個常見場景:1. 內存地址不滿足對齊要求,比如Intel的intrinsic接口中很多對地址有對齊要求,如果不滿足對齊要求就會報總線錯誤;2. 在mmap時,映射了一個文件,但其他進程將底層的文件截短,當訪問到這部分截掉的內容時,會發生總線錯誤。
問題四:全局符號介入
在《從四個問題透析Linux下C++編譯&鏈接》中提到全局符號介入,這種問題通常會引起core dump,要定位相關問題需要對代碼執行路徑有一定了解,通過GDB反饋的當前幀符號來源來定位符號是否來自非預期的庫中。對于某些飄忽不定的core dump,還要看是不是由于當前這次發布引入了錯誤版本的動態庫?由于接口的變化導致類似全局符號介入的效果。
問題五:無源碼調試
在沒有源代碼的時候strace就可以發揮神威了,strace會記錄程序所產生的每次系統調用,系統調用的名字,參數,返回值會在同一行顯示,通過觀察返回值的異常對于快速定位問題非常有幫助。
Linux 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。