《電子技術(shù)應(yīng)用》
您所在的位置:首頁 > 嵌入式技術(shù) > 業(yè)界動(dòng)態(tài) > GDBstub的剖析與改進(jìn)

GDBstub的剖析與改進(jìn)

2008-05-27
作者:黃紅燕,史 烈

  摘 要: 討論了GDB遠(yuǎn)程調(diào)試" title="遠(yuǎn)程調(diào)試">遠(yuǎn)程調(diào)試技術(shù)在調(diào)試內(nèi)核、嵌入式系統(tǒng)" title="嵌入式系統(tǒng)">嵌入式系統(tǒng)中的實(shí)現(xiàn),簡要闡述GDB宿主機(jī)和GDB遠(yuǎn)程串行協(xié)議,詳細(xì)分析GDB調(diào)試代理在內(nèi)核層、應(yīng)用層的各種實(shí)現(xiàn)方法,并提出了一種在不修改操作系統(tǒng)內(nèi)核前提下調(diào)試應(yīng)用程序" title="應(yīng)用程序">應(yīng)用程序的方法。這種方法可移植性強(qiáng),而且消除了修改系統(tǒng)內(nèi)核可能帶來的隱患,減少了因修改內(nèi)核而帶來的工作量,在調(diào)試微內(nèi)核操作系統(tǒng)服務(wù)的應(yīng)用中非常有效。
  關(guān)鍵詞: 遠(yuǎn)程調(diào)試 stub GDBserver KGDB 嵌入式系統(tǒng)調(diào)試


  調(diào)試是開發(fā)過程中必不可少的環(huán)節(jié),然而內(nèi)核、嵌入式系統(tǒng)的調(diào)試不同于傳統(tǒng)的調(diào)試系統(tǒng)。通常嵌入式系統(tǒng)不具備使用本地調(diào)試器" title="調(diào)試器">調(diào)試器的能力,原因如下:
  (1)系統(tǒng)自身的資源有限,內(nèi)存小,輸入輸出設(shè)備不能用于調(diào)試。
  (2)傳統(tǒng)的調(diào)試系統(tǒng)需要文件系統(tǒng),而嵌入式系統(tǒng)通常無文件系統(tǒng),內(nèi)核調(diào)試時(shí)還不支持文件系統(tǒng)。
  (3)調(diào)試器的運(yùn)行本身需要操作系統(tǒng)的支持,因此無法實(shí)現(xiàn)操作系統(tǒng)內(nèi)核的調(diào)試。
  最有效的解決方法是采用遠(yuǎn)程調(diào)試技術(shù)。遠(yuǎn)程調(diào)試是指調(diào)試器運(yùn)行的環(huán)境(主機(jī))與被調(diào)試的系統(tǒng)(目標(biāo)機(jī))在物理上是分離的、通過串口或者網(wǎng)絡(luò)進(jìn)行連接的調(diào)試技術(shù)。
  GNU免費(fèi)提供的GDB擁有強(qiáng)大的遠(yuǎn)程調(diào)試功能,它能夠使開發(fā)人員以遠(yuǎn)程調(diào)試的方式單步執(zhí)行目標(biāo)平臺上的程序代碼、設(shè)置斷點(diǎn)、查看內(nèi)存,并與目標(biāo)平臺交換信息。GDB遠(yuǎn)程調(diào)試的實(shí)時(shí)、動(dòng)態(tài)、方便、免費(fèi)等優(yōu)點(diǎn),使它逐漸成為嵌入式開發(fā)首選的調(diào)試方案。
  遠(yuǎn)程調(diào)試系統(tǒng)由三部分組成:主機(jī)上的本地調(diào)試器、目標(biāo)機(jī)上的調(diào)試代理、遠(yuǎn)程調(diào)試協(xié)議,如圖1所示。對應(yīng)GDB遠(yuǎn)程調(diào)試系統(tǒng)的三部分為:GDB、GDBstub、GDB遠(yuǎn)程串行協(xié)議。下面就這三部分進(jìn)行分析。


1 RSP協(xié)議
  GDB RSP(Remote Serial Protocol)定義了GDB宿主機(jī)與被調(diào)試目標(biāo)機(jī)進(jìn)行通信時(shí)數(shù)據(jù)包的格式。信息的格式是:$數(shù)據(jù)#校驗(yàn)碼。多數(shù)信息使用ASCII碼,數(shù)據(jù)由一系列的ASCII碼組成,校驗(yàn)碼是由兩個(gè)16進(jìn)制數(shù)組成的單字節(jié)校驗(yàn)碼。接收方接收數(shù)據(jù)并校驗(yàn),若正確則回應(yīng)“+”,否則回應(yīng)“-”。通信的內(nèi)容包括讀寫數(shù)據(jù)、控制程序運(yùn)行、報(bào)告程序狀態(tài)等命令。RSP的基本命令從通信對話角度可以分為兩種:
  (1)請求
  ?:讀當(dāng)前系統(tǒng)狀態(tài)
  g:讀所有寄存器
  G〈register_data〉:寫所有寄存器
  m〈address〉,〈length〉:讀內(nèi)存
  M〈address〉,〈length〉:〈memory_data〉:寫內(nèi)存
  c:繼續(xù)執(zhí)行
  s:單步執(zhí)行
  k:終止進(jìn)程
  (2)答復(fù)
  “”:告訴GDB上次請求命令不支持。
  E:告訴GDB出錯(cuò)
  OK:上次請求正確
  W〈exit_status〉:系統(tǒng)在“exit_status”狀態(tài)下退出。
  X〈signal〉:系統(tǒng)在signal信號下終止。
  S〈signal〉:系統(tǒng)在signal信號下停止。
  O:告訴GDB控制臺輸出,這也是惟一向GDB發(fā)出的命令。
2 GDB遠(yuǎn)程調(diào)試功能
  調(diào)試內(nèi)核時(shí)通常還沒有文件系統(tǒng),而且多數(shù)嵌入式系統(tǒng)由于自身資源的限制不具備文件系統(tǒng),因此將與文件系統(tǒng)有關(guān)的源文件、目標(biāo)文件及符號表都存放在主機(jī)上,由主機(jī)上的調(diào)試器處理。同樣,調(diào)試用的輸入輸出設(shè)備也是由主機(jī)提供。主機(jī)上的調(diào)試器接收用戶輸入的調(diào)試命令并進(jìn)行預(yù)處理,對于某些命令(如breakpoint)的處理在主機(jī)GDB上實(shí)現(xiàn),不需要與目標(biāo)機(jī)通信。當(dāng)然,更多的指令需要在目標(biāo)機(jī)調(diào)試代理上實(shí)現(xiàn)。主機(jī)根據(jù)RSP對預(yù)處理之后的命令進(jìn)行封裝,發(fā)送給目標(biāo)機(jī)上的調(diào)試代理,調(diào)試代理接收命令后作相應(yīng)的處理,并返回信息給主機(jī)上的調(diào)試器。
3 目標(biāo)機(jī)上stub的實(shí)現(xiàn)
  目標(biāo)機(jī)上stub的基本功能是與主機(jī)GDB通信,實(shí)現(xiàn)讀寫內(nèi)存、寄存器,stop、continue指令。主機(jī)GDB與目標(biāo)機(jī)上stub通信的通用模型如圖2。


  目標(biāo)機(jī)與主機(jī)通過硬件連接,被調(diào)試部分插入stub,GDB與被調(diào)試部分通過RSP通信。根據(jù)stub所處層的不同實(shí)現(xiàn)不同層的調(diào)試,包括內(nèi)核層、應(yīng)用層的調(diào)試。
3.1 內(nèi)核層調(diào)試模型
  內(nèi)核層調(diào)試模型如圖3,將stub插入到內(nèi)核可以實(shí)現(xiàn)內(nèi)核的調(diào)試。Linux內(nèi)核調(diào)試機(jī)制KGDB就是使用這種模式。KGDB可以分為初始化模塊和控制模塊。


