列位安好。簡單總結(jié)下GDB調(diào)試器的使用。 準(zhǔn)備 默認(rèn)情況下,gcc/g++編譯的可執(zhí)行文件是不包含調(diào)試信息的,GDB是一個源代碼級的調(diào)試器,使用GDB調(diào)試程序需要程序的源代碼、符號及其對應(yīng)的行號等,其中符號和行號可以是單獨的文件,亦可以在編譯時嵌入到可執(zhí)行文件中。使用gcc/g++時使用-g選項即可將必要的調(diào)試信息包含到可執(zhí)行文件中,使用-g3選項還可以將源代碼中的宏信息也包含進(jìn)去。 另外,調(diào)試過程中需要隨時查看源代碼,但源代碼并沒有包含到可執(zhí)行文件中。通常GDB在當(dāng)前目錄查找源文件,但某些情況下(比如調(diào)試系統(tǒng)命令)需要手動指明源代碼的查找目錄,directory ~向GDB指明到$HOME下查找源文件。 啟動 GDB的啟動很靈活,它的各種特性,你可以在Shell下通過選項和參數(shù)指定,也可以在GDB啟動之后在GDB自己的命令行下使用GDB內(nèi)置的命令來指定。最常用的是直接使用命令gdb PROGRAM啟動,這樣gdb自動加載符號表等調(diào)試信息。若要向被調(diào)試程序傳遞參數(shù),可以采用gdb –args program ARG1 ARG2的形式,其中–args(或者-args)是必須的,它告訴GDB該選項之后已經(jīng)沒有GDB需要的選項了。另外,還可以直接使用gdb啟動,然后使用file program加載調(diào)試信息。此時若要設(shè)置被調(diào)試程序的參數(shù),可以使用set命令的args子命令,如set args ARG1 ARG2. 還有一種傳遞參數(shù)的方法,在下面介紹。 斷點 調(diào)試程序,就是使用調(diào)試器(Debugger)通過檢測和改變被調(diào)試程序(Debuggee)的狀態(tài)、控制其執(zhí)行的方式找出被調(diào)試程序中的錯誤和潛在的bug。調(diào)試程序,觀察程序當(dāng)前的行為的前提是讓程序在“適當(dāng)?shù)臅r候”暫停運,那么什么是適當(dāng)?shù)臅r候呢?使用GDB時,讓程序暫停運行需要使用斷點。具體地,斷點又可以分為普通斷點(breakpoint以下簡稱斷點),觀察點(watchpoint),捕捉點(watchpoint)三類。 普通斷點使用break命令(簡寫為b)設(shè)置。break命令的格式為break [bp-spec] [if CONDITION] [thread THREADNUM],bp-spec指明斷點設(shè)置的位置,可以是行號、函數(shù)名或者指令地址,如果bp-spec省略,則斷點被設(shè)置在程序所要執(zhí)行的下一行代碼(或者指令)上。if CONDITION指明當(dāng)程序到達(dá)bp-spec位置時,只有CONDITION條件成立時程序才會暫停。thread THREADNUM用在多線程程序的調(diào)試中,斷點只被設(shè)置在指定的線程號(GDB內(nèi)部而不是系統(tǒng)使用的標(biāo)號)為THREADNUM的線程上,如果THREADNUM為all則所有線程都會被設(shè)置斷點。補充下bp-spec可以是函數(shù)名b FUNCTIONNAME(重載函數(shù)名需要使用”包含才能自動補全),可以是行號b LINENUMBER或者b FILENAME:LINENUMBER,還可以是指令地址b *ADDRESS。另外,break命令還有其它一些變種,比如tbreak設(shè)置臨時斷點,被使用一次就會自動刪除,rbreak使用正則表達(dá)式來指明函數(shù)名。 觀察點使用watch命令,命令格式與break相同,但它并不是指明斷點的位置,而是指明一個表達(dá)式,每當(dāng)該表達(dá)式的值改變時,程序便會被暫停。表達(dá)式可以是某個變量、由若干變量組成的表達(dá)式或者內(nèi)存地址。 捕捉點是另一種斷點,它使用某種事件的發(fā)生作為觸發(fā)條件,命令各式為catch EVENT。這些事件主要包括異常的拋出和捕獲(catch throw/catch)和某個系統(tǒng)調(diào)用(catch fork/open/exec, catch syscall CALLNUM)。 查看當(dāng)前的斷點設(shè)置情況可以使用breakpoints,也可以使用info breakpoints(或者簡寫為i b)命令,查看某個斷點使用breakpoints bpnum,bpnum為斷點號。 使用enable/disable bpnum使某個斷點生效和失效,delete bpnum刪除斷點。bpnum還可以是一個范圍,以此批量操作斷點,比如d 2-6刪除斷點2到6。 使用ignore bpnum COUNT還可以使某個斷點被或略COUNT次,即是說斷點bpnum的前COUNT次到達(dá)都不會被觸發(fā),知道COUNT遞減至0。另外,在COUNT遞減至0之前,該斷點上的條件是不會被考慮的。 CONDITION bpnum [if condition],修改bpnum上的觸發(fā)條件,若if被省略,則bpnum斷點上的條件將被刪除。 運行 GDB啟動和加載調(diào)試信息后,被調(diào)試程序并沒有運行。使用run/r或者start命令,GDB建立子進(jìn)程來運行被調(diào)試程序。run和start命令稍有不同,即run僅僅加載程序然后運行,而start會在程序的入口函數(shù)(main)設(shè)置一個臨時斷點,程序運行到那里就會暫停,臨時斷點也隨即被清除。另外run和start命令后面都可以加上傳遞給被調(diào)試程序的參數(shù),若不加參數(shù)則使用GDB啟動時傳遞的參數(shù)或者使用set args命令設(shè)置的參數(shù)。若要清除參數(shù)而不退出GDB,使用不帶參數(shù)的set args即可。 其它運行相關(guān)的命令還有 continue/c,繼續(xù)運行。 next/n, 下一行,且不進(jìn)入函數(shù)調(diào)用 stop/s, 下一行,但進(jìn)入函數(shù)調(diào)用 ni或者si, 下一條指令,ni與si的區(qū)別同n與s的區(qū)別 finish/fini, 繼續(xù)運行至當(dāng)前棧幀/函數(shù)剛剛退出 until/u, 繼續(xù)運行至某一行,在循環(huán)中時,u可以實現(xiàn)運行至循環(huán)剛剛退出,但這取決于循環(huán)的實現(xiàn)。 查看 調(diào)試的主要工作,我想就是檢查程序的狀態(tài)吧,內(nèi)存的狀態(tài),程序的流程,指令的安排。GDB有多個命令來查看程序的狀態(tài),最常用的是list/l, print/p和x和disassemble。 list linenum/function列出第linenum行或者function所在行附近的10行,list *address列出地址address附近的10行。 list列出上一次list命令列出的代碼后面的10行 list -,列出上一次list命令列出的代碼前的10行 list列出的默認(rèn)行數(shù)可由set listsize size來設(shè)置 p /fmt VARIABLE根據(jù)fmt指定的格式打印變量VARIABLE的值,常用的fmt有d(decimal), u(unsigned), x(hex), o(octal), c(character), f(float), s(string) x /nfs ADDRESS是我最喜歡的命令,它顯示ADDRESS地址開始的內(nèi)容,形式由nfs指定。其中n為次數(shù)(個數(shù)),f是格式,除p命令可以使用的格式外,還可以使用i(instruction)打印指令,s為所打印內(nèi)容的大小,可以是b(byte), h(half word), w(word, 4bytes), g(8bytes). nfs均可省略,若省略則使用最近一次使用x命令時的值,最初nfs是1dw。 disassemble /[r][m] [ADDRESS]將ADDRESS所在函數(shù)進(jìn)行反匯編,ADDRESS也可以是一個由行號組成的區(qū)間。不指定任何選項時disas只簡單的列出反匯編指令,指定m(mixed)選項時會對應(yīng)地列出指令和源代碼,指定r(raw)時會打印出指令對應(yīng)的十六進(jìn)制編碼。 退出 Ctrl-C會終止當(dāng)前調(diào)試的程序(而不是調(diào)試器)。q(quit)退出GDB,若退出時被調(diào)試程序尚未結(jié)束,GDB會提示,請求確認(rèn)。 其它 help 使用GDB的過程中,如果對某一個命令的用法不清楚,可以隨時使用help/h尋求幫助。 info info/i命令也是一個十分常用的GDB命令,可以查看許多信息。 i program查看被調(diào)試程序的運行狀態(tài),如進(jìn)程號、ip(指令指針)、是否運行、停止原因等。 i b [bpnum]查看斷點 i f [frame-num]查看當(dāng)前(或指定)棧幀,i f all還會列出當(dāng)前棧幀的局部變量 i line LINENUM查看代碼行LINENUM,打印其指令地址,此命令后執(zhí)行x/i可查看該地址處的指令,然后回車即可繼續(xù)向后查看接下來的指令 i reg查看寄存器狀態(tài),i all-reg查看包含浮點堆棧寄存器在內(nèi)的所有寄存器情況 i args查看當(dāng)前棧幀的參數(shù) i s追蹤棧幀信息,相當(dāng)于backtrace/bt命令 i threads查看當(dāng)前進(jìn)程的線程信息 回到過去 跟蹤調(diào)試程序的過程中,偶爾會錯過一些關(guān)鍵點,不得不重新啟動程序。如果錯過的這些關(guān)鍵點不容易再現(xiàn),就更令人懊惱了。GDB提供一種機(jī)制可以讓你將程序向后調(diào)整,重新來過。這種機(jī)制叫做checkpoint,你可以在程序運行的關(guān)鍵點處執(zhí)行checkpoint命令,你將得到一個數(shù)字(check-num)來標(biāo)識這個checkpoint。在以后的某個時刻使用restart check-num將程序回滾到設(shè)置該checkpoint的時刻,而此時此刻的“內(nèi)存狀態(tài)”恰如彼時彼刻,你可以重新調(diào)試這段程序(比如設(shè)置不同的變量值來測試不同的情況)。但覆水難收的道理你是懂得,回滾的這段程序之間產(chǎn)生的內(nèi)存之外的效應(yīng)是無法恢復(fù)的,比如寫出到文件的數(shù)據(jù)收不回來了(但文件的指針偏移是可以恢復(fù)的,因為它的值保存在內(nèi)存),通過網(wǎng)絡(luò)發(fā)出的數(shù)據(jù)就更要不回來了。 使用i checkpoints可以查看checkpoint信息,比如check-num及其所處代碼行。 據(jù)我揣測,GDB的這種“回到過去”的伎倆并不是逐步撤銷之前運行的指令,而是在checkpoint命令執(zhí)行的時候,把被調(diào)試程序fork/clone了。 多線程 調(diào)試多線程程序與普通程序沒有太大的區(qū)別。 i threads查看當(dāng)前進(jìn)程包含的線程的信息,包括線程號及其GDB內(nèi)部標(biāo)識號,當(dāng)前處于前端的線程。 thread thread-num切換到線程thread-num set scheduler-locking [on|off|step],這是一個比較重要的選項,它控制這當(dāng)前調(diào)試線程與其它線程的執(zhí)行關(guān)系。設(shè)置為on時,其它線程不會搶占當(dāng)前線程。設(shè)置為off(默認(rèn))時,當(dāng)前線程執(zhí)行時隨時可能被其它線程搶占。設(shè)置為step時,在執(zhí)行step命令時不會被搶占,但使用next跳過函數(shù)調(diào)用時可以/可能會被搶占,即調(diào)度器會被執(zhí)行。 技巧 回車。在GDB命令行下,簡單的回車會執(zhí)行上一個命令。而且GDB命令中可以使用回車重復(fù)執(zhí)行的都是有記憶的,如果使用回車,該命令就會根據(jù)上一次的執(zhí)行適當(dāng)?shù)卣{(diào)整參數(shù)重復(fù)執(zhí)行。比如使用l func列出函數(shù)func附近的10行代碼,接著回車就會打印接下來的10行。使用x/16xw ADDRESS以16進(jìn)制形式查看ADDRESS處的16個字,接著回車就會以同樣的格式打印接下來的16個字。如果你已經(jīng)set $i=0了,那么通過p a[$i++]然后一直回車就可以依次打印數(shù)組a的元素了。x/i $eip打印下一條指令,接著回車就會打印下下一條指令,以此類推。但,有的指令是無法使用回車來重復(fù)執(zhí)行的,比如run/start,總之,通常,你覺得不能/不適合重復(fù)執(zhí)行的命令就無法重復(fù)執(zhí)行。 命令簡寫,有的命令名稱較長甚至很長,但GDB允許用戶只使用足以區(qū)別其它命令的前幾個字符來執(zhí)行命令,當(dāng)然你也可以使用TAB自動補全。另外一些極為常用的命令有專門的簡寫形式,通常只有一個字母,例如break/b, list/l, info/i, continue/c, next/n, step/s, nexti/ni, stepi/si, frame/f, print/p等等等等。 在GDB中可以自定義變量(僅供GDB內(nèi)部使用),很多時候可以方便查看一些表達(dá)式的值。有的時候使用變量似乎是必須的,比如x *($esp)是不合法的,因為GDB不允許對寄存器變量進(jìn)行解引用(dereference, but why?)。這時,設(shè)置變量set $p=*($esp),然后x $p就可以了。 這只是一個小小的不完全的總結(jié),如果你從未使用過GDB,推薦使用RMS的《Debugging with GDB》學(xué)習(xí),還有一本小書《GDB Pocket Reference》,不妨做你的調(diào)試菜譜,如果你喜歡GDB的話。 |
|