GDB輕松調(diào)試
一、引言
在了解GDB可以做什么,怎么做之前,讓我們先來(lái)看看為什么要用GDB,或者說(shuō)對(duì)調(diào)試工具有什么期望。
一般我們使用GDB(或其他調(diào)試工具)是為了發(fā)現(xiàn)程序bug,更經(jīng)常地是在已知程序有錯(cuò)的情況下定位bug。既然這樣,我們就需要跟蹤程序的執(zhí)行情況,查看程序執(zhí)行是否正常,當(dāng)然這就需要有個(gè)讓我們與執(zhí)行程序交互的環(huán)境,調(diào)試工具提供一個(gè)能讓程序在你的掌控下執(zhí)行,并讓你能夠查看一些執(zhí)行過(guò)程中的“內(nèi)幕信息”的環(huán)境。
為了查看程序運(yùn)行過(guò)程中的狀態(tài),我們就希望程序能在適當(dāng)?shù)奈恢没蛘咴谝欢ǖ臈l件下能夠暫停運(yùn)行;為此,調(diào)試工具提供了斷點(diǎn)、查看變量/表達(dá)式、顯示程序棧等功能。看了某個(gè)點(diǎn)的“內(nèi)幕”后,我們還期望更多,所以要能控制程序運(yùn)行才行,這就要求斷點(diǎn)、繼續(xù)運(yùn)行、單步(多步)運(yùn)行、進(jìn)入函數(shù)運(yùn)行等功能,在某些情況下,還需要通過(guò)修改當(dāng)前的執(zhí)行環(huán)境(變量等)來(lái)達(dá)到期望的執(zhí)行順序。也就是說(shuō),光看著是不夠的,還需要能改才行。
理解了這些問(wèn)題后,我們就明白GDB的各個(gè)功能的用意了,自然也就明白該如何使用調(diào)試工具了。當(dāng)然,要讓GDB有效的發(fā)揮作用,還是需要一定的經(jīng)驗(yàn)與技巧,而這主要靠實(shí)踐,學(xué)習(xí)資料(包括本文)充其量只能幫你一把(小心別讓它幫倒忙)。
總而言之,我們首先要明白使用調(diào)試工具的目的和用意,才能理解它的各項(xiàng)功能,才能借助它快速有效的發(fā)現(xiàn)問(wèn)題;否則,即使工具再?gòu)?qiáng)大,你也不知道該如何使用才好。
另外要多結(jié)合使用代碼檢視、運(yùn)行日志、測(cè)試工具等方法來(lái)發(fā)現(xiàn)潛在的問(wèn)題,提供程序的質(zhì)量。這些問(wèn)題將在另文探討,先做個(gè)廣告。
?
?
二、GDB能做什么
GDB可以用來(lái)調(diào)試C、C++、Modula-2的程序。一般來(lái)說(shuō),GDB能做的事大致可以分為四類:
1、啟動(dòng)程序,按指定的方式執(zhí)行程序。
2、在指定條件下使程序暫停.
3、當(dāng)程序被停住時(shí),可以檢查此時(shí)你的程序中的變化。
4、改變程序中的變量或執(zhí)行順序來(lái)試驗(yàn)。
?
?
三、GDB使用概述
首先要了解的是gdb的help命令,因?yàn)槟憧赡苡洸蛔「鱾€(gè)命令的語(yǔ)法和用途,但只要能正確使用help命令,你就不需要任何其它的gdb資料。
啟動(dòng)gdb后,輸入help
[eric@linux eric]$ gdb
GNU gdb Red Hat Linux (5.3.90-0.20030710.40rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands
Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
如上文顯示,gdb的命令很多,所以把它分成許多個(gè)種類。help命令只是例出gdb的命令種類,如果要看某類中的命令,可以使用help <class> 命令,如:help breakpoints,查看設(shè)置斷點(diǎn)的所有命令。當(dāng)如也可以直接help <command>來(lái)查看某個(gè)命令的具體信息。
gdb 技巧:在記不清整個(gè)命令時(shí),可以只打命令的前一個(gè)或幾個(gè)字符,然后敲擊兩次TAB鍵來(lái)列出所有以這幾個(gè)字符開(kāi)頭的命令;另為,大多命令都有縮寫(xiě),如b同 break,c同continue,n同next,p同print等。另為,一個(gè)命令在輸入能唯一標(biāo)示命令的前綴后,按一下TAB鍵就能補(bǔ)齊命令的全稱,比如輸入ba后按一下TAB鍵,就自動(dòng)補(bǔ)齊為backtrace,輸入pr后按一下TAB鍵就補(bǔ)齊為print。
為調(diào)試編譯代碼
為了使 gdb 正常工作, 你必須使你的程序在編譯時(shí)包含調(diào)試信息. 調(diào)試信息包含你程序里的每個(gè)變量的類型和在可執(zhí)行文件里的地址映射以及源代碼的行號(hào). gdb 利用這些信息使源代碼和機(jī)器碼相關(guān)聯(lián).
在編譯時(shí)用 -g 選項(xiàng)打開(kāi)調(diào)試選項(xiàng).
在GDB中運(yùn)行程序
當(dāng)以gdb <program>方式啟動(dòng)gdb后,可以使用r或是run命令運(yùn)行程序。在程序運(yùn)行之前,你有可能需要設(shè)置下面四方面的事。
1、程序運(yùn)行參數(shù)。
set args 可指定運(yùn)行時(shí)參數(shù)。(如:set args 10 20 30 40 50)
show args 命令可以查看設(shè)置好的運(yùn)行參數(shù)。
2、運(yùn)行環(huán)境。
path <dir> 可設(shè)定程序的運(yùn)行路徑。
show paths 查看程序的運(yùn)行路徑。
set environment varname [=value] 設(shè)置環(huán)境變量。如:set env USER=hchen
show environment [varname] 查看環(huán)境變量。
3、工作目錄。
cd <dir> 相當(dāng)于shell的cd命令。
pwd 顯示當(dāng)前的所在目錄。
4、程序的輸入輸出。
info terminal 顯示你程序用到的終端的模式。
使用重定向控制程序輸出。如:run > outfile
tty命令可以指寫(xiě)輸入輸出的終端設(shè)備。如:tty /dev/ttyb
調(diào)試已運(yùn)行的程序
可以有兩種方法調(diào)試已運(yùn)行程序:
1、用ps查看正在運(yùn)行的程序的進(jìn)程ID,然后用gdb <program> PID格式掛接正在運(yùn)行的程序。
2、先用gdb <program>關(guān)聯(lián)上程序,并進(jìn)行g(shù)db,在gdb中用attach命令來(lái)掛接程序正在運(yùn)行的進(jìn)程。detach可用來(lái)取消掛接的進(jìn)程。
暫停/恢復(fù)程序運(yùn)行
你可以使用info program 來(lái)查看程序的當(dāng)前的執(zhí)行狀態(tài)。
在gdb中,我們可以有以下幾種暫停方式:斷點(diǎn)(BreakPoint)、觀察點(diǎn)(WatchPoint)、捕捉點(diǎn)(CatchPoint)、信號(hào)(Signals)、線程停止(Thread Stops)。如果要恢復(fù)程序運(yùn)行,可以使用c或是continue命令。
查看變量/表達(dá)式的值
可以使用print expr(或p expr)來(lái)查看程序變量/表達(dá)式的值
顯示程序棧
可以使用backtrace(或bt)來(lái)顯示程序棧
單步跟蹤
next [n] 執(zhí)行下一條(或n條)語(yǔ)句,不進(jìn)入子程序
step [n] 執(zhí)行下一條(或n條)語(yǔ)句,進(jìn)入子程序,可用finish從子程序返回
?
?
?
四、GDB常用命令
backtrace 顯示程序中的當(dāng)前位置和表示如何到達(dá)當(dāng)前位置的棧跟蹤(同義詞:where)
breakpoint 在程序中設(shè)置一個(gè)斷點(diǎn)
cd 改變當(dāng)前工作目錄
clear 刪除剛才停止處的斷點(diǎn)
commands 命中斷點(diǎn)時(shí),列出將要執(zhí)行的命令
continue 從斷點(diǎn)開(kāi)始繼續(xù)執(zhí)行
delete 刪除一個(gè)斷點(diǎn)或監(jiān)測(cè)點(diǎn);也可與其他命令一起使用
display 程序停止時(shí)顯示變量和表達(dá)時(shí)
down 下移棧幀,使得另一個(gè)函數(shù)成為當(dāng)前函數(shù)
frame 選擇下一條continue命令的幀
info 顯示與該程序有關(guān)的各種信息
jump 在源程序中的另一點(diǎn)開(kāi)始運(yùn)行
kill 異常終止在gdb 控制下運(yùn)行的程序
list 列出相應(yīng)于正在執(zhí)行的程序的原文件內(nèi)容
next 執(zhí)行下一個(gè)源程序行,從而執(zhí)行其整體中的一個(gè)函數(shù)
print 顯示變量或表達(dá)式的值
pwd 顯示當(dāng)前工作目錄
pype 顯示一個(gè)數(shù)據(jù)結(jié)構(gòu)(如一個(gè)結(jié)構(gòu)或C++類)的內(nèi)容
quit 退出gdb
reverse-search 在源文件中反向搜索正規(guī)表達(dá)式
run 執(zhí)行該程序
search 在源文件中搜索正規(guī)表達(dá)式
set variable 給變量賦值
signal 將一個(gè)信號(hào)發(fā)送到正在運(yùn)行的進(jìn)程
step 執(zhí)行下一個(gè)源程序行,必要時(shí)進(jìn)入下一個(gè)函數(shù)
undisplay display命令的反命令,不要顯示表達(dá)式
until 結(jié)束當(dāng)前循環(huán)
up 上移棧幀,使另一函數(shù)成為當(dāng)前函數(shù)
watch 在程序中設(shè)置一個(gè)監(jiān)測(cè)點(diǎn)(即數(shù)據(jù)斷點(diǎn))
whatis 顯示變量或函數(shù)類型
命令的具體使用方法請(qǐng)用上面介紹的help查詢,看不明白的地方就多試試。
?
?
五、用例子說(shuō)話
本文是打算寫(xiě)個(gè)簡(jiǎn)單的程序作為例子講解的,后來(lái)一想:“太假”,就講一個(gè)前幾天我的實(shí)際調(diào)試經(jīng)歷吧,因?yàn)楫?dāng)時(shí)沒(méi)抓圖,這里就用文字描述了,請(qǐng)讀者注意其中的思路和方法,具體的一些操作就要?jiǎng)跓┳约喝?shí)踐了
背景:在將一個(gè)linux程序(姑且就叫A吧)重redhat 9.0移植到redhat es時(shí)發(fā)現(xiàn)程序core了
開(kāi)始了,呵呵:
1、首先我查看了程序日志,找到引起程序core掉的數(shù)據(jù)(一個(gè)網(wǎng)頁(yè));//所以說(shuō)日志很重要
2、下載了那個(gè)網(wǎng)頁(yè),用它作為輸入,結(jié)果必core;//確認(rèn)出錯(cuò)環(huán)境
3、用gdb啟動(dòng)程序,然后觸發(fā)錯(cuò)誤后,用bt查看程序棧,記錄棧中的函數(shù)調(diào)用鏈以及出錯(cuò)的代碼行數(shù)
4、在用gdb啟動(dòng)程序,在出錯(cuò)行前設(shè)斷點(diǎn),運(yùn)行之,再觸發(fā)錯(cuò)誤
5、使用next和step精確定位到出錯(cuò)行
6、print一個(gè)指針變量,發(fā)現(xiàn)不是NULL,再看指針?biāo)附Y(jié)構(gòu)的各個(gè)變量也正常
7、好像沒(méi)錯(cuò)呀,呵呵,此處是個(gè)循環(huán),繼續(xù)單步便重復(fù)6
8、發(fā)現(xiàn)循環(huán)中指針遞減,懷疑指針?biāo)笖?shù)組越界,打印數(shù)組起始位置地址
9、繼續(xù)循環(huán)一直到出錯(cuò),打印指針變量,發(fā)現(xiàn)其指向的地址低于數(shù)組起始位置地址,真的越界了
10、初步算是找到了,查看程序源碼,發(fā)現(xiàn)循環(huán)中沒(méi)有判斷該指針是否低于數(shù)組起始位置地址
11、修改代碼后重新運(yùn)行,程序不core了
12、將新程序放到正常執(zhí)行環(huán)境下工作,長(zhǎng)時(shí)間運(yùn)行后沒(méi)有發(fā)現(xiàn)該問(wèn)題重現(xiàn),確認(rèn)解決問(wèn)題
13、通知出錯(cuò)部分(一個(gè)功能函數(shù)庫(kù))的作者問(wèn)題找到、原因
注:為簡(jiǎn)單起見(jiàn)省略了過(guò)程中的一些因系統(tǒng)特殊性引起的工作
?