3.1.1 初始化模塊
  修改異常處理" title="異常處理">異常處理函數(shù),使得異常發(fā)生時(shí)進(jìn)入函數(shù)handle_exception( ),GDB就能夠捕獲這些異常。初始化后使用breakpoint( )函數(shù)將系統(tǒng)控制權(quán)直接交給GDB。KGDB對異常處理函數(shù)的修改基本上可以分為二種,這兩種方式均需先定義宏CHK_REMOTE_DEBUG:
  #define CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,after) \
  { if(linux_debug_hook!=(gdb_debug_hook*) NULL && \
    !user_mode(regs)) \
    { (*linux_debug_hook)(trapnr,signr,error_code,regs);\
      after;\
    }\
  }
  (1)改變程序的流程,以int3的處理函數(shù)為例。
  #define DO_VM86_ERROR(trapnr,signr,str,name) \
  asmlinkage void do_##name(struct pt_regs*regs,long \
  error_code) \
  { \
  CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,goto skip_trap)\
  do_trap(trapnr,signr,str,1,regs,error_code,NULL);\
  skip_trap:\
  return;\
  }
  展開DO_VM86_ERROR(3,SIGTRAP,″int3″,int3)
  asmlinkage void do_int3(struct pt_regs*regs,long
  error_code)
  { if(linux_debug_hook!=(gdb_debug_hook*)NULL&&!
  user_mode(regs))
  { (*linux_debug_hook)(3,SIGTRAP,errorcode,regs);
    goto skip_trap;
  }
  do_trap(3,SIGTRAP,″int3″,1,regs,error_code,NULL);
  skip_trap:
  return;
  }
  由以上代碼可見,進(jìn)入內(nèi)核調(diào)試狀態(tài)后,異常處理函數(shù)就是handle_exception( ),程序流程跳過了非調(diào)試狀態(tài)時(shí)的處理函數(shù)do_trap( )。
  (2)不改變程序的流程,以異常divide_error 的處理函數(shù)為例。
  #define DO_VM86_ERROR_INFO(trapnr,signr,str,name,sicode,siaddr) \
  asmlinkage void do_##name(struct pt_regs*regs,long error_code) \
  { ……\
  do_trap(trapnr,signr,str,1,regs,error_code,&info); \
  }
  展開DO_VM86_ERROR_INFO( 0,SIGFPE,″divide error″,
  divide_error,F(xiàn)PE_INTDIV,regs-〉eip)
  asmlinkage void do_divide_error(struct pt_regs*regs,long
  error_code)
  { if(linux_debug_hook!=(gdb_debug_hook*)NULL&&! user_mode(regs))
  { (*linux_debug_hook)(3,SIGTRAP,errorcode,regs);
  }
  do_trap(0,SIGTRAP,″divide erro″,1,regs,error_code,&info);
  }
  從以上代碼看不出調(diào)試狀態(tài)與非調(diào)試狀態(tài)的區(qū)別,可以看一下do_trap函數(shù)可能會調(diào)用的函數(shù)die( )。
  void die(const char*str,struct pt_regs*regs,long err)
  { ……
  CHK_REMOTE_DEBUG(1,SIGTRAP,err,regs,)
  ……
  do_exit(SIGSEGV);
  }
  由此可見,調(diào)試狀態(tài)下的異常處理函數(shù)還是進(jìn)入了handle_exception函數(shù)。不過與上面一種異常不同之處在于:異常處理函數(shù)在調(diào)試與非調(diào)試狀態(tài)下的程序流程是相同的,handle_exception只獲取系統(tǒng)當(dāng)時(shí)的狀態(tài),繼續(xù)運(yùn)行的結(jié)果還是do_exit。
  雖然不是所有異常函數(shù)都是按上述兩種方法定義,但本質(zhì)上都一樣。絕大多數(shù)處理函數(shù)的修改屬于第二種,因?yàn)榈谝环N異常是為調(diào)試準(zhǔn)備的。因此在目標(biāo)機(jī)具有調(diào)試輸出設(shè)備的情況下,完全可以不修改第二種異常處理函數(shù)。因?yàn)長inux內(nèi)核在非調(diào)試狀態(tài)下的異常處理函數(shù)已經(jīng)輸出必要的狀態(tài)信息、出錯(cuò)信息。
3.1.2 控制模塊
  控制模塊與主機(jī)GDB通信的具體流程如圖4。KGDB只調(diào)試內(nèi)核態(tài)程序。handle_exception函數(shù)首先判斷CPU是否處于VM86模式或用戶態(tài),若是則返回;然后接收GDB發(fā)來的信息,根據(jù)接收的信息作出相應(yīng)的操作和回復(fù)。流程圖內(nèi)的虛線框是所有GDBstub中handle_exception函數(shù)的通用流程。


3.2 應(yīng)用程序調(diào)試模型
  在嵌入式Linux開發(fā)領(lǐng)域中調(diào)試應(yīng)用程序常用調(diào)試代理工具GDBserver。其工作原理不是在被調(diào)試應(yīng)用程序內(nèi)編譯stub,而是把被調(diào)試程序作為GDBserver的子進(jìn)程,這樣GDBserver可以利用內(nèi)核提供的代碼跟蹤機(jī)制(ptrace)監(jiān)控被調(diào)試進(jìn)程的運(yùn)行,從而完成調(diào)試任務(wù)。此工作原理與GDB本地調(diào)試相似。其調(diào)試模型如圖5。GDBserver的工作流程是:GDBserver創(chuàng)建子進(jìn)程-〉綁定跟蹤ptrace(ptrace_traceme,,)-〉從主機(jī)傳來的各種調(diào)試命令通過GDBserver轉(zhuǎn)化為各種操作需求的ptrace。如果用GDBserver進(jìn)行遠(yuǎn)程調(diào)試,需要內(nèi)核操作系統(tǒng)的支持,包括子進(jìn)程、代碼跟蹤機(jī)制,這樣其他嵌入式系統(tǒng)內(nèi)核工作量會較大。而且ptrace也有其局限性,例如只能跟蹤子進(jìn)程,在調(diào)試進(jìn)程與被調(diào)試進(jìn)程之間傳送一個(gè)長字的數(shù)據(jù)。使用通用的調(diào)試模式工作量會更小。如圖6,在應(yīng)用程序中編譯stub,并在應(yīng)用程序入口處插入斷點(diǎn),程序開始將控制權(quán)交給GDB,之后的流程與內(nèi)核層調(diào)試類似。


