原文鏈接:http://www.aosabook.org/en/gdb.html
作者:Stan Shebs
GDB, 即GNU調(diào)試器(GNU Debugger)。它誕生自開源軟件基金會(Free Software Foundation)成立之初的第一批程序,并一直是免費和開源軟件系統(tǒng)中的主要成員。最初GDB只是Unix系統(tǒng)上一個簡單的源碼層次的調(diào)試器,代碼量不過數(shù)千行C代碼,后來逐步發(fā)展壯大,拓展到包括嵌入式系統(tǒng)在內(nèi)多個平臺,代碼量也達到了上百萬行。
GDB在發(fā)展,不斷地滿足著新的用戶需求并增加新的功能。這一章將我們將介紹GDB的整體內(nèi)部結(jié)構(gòu),探討一下GDB是如何做到這一點的。
GDB的設計目標是一個針對使用命令式(imperative)語言(例如C,C++,Ada,Fortran等)編寫的程序的符號調(diào)試器。使用GDB原始命令行界面的一個示例如下:
% gdb myprog
[...]
(gdb) break buggy_function
Breakpoint 1 at 0x12345678: file myprog.c, line 232.
(gdb) run 45 92
Starting program: myprog
Breakpoint 1, buggy_function (arg1=45, arg2=92) at myprog.c:232
232 result = positive_variable * arg1 + arg2;
(gdb) print positive_variable
$$1 = -34
(gdb)
GDB能顯示程序中的錯誤,開發(fā)者據(jù)此判斷錯誤的類型并找到解決的方案。
設計GDB最需要考慮的是調(diào)試工具的交互性,因為用戶在調(diào)試時提交的請求是不可預測的。此外,GDB還需要深入到系統(tǒng)最底層,因為編譯器會充分利用硬件的各種選項來優(yōu)化程序的性能。
GDB還要求能夠調(diào)試不同編譯器編譯的程序(不僅僅是GNU C編譯器),能夠調(diào)試過時編譯器編譯的程序,能夠調(diào)試符號信息丟失、過時或錯誤的程序。所以,另外一個設計要求是,即使程序中的數(shù)據(jù)丟失、損壞或干脆無法理解,GDB也能夠繼續(xù)工作并發(fā)揮作用。
接下來的幾章假定讀者熟悉GDB基本的命令行使用方法。如果你還是新手,建議先用一用GDB并細讀一下手冊[SPS+00]。
GDB程序歷史悠久,早在1985年就已經(jīng)存在。它的作者是Richard Stallman,這個人還編寫了GCC,GNU Emacs和其它一些早期的GNU軟件。(由于當時并沒有軟件倉庫,GDB開發(fā)過程的細節(jié)已不為人所知。)
GDB的最早的穩(wěn)定版本在1988年發(fā)布,但在今天的GDB源碼中已經(jīng)找不到多少相似的地方了,GDB被完全重寫過至少一次。 令人驚訝的是,早期的GDB并沒有太大的野心,后來的平臺移植和功能擴展并沒有包括在GDB最初的計劃之中。
set print elements 80"使用了3個表格,第一個是包含了所有命令的表格,第二個是包含了set
選項的表格,第三個是print
選項的表格,其中elements
選項用于控制打印一個集合體(如字符串或數(shù)組)中輸出對象的個數(shù)。最后瀑布型表格將控制權(quán)交給一個實際的命令處理函數(shù),命令的參數(shù)將傳遞給這個函數(shù)來解析。一些命令, 比如run
, 處理參數(shù)的方式和傳統(tǒng)C語言的argc/argv
標準類似, 而其它一些命令, 比如print
, 則假定參數(shù)是一個程序表達式, 并將其完整傳遞給源碼解析器。
機器界面
一種GUI調(diào)試器方案是將GDB作為圖形用戶界面程序的后端,將鼠標點擊翻譯成GDB命令,然后將打印的結(jié)果顯示在窗口中。這種方案已經(jīng)在一些軟件中實現(xiàn),比如KDbg和DDD(Data Display Debugger)。但這個方法仍然不理想,因為有時候顯示結(jié)果時為了可讀性會省略掉一些細節(jié),前端提供上下文的能力也會影響到結(jié)果的顯示。
為解決這個問題,GDB提供了一個被稱為機器界面(Machine Interface,MI)的接口。本質(zhì)上MI仍然是一個命令行界面,但是命令和結(jié)果都增加了額外的語法,使得其意義更為顯然:每個參數(shù)都使用了引號,復雜輸出則使用定界符來分組,使用參數(shù)名來分塊。此外, MI的命令還可以加上順序標識符作為前綴, 并在結(jié)果中返回,保證了結(jié)果和命令的匹配。
為了比較兩種界面, 分別給出它們對于同一命令的使用情況。下面是正常的step命令及GDB的響應
(gdb) step
buggy_function (arg1=45, arg2=92) at ex.c:232
232 result = positive_variable * arg1 + arg2;
With the MI, the input and output are more verbose, but easier for other software to parse accurately:
下面是MI的輸入和輸出,雖然顯得有些冗余,但更加精確,便于第三方軟件進行解析。
4321-exec-step
4321^done,reason="end-stepping-range",
frame={addr="0x00000000004004be",
func="buggy_function",
args=[{name="arg1",value="45"},
{name="arg2",value="92"}],
file="ex.c",
fullname="/home/sshebs/ex.c",
line="232"}
Eclipse[ecl12]開發(fā)環(huán)境是最著名的使用MI的調(diào)試環(huán)境。
其它用戶界面
其它GDB前端軟件包括基于tcl/tk的GDBtk或Insight,基于文字界面的TUI(最初由Hewlett-Packard開發(fā))。GDBtk是一個傳統(tǒng)的多面板圖形用戶界面,使用tk軟件庫開發(fā),而TUI是一個在終端中使用的分屏文字界面。
維護者
作為一個GNU程序,GDB的開發(fā)遵循"大教堂(cathedral)"開發(fā)模型。GDB最初由Stallman編寫,隨后維護者幾易其人,每個人都是身兼設計師,補丁審查員,發(fā)布管理員數(shù)職,他們有權(quán)訪問僅向少數(shù)Cygnus雇員開放的源碼倉庫。
1999年,GDB被遷移到一個公共源碼倉庫,維護團隊也擴展到了幾十人,并且還有一些擁有簽入(commit)權(quán)限的個人從旁協(xié)助。這個模式顯著加速了GDB的開發(fā),從原來的每周10個簽入增加到了100個以上。
測試,測試
由于GDB高度依賴于特定平臺,幾乎涵蓋全系列的計算設備,而且包含了數(shù)以百計的命令,選項以及使用風格,即使是一個經(jīng)驗豐富的GDB黑客也難以完全預料一個修改所產(chǎn)生的后果。
于是,測試套件變得舉足輕重。GDB的測試套件包含了眾多測試程序以及expect
腳本,使用一個基于tcl被稱為DejaGNU的測試框架。其基本模式是, 每個腳本驅(qū)動GDB去調(diào)試一個測試程序, 然后向其發(fā)送命令, 并使用模式匹配來判斷結(jié)果正確與否。
這個測試套件還能進行交叉調(diào)試,既支持真實硬件也支持模擬器,它還能對于特定平臺或配置進行測試。
到2011年底,GDB測試套件包含了大約18000個測試用例,包括了基本功能測試,語言特性測試,體系特性測試,和MI測試。所有這些測試都是通用的,適用于所有配置。GDB需要志愿者來測試打補丁后的源碼,新的功能也需要新的測試。但是,因為沒有人能在所有平臺上測試同一修改,要實現(xiàn)測試的完全通過是不現(xiàn)實的。對于本地調(diào)試來說, 主干GDB測試時失敗10-20次左右是可以授受的, 嵌入式系統(tǒng)則更容易出錯。
開放是王道
GDB是"大教堂"開發(fā)模型的典范,在該模式下,維護者嚴密控制源碼,而外部用戶則跟蹤其進度。補丁提交數(shù)目較少,封閉的開發(fā)過程實際上并不鼓勵補丁。自從采用開放模式之后,補丁數(shù)量顯著增多,而軟件質(zhì)量則一如既往,甚至更好。
制訂計劃, 但計劃趕不上變化
開源軟件開發(fā)過程實際上會比較混亂,因為開發(fā)者之間是松散的,流動性很大。
但是,制訂開發(fā)計劃并發(fā)布仍然很有意義。這有助于指導開發(fā)者完成相關任務,而且能夠吸引潛在的贊助者,另外志愿者在嘗試做出貢獻時也能有一定的依據(jù)。
但是不要嘗試設置截止時間,即使是每個人都熱情地朝著一個方向努力,也不要指望大家都能全身心地投入并按時完成任務。
鑒于此,不要堅持一個已經(jīng)過時的計劃。長期以來,GDB都有重構(gòu)為軟件庫libgdb
的計劃,這樣, 別的程序就可以通過使用libgdb
來實現(xiàn)一個擁有GUI的調(diào)試器。開發(fā)人員甚至嘗試過將構(gòu)建libgdb.a
作為整個構(gòu)建過程的一個中間步驟。雖然這個想法一直存在,但隨著Eclipse和MI的成功,libgdb
被擱置了起來,到2012年1月這個想法最終壽終正寢。
無比聰明該多好
看到曾經(jīng)提交的修改,我們也許會想:為什么一開始不這么做呢? 唉,只因為我們不夠聰明。
我們本可以預料到GDB會如此流行,并且會移植到數(shù)以百計的平臺上,還支持本地和交叉調(diào)試。如果事先知道這些,說不定一開始就會使用gdbarch
對象,而不會數(shù)年來都在用陳舊的宏和全局變量,目標向量也早該出現(xiàn)。
我們本可以預料到GDB將會被用到GUI中, 畢竟1986年Mac和X窗口系統(tǒng)已經(jīng)出現(xiàn)了2年。與其設計一個傳統(tǒng)的命令行界面,我們更應該讓其支持異步事件處理。
然而,真正的教訓不在于GDB開發(fā)者們有多蠢,而是我們不可能如此聰明地未卜先知。1986年, 窗口-鼠標風格的界面的未來還并不清晰, 我們預料不到它會像今天這樣流行,如果第一個版本的GDB就設計為在GUI下使用,我們就可以稱得上天才了,但這種好運不是人人都能有的。相反,在一個有限的范圍內(nèi)讓GDB有所作為,我們已經(jīng)為今后的擴展和重構(gòu)打下了用戶基礎。
學會接受缺陷
盡力完成過渡,但是時間總是太快,你只能接受缺陷。
在2003年的GCC峰會上, Zack Weinberg哀嘆GCC的"不完整過渡",新的底層結(jié)構(gòu)已經(jīng)引入,但是舊的卻尾大不掉。GDB有著同樣的問題,但是我們應該看到積極的一面,因為畢竟一些過渡已經(jīng)完成,比如目標向量,gdbarch
等等。雖然過渡需要多年來完成,調(diào)試卻要一直繼續(xù)。
謹防著迷于代碼
當你遇到一個對你非常重要的項目,你會花費大量時間在單個代碼上, 你會很容易沉迷其中,甚至為了迎合代碼而改變自己的想法。但是,很有可能你已經(jīng)誤入歧途,退一步說不定海闊天空。
這樣的事情要杜絕發(fā)生。
所有代碼都源自于一系列清醒的判斷:有些來自靈感,有些則不是。1991年節(jié)省空間的小伎倆對于2011年的數(shù)個G的內(nèi)存來說是毫無意義的。
GDB曾經(jīng)支持Gould超級計算機。當他們在2000年關閉最后一臺機器時,保留對這種機器的支持已是毫無意義。那些代碼只是GDB過往歷史中的一些小小篇章,然而現(xiàn)在大部分的發(fā)行版中仍然有些"懷舊"。
事實上,很多激進的修改已經(jīng)擺上日程或已經(jīng)開展,包括對Python腳本的支持,對并行多核平臺的支持,重編碼為C++等。這些修改可能要花費數(shù)年,但其動機卻來自于今天(等到它們完成時說不定已經(jīng)過時)。
更多建議: