<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                > 原文鏈接:[http://www.aosabook.org/en/gdb.html](http://www.aosabook.org/en/gdb.html) > 作者:Stan Shebs GDB, 即GNU調試器(GNU Debugger)。它誕生自開源軟件基金會(Free Software Foundation)成立之初的第一批程序,并一直是免費和開源軟件系統中的主要成員。最初GDB只是Unix系統上一個簡單的源碼層次的調試器,代碼量不過數千行C代碼,后來逐步發展壯大,拓展到包括嵌入式系統在內多個平臺,代碼量也達到了上百萬行。 GDB在發展,不斷地滿足著新的用戶需求并增加新的功能。這一章將我們將介紹GDB的整體內部結構,探討一下GDB是如何做到這一點的。 ## 4.1 目標 GDB的設計目標是一個針對使用命令式(imperative)語言(例如C,C++,Ada,Fortran等)編寫的程序的符號調試器。使用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能顯示程序中的錯誤,開發者據此判斷錯誤的類型并找到解決的方案。 設計GDB最需要考慮的是調試工具的交互性,因為用戶在調試時提交的請求是不可預測的。此外,GDB還需要深入到系統最底層,因為編譯器會充分利用硬件的各種選項來優化程序的性能。 GDB還要求能夠調試不同編譯器編譯的程序(不僅僅是GNU C編譯器),能夠調試過時編譯器編譯的程序,能夠調試符號信息丟失、過時或錯誤的程序。所以,另外一個設計要求是,即使程序中的數據丟失、損壞或干脆無法理解,GDB也能夠繼續工作并發揮作用。 接下來的幾章假定讀者熟悉GDB基本的命令行使用方法。如果你還是新手,建議先用一用GDB并細讀一下手冊[[SPS+00]](http://www.aosabook.org/en/bib2.html#gdb-manual)。 ## 4.2 GDB的起源 GDB程序歷史悠久,早在1985年就已經存在。它的作者是Richard Stallman,這個人還編寫了GCC,GNU Emacs和其它一些早期的GNU軟件。(由于當時并沒有軟件倉庫,GDB開發過程的細節已不為人所知。) GDB的最早的穩定版本在1988年發布,但在今天的GDB源碼中已經找不到多少相似的地方了,GDB被完全重寫過至少一次。 令人驚訝的是,早期的GDB并沒有太大的野心,后來的平臺移植和功能擴展并沒有包括在GDB最初的計劃之中。 ## 4.3 GDB結構框圖 ![enter image description here](http://box.kancloud.cn/2015-08-20_55d58b498fe58.jpg)圖4.1 GDB總體結構圖 總體來講,GDB內部結構可分為兩大塊: 1. 符號端,涉及到程序的符號信息。符號信息包含函數名,變量名,變量類型,行號,機器寄存器使用情況,等等。符號端將程序可執行文件中的符號信息提取出來,解析表達式,找到指定行號的內存地址,列出源代碼,并大體上獲取程序中的文本信息。 2. 目標端,涉及到目標系統的操控。目標端包含了基本的調試工具,包括啟動和終止程序,讀取或修改內存和寄存器,捕捉信號,等等。這些工具的實現在不同的系統上可能會相差很大。大部分Unix類操作系統上都提供了一個系統函數`ptrace`,`ptrace`可以讓一個進程讀寫另一個進程的狀態。因此,GDB的目標端的主要工作就是調用`ptrace`和解析結果。對于嵌入式系統的交叉調試,過程有所不同,目標端通過數據線發送消息包,然后等待應答。 這兩大模塊相互較為獨立,用戶可以查看程序的代碼,顯示變量類型,但不需要實際地運行程序。反過來,不用符號信息完全使用機器碼調試也是可能的。 將符號端和目標端連接起來的中間層是命令解釋器和主程序運行控制循環。 ## 4.4 操作實例 為了解GDB各部分是如何協同工作的,不妨考慮一下前面示例中提到的`print`命令。命令解釋器會搜索`print`命令函數,該函數將表達式轉化為一個簡單的樹結構,通過遍歷樹結構來運算這個表達式。運算器會查詢符號表,它發現`positive_variable`是一個全局整型變量,其存儲地址是`0x601028`。隨后,它會調用一個目標端中的函數來獲取該地址中的4個字節內容,將結果傳遞給格式化函數,并顯示為一個數字。 為了同步顯示源碼和對應的編譯后代碼,GDB同時讀取源碼文件和目標文件,然后使用編譯器產生的行號信息將兩者聯系起來。在本例中,232行的地址是`0x4004be`,233行是`0x4004ce`,等等。 ~~~ [...] 232 result = positive_variable * arg1 + arg2; 0x4004be <+10>: mov 0x200b64(%rip),%eax # 0x601028 <positive_variable> 0x4004c4 <+16>: imul -0x14(%rbp),%eax 0x4004c8 <+20>: add -0x18(%rbp),%eax 0x4004cb <+23>: mov %eax,-0x4(%rbp) 233 return result; 0x4004ce <+26>: mov -0x4(%rbp),%eax [...] ~~~ 單步執行命令`step`背后則稍為復雜。當用戶使用`step`請求執行到下一行時,目標端只執行程序的一個指令后就再次暫停(`ptrace`支持類似的操作)。當獲悉程序已經停止了,GDB讀取程序計數器(PC, program counter)寄存器(另外一個目標端操作),并與符號端中記錄的當前行的地址范圍進行比較。如果程序計數器在這個范圍之外,GDB允許程序停止,并獲取新的代碼行反饋給用戶。如果程序計數器仍然在地址范圍之內,GDB會重復執行指令和檢查計數器的操作,直到程序計數器到達新的代碼行。這個簡單的算法保證了調試的邏輯正確性,無論當前行是跳轉還是子函數調用都不會出錯,而且也不要求GDB去理解機器指令集的所有細節。但不足之處是,一個單步執行命令(`step`)會產生與目標之間的多個交互過程。對于嵌入式調試,這個算法會導致單步執行變得很慢。 ## 4.5 可移植性 由于需要大量地訪問芯片上的物理寄存器,GDB最初的設計就考慮了面向不同系統的可移植性問題。 但是GDB的可移植性策略卻是與時俱進的。 最初, GDB和當時的其它GNU程序一樣, 使用C語言的最小子集編碼, 結合預處理宏和`Makefile`腳本來適應特定的硬件結構和操作系統。 GNU項目的目標是完備的"GNU操作系統"(實際上,很多年之后Linux內核才問世), 因此其系統引導程序(bootstrapping)必須考慮多種已有平臺。在面向多平臺移植的過程中,?`configure`腳本是第一個關鍵步驟。`configure`要做的事情很多,比如用符號鏈接將特定平臺的文件統一為通用的頭文件,比如從多個配置文件出發生成結果文件(在構建軟件時,主要目標是生成`Makefile`文件)。 和`cat`,`diff`之類的程序一樣,GCC和GDB也有額外的平臺移植需求。隨時間變化,GDB的可移植性問題分成了三類,每一類都有獨立的`Makefile`腳本和頭文件。 * "主機(host)"定義。指GDB運行時所在的機器的信息,包含了主機整型大小等信息。原來這些頭文件是手工編寫的,漸漸地人們發現`configure`可以自動完成這個工作。`configure`腳本會調用一些測試程序,這些測試程序與GDB使用的是同一個編譯器。這些都是`autoconf`[[aut12]](http://www.aosabook.org/en/bib2.html#autoconf)的工作, 幾乎所有的GNU工具和許多Unix程序都在使用`autoconf`來生成`configure`腳本。 * "目標(target)"定義。待調試程序所在的特定機器上的信息。如果目標機器和主機是同一臺機器,這樣的調試稱為本地(native)調試,否則稱為交叉(cross)調試(主機和目標機器通過數據線連接)。目標定義又分為兩個主要類別: * "結構(architecture)"定義:定義了如何分解機器碼,如何訪問調用堆棧,在斷點處插入何種trap指令。最初這些工作由宏來完成,后來使用C語言編寫的所謂"`gdbarch`"對象(后面會進一步介紹)。 * "本地(native)"定義:定義了`ptrace`的參數規范(不同Unix之間變化很大),如何搜索已加載的動態鏈接庫,等等。本地定義只適用于本地調試的情況,它是從80年代的那些宏中遺留下來的,其它的都已經被`autoconf`取代了。 ## 4.6 數據結構 在深入了解GDB各部分之前, 讓我們先看看GDB主要的數據結構。作為一個C程序,GDB必然使用`struct`而非C++對象(object)。但是,struct也是可以視為對象(object)的,而GDB開發者也喜歡稱其為對象, 那么我們也就入鄉隨俗了。 **斷點(breakpoint)** 斷點是用戶能夠直接訪問得到的主要對象。用戶使用`break`命令創建一個斷點,其參數為斷點的位置,位置可以是函數名,源碼行號或機器地址。GDB為每個斷點對象指定一個正整數作為其標識符,用戶將通過這個正整數來操縱斷點。在GDB中,斷點是一個內容豐富的C語言結構體。位置信息會被翻譯為機器地址,但仍然保留其原始形式,因為機器地址可能會發生變化(比如在不退出會話的情況下重新編譯運行)。 還有其它一些斷點類對象也使用斷點的數據結構,包括觀察點(watchpoint),捕捉點(catchpoint),跟蹤點(tracepoint)。數據結構的共享保證了一些公共操作(創建,操縱和刪除)對這些對象的通用性。 "位置"一詞還可以指斷點定義處的內存地址。對于inline函數和C++模板,用戶定義的一個斷點可能會對應到多個地址。比如當一個斷點定義到inline函數上時,代碼中所有使用這個函數的位置都會有斷點存在。 **符號和符號表** 符號表是GDB中的核心數據結構, 它的數據量很大, 有時甚至會達到數G字節。 從某種意義上說, 這也是無法避免的。 每個局部變量,每種類型,每個枚舉值,都是獨立的符號。一個大型C++程序本身就包含了數百萬個符號,而 它所引用的頭文件同樣會有數百萬符號。 GDB使用了很多技巧來減少符號表占用的空間,比如使用不完全符號表(partial symbol table,后面會有介紹),在結構體中使用比特位,等等。 符號表的作用是建立字符串到地址和類型信息之間的映射,除此之外, GDB還建立了一些支持雙向查詢的行號表: 從源碼行查詢地址,從地址查詢源碼行。(早前介紹的單步執行算法就嚴重依賴于地址到源碼的映射。) **棧幀** GDB支持的過程式語言運行時都有一個相似過程, 即函數調用會引起程序計數器,函數參數,以及局部參數的入棧。這些入棧數據的組合體稱為"棧幀(stack frame)", 或簡稱"幀"。在程序執行的任何時刻,棧中都包含了多個串連在一起的幀。棧幀的細節取決于芯片體系結構,還和操作系統,編譯器,以及優化選項有關系。 將GDB遷移到新的芯片時需要編寫大量的代碼來分析棧,因為用戶程序(特別是帶Bug的程序)可能在任何地方暫停運行,屆時幀可能并不完整,部分甚至會被程序覆蓋。更糟糕的是,為每個函數調用創建一個棧幀會影響程序效率,因而編譯器在優化時會盡可能地簡化棧幀,甚至完全消除(tail調用即是如此)。 對于特定芯片的棧的分析結果保存在一系列的幀對象中。最初,GDB使用一個固定幀指針寄存器來跟蹤幀。但這個方法對inline函數調用以及其它編譯器優化不起作用。從2002年開始,GDB開發人員引入了顯式幀對象(explicit frame object)來記錄每一幀的信息,這些顯式幀對象鏈接在一起,并映射到程序的棧幀上。 **表達式** 對于棧幀,GDB假定它所支持的不同語言的表達式具有一定的共性,并將表達式表達為一個由結點對象構成的樹結構。實際上, 結點的類型集合是所有不同語言中所有可能的表達式類型的一個聯合。和編譯器不一樣,GDB允許Fortran變量和C變量之間的減法,雖然兩種變量類型相差甚遠并且結果會人大吃一驚。 **值(value)** 表達式計算得到的結果可能要比一個整數或內存地址更為復雜,GDB將這些結果保存在一個經過編號的歷史列表中,以便在后面的表達式中能夠訪問得到。為實現這個功能,GDB有一個關于值(value)的數據結構。value結構體(`struct`)包含了大量的成員來記錄其屬性,包括標記這個值是左值還是右值(左值可以被賦值),以及這個值是否由懶構造(lazy construction)得到。 ## 4.7\. 符號端 GDB的符號端的功能主要是讀取可執行文件,提取所有的符號信息,然后構造一個符號表。 讀取可執行文件的首先要調用BFD軟件庫。 BFD是一個通用的處理二進制和對象(object)文件的軟件庫,支持從任意主機上讀取Unix的`a.out`格式,COFF格式(用于System V Unix系統和微軟Windows操作系統),ELF格式(用于現代Unix, GNU/Linux和大部分嵌入式系統),以及其它文件格式。BFD內部采用一個復雜的C語言宏,這個宏展開成代碼之后能夠深入到對象文件格式的復雜細節中去,而這些對象文件可能來自于幾十個不同平臺。BFD從1990年被引入到GNU匯編器和鏈接器,它對多種對象文件輸出的支持成為跨平臺開發的關鍵因素。(自然,將GDB移植到一個新的平臺的首要條件是將BFD移植過去。) GDB只用BFD來讀取文件,將可執行文件中的數據塊讀到GDB的內存空間中去。GDB本身擁有兩個層次的讀入函數。第一個層次針對基本符號,或最簡符號(minimal symbols),只包含了鏈接器需要的名稱。這些基本符號只是一些帶地址的字符串,在這一層次下,我們假定文本節(text section)中的地址都是函數,而數據節(data section)中的都是數據,依此類推。 第二個層次針對詳細的符號信息,通常這種符號信息擁有與可執行文件不同的格式。例如,DWARF調試格式中的信息存儲在ELF文件中單獨命名的節(section)內。而Berkeley Unix系統中用到的舊的`stabs`調試格式將這些詳細符號信息加上特別的標記后存儲在通用符號表中。 閱讀符號信息的代碼非常無聊,因為不同的符號格式都需要將源碼中的每個類型信息進行編碼,而每一種符號格式的編碼方式又都不一樣。GDB的文件閱讀器的工作就是掃描符號格式,將其轉化為原來的形式。 **不完全符號表** 對于較大規模的程序(如Emacs或Firefox),建立符號表是比較費時的,有可能會達到幾分鐘的時間。實踐表明文件加載時間倒不是主要的,主要是瓶頸在于內存中GDB符號的構造。一個程序中往往存在著上百萬個小對象相互聯系著,處理起來時間開銷非常大。 大部分符號信息在一個GDB會話中從來不會用到,因為它們來自于函數的局部作用域。所以,GDB第一次導入程序的符號時,它先掃描一下符號信息,只把全局可見的符號存進符號表。當用戶在某個函數內暫停運行時,這個函數的完整的符號信息才會動態加載進來。 在GDB中,不完全符號表使得大程序也能在數秒內啟動。(動態鏈接庫的符號也會動態加載,但過程完全不同。當動態鏈接庫被加載時, 平臺會通知GDB建立一個符號表,符號表中存儲了動態鏈接地址對應的那些函數。這個過程取決于特定平臺的消息機制,不同的平臺會有所不同。) **語言支持** 對源碼語言的支持主要包括表達式解析和值的打印。表達式解析由語言自身負責,但一般來說表達式解析器是一個基于Yacc語法的詞法分析器。為了讓GDB在用戶交互操作時具有更大的靈活性,解析器不需要對語法有嚴格的要求。比如,如果用戶能合理地猜出來表達式的類型,那他就不需要顯式地做類型轉換。 GDB表達式解析器不需要考慮變量聲明和類型聲明,比完整的語言解析器要簡單得多。類似的,值的打印,也只有考慮一部分類型的值,甚至還可以由特定語言的函數來實現。 ## 4.8\. 目標端 目標端的功能是操縱程序的執行和處理底層原始數據。從某種意義上講,目標端是一個完全低層次的調試器。如果只是逐個指令調試并打印原始內存,用戶根本就不需符號信息。(如果程序剛好在一個被剝離符號的軟件庫中暫停,你也只能使用這種模式。) **目標向量和目標向量棧** 最初,GDB的目標端由一些特定平臺上的文件組成,用于處理ptrace的調用,啟動可執行文件等等。但這對于長時間運行的GDB會話來說是不夠靈活的,因為用戶可能會中途變化調試目標或方式,比如從本地調試切換到遠程調試,從調試core文件切換到調試運行的程序,從附加(attach)線程變為分離(detach),等等。1990年,John Gilmore重新設計了GDB的目標端,使用目標向量來流水處理特定目標的操作。目標向量主要是由一類定義了目標系統特性的對象,每個目標向量是多個函數指針(通常稱為"方法")構成的結構體,這些方法的功能包括讀寫寄存器內存,恢復程序運行,設置處理共享庫時的參數。GDB中大概有40多個目標向量,包括有名的針對Linux的目標向量,以及不那么出名的操縱Xilinx MicroBlaze的目標向量。對Core dump的支持使用了一個從corefile中獲取數據的目標向量,對應的,還有從可執行文件中獲取數據的目標向量。 通常將幾個目標向量混合使用比較有利。以在Unix上打印一個已初始化的全局變量為例,在程序開始運行之前,GDB也要能夠支持對這個變量的打印,但這個時候進程并沒有啟動,數據只能從可執行文件的`.data`節(section)獲取,所以GDB只能使用針對可執行文件的目標向量來讀取二進制文件。但是如果程序已經運行,數據就應該從進程的地址空間中獲取。這時候,GDB就會使用"目標向量棧",運行進程目標向量被推入棧頂,置于可執行文件目標向量之上,當進程退出時棧頂目標向量就會被彈出。 實際上,目標向量棧和你想像中的棧并不完全相同,目標向量之間并不是完全獨立的。如果一個GDB會話同時調試一個可執行文件和一個運行進程,幾乎總是讓進程的方法覆蓋可執行文件的方法。所以GDB提出"階層(stratum)"的概念,令所有"進程類"的目標向量位于較高的階層,而所有"文件類"的目標向量位于較低階層,目標向量棧支持目標向量的推入(push)和彈出(pop),還支持插入操作。 (雖然GDB的維護者們并不怎么喜歡目標向量棧,但是還沒有人能提出或實現更好的的方案。) **Gdbarch** 因為程序直接和CPU的指令打交道,GDB需要深入了解芯片的細節,比如,所有的寄存器的信息,不同種類數據的大小,地址空間的大小和形狀,調用約定是怎么工作的,什么指令會導致trap異常,等等。GDB中這一類工作的代碼量取決于芯片的復雜度,從1000行到10000行的C代碼都是有可能的。 最初,這個工作是由特定目標的預處理宏來完成的,但是隨著調試器變得越來越復雜,這些宏變得越來越長,以致于不得不讓部分宏變成了C函數(由其它宏來調用)。雖然這暫時減小了宏的復雜度,但是無助于解決平臺的多樣性問題(ARM或Thumb, 32位或64位, 64位MPIS或x86,等等)。更糟糕的是,多體系結構設計開始出現,對此,宏已經無能為力。1995年,我提出使用面向對象的設計來解決這個問題。從1998年開始Cygnus Solutions公司資助Andrew Cagney來開始實現這個設計。(Cygnus Solutions是一家1989年創立的提供免費軟件商業支持的公司,2000年被Red Hat收購)。在幾十個黑客數年的努力下,這個工作終于完成,其代碼量大概有80000行。 新引入的結構稱為`gdbarch`對象, 目前它包含了多達130個方法和變量來定義目標體系結構, 其實一個簡單的目標平臺也許只需要幾十個。 為了比較其差異,我們來看一下"將x86平臺下long doubles類型的大小定義為96"在新舊方式下分別是如何實現的: `gdb/i38-tdep.c`中2012行處的代碼(舊方式) ~~~ #define TARGET_LONG_DOUBLE_BIT 96 ~~~ `gdb/config/i386/tm-i386.h`中2002行(新方式) ~~~ i386_gdbarch_init( [...] ) { [...] set_gdbarch_long_double_bit (gdbarch, 96); [...] } ~~~ **運行控制** GDB的核心是運行控制循環, 前面描述單步執行一行代碼時提到過這個名詞: 用一個簡單的循環,來判斷指令是否運行到了下一行源代碼。這個循環稱為`wait_for_inferior`,或簡稱為wfi。 從概念上看, wfi位于主程序命令循環內部, 并且只有在程序恢復執行時才會進入wfi循環。當用戶提交`continue`或`step`命令時,看起來似乎什么也沒發生,其實這時候的GDB忙得很。除了前面提到的單步運行循環,程序還可能會執行到trap指令并將此異常匯報給GDB。如果遇到一個由斷點引發的trap異常,GDB會判斷這個斷點的條件,如果條件為假,則移除此trap指令,繼續執行單步運行循環,然后重新插入trap指令并令程序恢復執行。類似的,如果接收到一個信號,GDB可能會選擇忽略,或根據預先指定的方式來處理。 所有這些活動都由`wait_for_inferior`來管理。最初wfi只是一個簡單的循環,等待目標停止執行然后決定接下來怎么辦,但移植到新的平臺意味著增加新的需求,這個循環漸漸地增加到了1000行代碼,而且變得難懂以至于不得不使用`goto`語句。隨著增加對更多種Unix系統的支持,沒有一個人能夠理解所有的代碼,也沒有人能夠對所有的代碼進行回歸測試。所以代碼重構顯得非常有必要,保留已有平臺的行為然后使用goto語句跳過循環中的部分代碼只是一個權宜之計。 這個龐大的循環在異步處理時也是有問題的。 因為,在調試多線程程序時, 用戶需要在程序其它部分保持運行的同時調試某一個線程。 GDB從wfi轉變為事件驅動模型花費了數年的時間。1999年,我將`wait_for_inferior`拆分開來,引入了一個執行控制狀態結構體,取代本地和全局的大量雜亂的變量,并將復雜的跳轉封裝到一些小型獨立的函數中。同時Elena Zannoni和其它人引入了事件隊列,該隊列的輸入既包含用戶的操作,還包括來自底層的通知。 **遠程協議** 雖然GDB的目標向量體系允許在不同計算機上以多種方式來控制程序的運行, 但是我們傾向于使用單一的協議。這個協議并沒有一個獨立而準確的名稱,它使用過的名稱包括"遠程協議(remote protocol)","GDB遠程協議", "遠程串行協議(Remote serial protocal, 簡寫為RSP)","遠程C協議(用實現語言命名)",或"樁協議(stub protocol)",其實都是指目標系統對這個協議的實現。 基本的協議比較簡單,主要面向19世紀80年代的小型嵌入式系統,其內存不過幾千字節。GDB向所有的寄存器發出協議數據包`$g`,請求獲得所有寄存器的所有內容,GDB假定這些寄存器的數目,大小和順序都是已知的。 協議假定連接是可靠的,且每個發出去的數據包都能得到應答,在發包時只是加上一個檢驗和數字(`$g`發送成`$g#67`)。 遠程協議中必要的數據包類型并不多(對應于6個最重要的目標向量方法),但為了支持硬件斷點,跟蹤點(tracepoint)和共享庫, 又逐步加入了數十個可選的數據包格式。 對于目標平臺本身來說,遠程協議可以以多種形式來實現。GDB的手冊中有完整的協議文檔,只要用戶不違反GNU協議就可以實現自己的協議。事實上,許多設備制造商已經在實驗或實踐中實現了一些使用GDB遠程協議的代碼。比如,廣為人知的Cisco的IOS,就一直運行在該公司的許多網絡設備上。 目標平臺對于遠程協議的實現通常稱為"調試樁(debugging stub)",或者簡稱為"樁(stub)",意指它不會獨立完成任何工作。GDB的源碼中包含了一些樁的示例代碼,大約只有1000行左右的C代碼。對于一個沒有操作系統的電路板, 樁必須能夠自己處理硬件異常, 特別是能夠捕捉trap指令。如果硬件鏈接是串行的,它還需要有串行驅動的支持。實際的協議處理過程是比較簡單的,因為所有必須的數據包都是單個字符,可以使用一個簡單的switch語句來解碼。 另外一個實現遠程協議的方法是構建一個"sprite",作為GDB和調試硬件(包括JTAG設備,"wiggler"等)之間的接口。通常這些設備需要在與目標板相連的計算機上運行一個特殊的軟件庫,這個庫的API往往與GDB內部結構不相容。所以,與其讓GDB直接使用硬件控制庫,還不如更簡單地讓sprite作為一個獨立的程序運行,它能夠理解遠程協議并將數據包翻譯成設備軟件庫函數。 **GDBserver** GDB源碼中已經包含了一個完整和可靠的目標端遠程協議的實現: GDBserver。GDBserver是一個在目標操作系統上運行的本地程序,它響應通過遠程協議接收到的數據包,控制目標操作系統上的其它程序來提供本地調試支持。換句話說,它類似于本地調試的一個代理。 GDBserver不做本地GDB能力范圍之外的事,也就是說,如果目標系統可以運行GDBserver,那么理論上它也可以運行GDB。但是,GDBserver只有GDB軟件規模的1/10,而且不需要管理符號表,所以用于嵌入式GNU/Linux之類的系統的調試是非常方便。 ![enter image description here](http://box.kancloud.cn/2015-08-20_55d58b4a20f39.jpg)圖4.2: GDBserver GDB和GDBServer共享相同的代碼,雖然大家都知道要將平臺依賴的控制代碼封裝起來,但是實際中GDB的這個遷移工作進展緩慢,因為將本地GDB中的依賴關系分離開來是比較困難的。 ## 4.9\. GDB界面 GDB本質上是一個命令行調試器。人們始終沒有放棄嘗試將其發展為一個圖形窗口調試器,但是即使投入了大量的時間和努力,至今也沒有一個得到廣泛接受的方案。 **命令行界面** 命令行接口使用了標準的GNU軟件庫`readline`來處理GDB和用戶之間的交互。`readline`用于命令行的編輯和自動補全,因而用戶可以像使用光標一樣在命令行中移動和修改。 GDB接收`readline`返回的命令,然后在一個瀑布型的命令表結構中查詢這條命令,命令中每個后續單詞會選擇一個額外的表格。比如,"`set print elements 80`"使用了3個表格,第一個是包含了所有命令的表格,第二個是包含了`set`選項的表格,第三個是`print`選項的表格,其中`elements`選項用于控制打印一個集合體(如字符串或數組)中輸出對象的個數。最后瀑布型表格將控制權交給一個實際的命令處理函數,命令的參數將傳遞給這個函數來解析。一些命令, 比如`run`, 處理參數的方式和傳統C語言的`argc/argv`標準類似, 而其它一些命令, 比如`print`, 則假定參數是一個程序表達式, 并將其完整傳遞給源碼解析器。 **機器界面** 一種GUI調試器方案是將GDB作為圖形用戶界面程序的后端,將鼠標點擊翻譯成GDB命令,然后將打印的結果顯示在窗口中。這種方案已經在一些軟件中實現,比如KDbg和DDD(Data Display Debugger)。但這個方法仍然不理想,因為有時候顯示結果時為了可讀性會省略掉一些細節,前端提供上下文的能力也會影響到結果的顯示。 為解決這個問題,GDB提供了一個被稱為機器界面(Machine Interface,MI)的接口。本質上MI仍然是一個命令行界面,但是命令和結果都增加了額外的語法,使得其意義更為顯然:每個參數都使用了引號,復雜輸出則使用定界符來分組,使用參數名來分塊。此外, MI的命令還可以加上順序標識符作為前綴, 并在結果中返回,保證了結果和命令的匹配。 為了比較兩種界面, 分別給出它們對于同一命令的使用情況。下面是正常的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]](http://www.aosabook.org/en/bib2.html#eclipse)開發環境是最著名的使用MI的調試環境。 **其它用戶界面** 其它GDB前端軟件包括基于tcl/tk的GDBtk或Insight,基于文字界面的TUI(最初由Hewlett-Packard開發)。GDBtk是一個傳統的多面板圖形用戶界面,使用tk軟件庫開發,而TUI是一個在終端中使用的分屏文字界面。 ## 4.10\. 開發過程 **維護者** 作為一個GNU程序,GDB的開發遵循"大教堂(cathedral)"開發模型。GDB最初由Stallman編寫,隨后維護者幾易其人,每個人都是身兼設計師,補丁審查員,發布管理員數職,他們有權訪問僅向少數Cygnus雇員開放的源碼倉庫。 1999年,GDB被遷移到一個公共源碼倉庫,維護團隊也擴展到了幾十人,并且還有一些擁有簽入(commit)權限的個人從旁協助。這個模式顯著加速了GDB的開發,從原來的每周10個簽入增加到了100個以上。 **測試,測試** 由于GDB高度依賴于特定平臺,幾乎涵蓋全系列的計算設備,而且包含了數以百計的命令,選項以及使用風格,即使是一個經驗豐富的GDB黑客也難以完全預料一個修改所產生的后果。 于是,測試套件變得舉足輕重。GDB的測試套件包含了眾多測試程序以及`expect`腳本,使用一個基于tcl被稱為DejaGNU的測試框架。其基本模式是, 每個腳本驅動GDB去調試一個測試程序, 然后向其發送命令, 并使用模式匹配來判斷結果正確與否。 這個測試套件還能進行交叉調試,既支持真實硬件也支持模擬器,它還能對于特定平臺或配置進行測試。 到2011年底,GDB測試套件包含了大約18000個測試用例,包括了基本功能測試,語言特性測試,體系特性測試,和MI測試。所有這些測試都是通用的,適用于所有配置。GDB需要志愿者來測試打補丁后的源碼,新的功能也需要新的測試。但是,因為沒有人能在所有平臺上測試同一修改,要實現測試的完全通過是不現實的。對于本地調試來說, 主干GDB測試時失敗10-20次左右是可以授受的, 嵌入式系統則更容易出錯。 ## 4.11\. 經驗教訓 **開放是王道** GDB是"大教堂"開發模型的典范,在該模式下,維護者嚴密控制源碼,而外部用戶則跟蹤其進度。補丁提交數目較少,封閉的開發過程實際上并不鼓勵補丁。自從采用開放模式之后,補丁數量顯著增多,而軟件質量則一如既往,甚至更好。 **制訂計劃, 但計劃趕不上變化** 開源軟件開發過程實際上會比較混亂,因為開發者之間是松散的,流動性很大。 但是,制訂開發計劃并發布仍然很有意義。這有助于指導開發者完成相關任務,而且能夠吸引潛在的贊助者,另外志愿者在嘗試做出貢獻時也能有一定的依據。 但是不要嘗試設置截止時間,即使是每個人都熱情地朝著一個方向努力,也不要指望大家都能全身心地投入并按時完成任務。 鑒于此,不要堅持一個已經過時的計劃。長期以來,GDB都有重構為軟件庫`libgdb`的計劃,這樣, 別的程序就可以通過使用`libgdb`來實現一個擁有GUI的調試器。開發人員甚至嘗試過將構建`libgdb.a`作為整個構建過程的一個中間步驟。雖然這個想法一直存在,但隨著Eclipse和MI的成功,`libgdb`被擱置了起來,到2012年1月這個想法最終壽終正寢。 **無比聰明該多好** 看到曾經提交的修改,我們也許會想:為什么一開始不這么做呢? 唉,只因為我們不夠聰明。 我們本可以預料到GDB會如此流行,并且會移植到數以百計的平臺上,還支持本地和交叉調試。如果事先知道這些,說不定一開始就會使用`gdbarch`對象,而不會數年來都在用陳舊的宏和全局變量,目標向量也早該出現。 我們本可以預料到GDB將會被用到GUI中, 畢竟1986年Mac和X窗口系統已經出現了2年。與其設計一個傳統的命令行界面,我們更應該讓其支持異步事件處理。 然而,真正的教訓不在于GDB開發者們有多蠢,而是我們不可能如此聰明地未卜先知。1986年, 窗口-鼠標風格的界面的未來還并不清晰, 我們預料不到它會像今天這樣流行,如果第一個版本的GDB就設計為在GUI下使用,我們就可以稱得上天才了,但這種好運不是人人都能有的。相反,在一個有限的范圍內讓GDB有所作為,我們已經為今后的擴展和重構打下了用戶基礎。 **學會接受缺陷** 盡力完成過渡,但是時間總是太快,你只能接受缺陷。 在2003年的GCC峰會上, Zack Weinberg哀嘆GCC的"不完整過渡",新的底層結構已經引入,但是舊的卻尾大不掉。GDB有著同樣的問題,但是我們應該看到積極的一面,因為畢竟一些過渡已經完成,比如目標向量,`gdbarch`等等。雖然過渡需要多年來完成,調試卻要一直繼續。 **謹防著迷于代碼** 當你遇到一個對你非常重要的項目,你會花費大量時間在單個代碼上, 你會很容易沉迷其中,甚至為了迎合代碼而改變自己的想法。但是,很有可能你已經誤入歧途,退一步說不定海闊天空。 這樣的事情要杜絕發生。 所有代碼都源自于一系列清醒的判斷:有些來自靈感,有些則不是。1991年節省空間的小伎倆對于2011年的數個G的內存來說是毫無意義的。 GDB曾經支持Gould超級計算機。當他們在2000年關閉最后一臺機器時,保留對這種機器的支持已是毫無意義。那些代碼只是GDB過往歷史中的一些小小篇章,然而現在大部分的發行版中仍然有些"懷舊"。 事實上,很多激進的修改已經擺上日程或已經開展,包括對Python腳本的支持,對并行多核平臺的支持,重編碼為C++等。這些修改可能要花費數年,但其動機卻來自于今天(等到它們完成時說不定已經過時)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看