4 不修改內(nèi)核前提下調(diào)試應(yīng)用程序
  GDB設(shè)置斷點(diǎn)的方式是使用內(nèi)存的讀寫,即將原指令用一個(gè)trap指令代替,使程序執(zhí)行到該指令時(shí)產(chǎn)生單步調(diào)試中斷,然后進(jìn)入異常處理函數(shù),針對調(diào)試器的各種操作處理函數(shù)作出相應(yīng)的操作。不同的系統(tǒng)提供不同的調(diào)試異常指令,如int3、trap2等。為了使用硬件平臺提供的斷點(diǎn)指令實(shí)現(xiàn)GDBstub調(diào)試功能,需要改寫這些指令異常處理函數(shù)。因此一般的調(diào)試系統(tǒng)器或調(diào)試代理都要涉及單步調(diào)試指令的處理函數(shù),需要系統(tǒng)內(nèi)核的支持。上面提到的KGDB修改了異常處理函數(shù),GDBserver需要系統(tǒng)內(nèi)核提供ptrace函數(shù)。這種方法存在一些不足:修改內(nèi)核工作量大,移植性差。針對這些情況可以采用另一種斷點(diǎn)實(shí)現(xiàn)方案:在stub中定義一個(gè)設(shè)置斷點(diǎn)函數(shù)。
  斷點(diǎn)函數(shù)模擬調(diào)試異常指令,保護(hù)現(xiàn)場、調(diào)用異常處理函數(shù)、恢復(fù)現(xiàn)場并將控制權(quán)交給被調(diào)試程序。斷點(diǎn)函數(shù)的基本流程如下。
  #define BREAKPOINT _asm_ _volatile_(″bl ent_exception\n″)
  void debug_trap( )
  { _asm_ _volatile_(″e(cuò)nt_exception: \n″
    保存現(xiàn)場
  ″bl handle_exception \n″
  ″out_exception: \n″
  恢復(fù)現(xiàn)場
  );
  }
  handle_exception( )流程類似圖4中的虛線框部分,實(shí)現(xiàn)的關(guān)鍵是斷點(diǎn)指令的替換。斷點(diǎn)設(shè)置時(shí),從GDB傳來硬件平臺提供的斷點(diǎn)異常指令的二進(jìn)制碼,必須將此二進(jìn)制碼替換成stub中新定義的BREAKPOINT二進(jìn)制碼,才能進(jìn)入調(diào)試異常處理函數(shù)。因此在handle_exception( )中,如果收到的請求是“M”,則需要處理數(shù)據(jù),流程如圖7。
  這種方法理論上在內(nèi)核調(diào)試和應(yīng)用程序調(diào)試中都可以使用,但在應(yīng)用程序的調(diào)試中優(yōu)點(diǎn)更明顯。在寫stub時(shí)不涉及內(nèi)核,在調(diào)試應(yīng)用程序時(shí)不需切換到內(nèi)核模式下,直接在用戶模式中即可完成。此方法也存在不足之處。為了實(shí)現(xiàn)現(xiàn)場保護(hù),要求用戶了解系統(tǒng)內(nèi)的寄存器。隨著stub本身復(fù)雜度的增加,其正確性需要更多的檢驗(yàn)。
  加stub的遠(yuǎn)程調(diào)試方法方便而有效,而且可以降低項(xiàng)目成本,在實(shí)際工作中得到廣泛的應(yīng)用。在不修改內(nèi)核前提下調(diào)試應(yīng)用程序的方法已成功應(yīng)用于筆者開發(fā)的微內(nèi)核結(jié)構(gòu)的操作系統(tǒng),為系統(tǒng)的開發(fā)應(yīng)用提供了良好的調(diào)試手段。當(dāng)然加stub的遠(yuǎn)程調(diào)試方法也存在一些不足。由于stub的應(yīng)用是在串口通信的基礎(chǔ)上,因此串口處理函數(shù)以及stub自身處理函數(shù)的正確性是確保stub安全調(diào)試的前提。
參考文獻(xiàn)
1 李紅衛(wèi),李翠萍.kgdb調(diào)試Linux內(nèi)核的剖析與改進(jìn).微型機(jī)與應(yīng)用,2004;23(10)
2 郭勝超.GDB遠(yuǎn)程調(diào)試及其在嵌入式Linux系統(tǒng)中的應(yīng)用.計(jì)算機(jī)工程與應(yīng)用,2004;26(10)
3 彭進(jìn)展.GRDBS:一種針對嵌入式系統(tǒng)的通用遠(yuǎn)程調(diào)試系統(tǒng).計(jì)算機(jī)工程,2003;29(2)
4 Gatliff,Bill.Embedding with GNU:the gdb Remote Serial Protocol.Embedded Systems Programming,1999;(9):109
5 Gilmore J,Shebs S.GDB Internals:A Guild to the Internals of the GNU Debugger.Free Software Foundation Inc,1999

本站內(nèi)容除特別聲明的原創(chuàng)文章之外,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,并不代表本網(wǎng)站贊同其觀點(diǎn)。轉(zhuǎn)載的所有的文章、圖片、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無法一一聯(lián)系確認(rèn)版權(quán)者。如涉及作品內(nèi)容、版權(quán)和其它問題,請及時(shí)通過電子郵件或電話通知我們,以便迅速采取適當(dāng)措施,避免給雙方造成不必要的經(jīng)濟(jì)損失。聯(lián)系電話:010-82306118;郵箱:aet@chinaaet.com。