時間:2006-04-25 10:13:00來源:0
圖1. 遠程調試系統
2、RSP協議
GDB RSP(Remote Serial Protocol)定義了GDB宿主機與被調試目標機進行通信時數據包的格式。信息的格式是:$數據#校驗碼。多數的信息都使用ASCII碼,數據由一系列的ASCII碼組成,校驗碼是由兩個16進制數組成的單字節校驗碼。接受方接受數據并校驗,若正確則回應“+”,錯誤則回應“-”。通信的內容包括讀寫數據、控制程序運行、報告程序狀態等命令。RSP的基本命令從通信對話角度可以分為兩種:
1) 請求
?:讀當前系統狀態
g:讀所有寄存器
G:寫所有寄存器
m:讀內存
M:寫內存
c:繼續執行
s: 單步執行
k:終止進程
2) 答復
“”:告訴GDB上次請求命令不支持。
E:告訴GDB出錯
OK:上次請求正確
W:系統在exit_status狀態下退出。
X:系統在signal信號下終止。
S:系統在signal信號下停止。
O:告訴GDB控制臺輸出,這也是唯一向GDB發出的命令
3、GDB遠程調試功能
調試內核時通常還沒有文件系統,而且多數嵌入式由于自身資源的限制不具備文件系統,因此將與文件系統有關的源文件、目標文件及符號表都存放在主機上,由主機上的調試器處理。同樣,調試用的輸入輸出設備也是由主機提供。主機上的調試器接受用戶輸入的調試命令并進行預處理,對于有些命令(如breakpoint)的處理就在主機GDB上實現,不需要同目標機進行通信。當然,更多的指令需要在目標機上調試代理上實現的。主機將預處理完之后的命令根據RSP進行封裝,發送給目標機上的調試代理,調試代理接受命令后作相應的處理,并返回信息給主機上的調試器。
4、目標機上stub的實現
目標機上stub的基本功能是與主機GDB進行通信,實現讀寫內存、寄存器,stop,continue。主機GDB同目標機上stub進行通信的通用模型如圖2:
圖2. GDB同目標機上stub通信的通用模型
目標機與主機通過硬件連接,被調試部分插入stub,GDB與被調試部分通過RSP進行通信。根據stub所處層的不同來實現不同層的調試,包括內核層、應用層的調試。
4.1 內核層調試模型
圖3. 使用stub對內核進行調試
如圖3,將stub插入到內核里就可以實現內核的調試了。Linux內核調試機制KGDB就是使用這種模式。KGDB可以分為初始化模塊和控制模塊。
4.1.1初始化模塊
修改異常處理函數,使得在異常發生時都進入函數handle_exception(),這樣GDB就能夠捕獲這些異常。初始化之后使用breakpoint()函數將系統控制權直接交給GDB。KGDB對異常處理函數的修改基本上可以分為二種。
定義宏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; } }
改變程序的流程,以int3的處理函數為例
#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;
}
從以上代碼可見,進入內核調試狀態之后,異常處理函數就是handle_exception(),程序流程跳過了非調試狀態時的處理函數do_trap。
不改變程序的流程,以異常divide_error 的處理函數為例
#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, FPE_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);
}
從以上代碼中看不出調試狀態跟非調試狀態的區別,然而我們看一下do_trap函數中可能會調用的函數die()。
void die(const char * str, struct pt_regs * regs, long err)
{
CHK_REMOTE_DEBUG(1,SIGTRAP,err,regs,)
do_exit(SIGSEGV);
}
由此可見,調試狀態下的異常處理函數還是進入了handle_exception函數。不過與上面一種異常不同之處在于:異常處理函數在調試與非調試狀態下的程序流程是相同的,handle_exception只提供獲取系統當時的狀態,繼續運行的結果還是do_exit。
雖然不是所有異常函數都是按上述兩種方法定義的,但本質上都可以歸劃為其一,顯然絕大多數處理函數的修改屬于第二種,因為第一種異常就是為調試準備的。因此在目標機具有調試用的輸出設備的情況下,完全可以不修改第二種異常處理函數,因為linux內核在非調試狀態下的異常處理函數已經輸出必要的狀態信息、出錯信息。
4.1.2控制模塊
在控制模塊完成與主機GDB的通信,具體流程如圖4,handle_exception函數首先判斷CPU是否處于VM86模式或用戶態,若是則返回,可見KGDB只調試內核態程序。然后接受GDB發來的信息,根據接受的信息作出相應的操作和回復。流程圖的虛線框內是所有GDBstub中handle_exception函數的通用流程。
4.2 應用程序調試模型
在嵌入式Linux開發領域里調試應用程序常用調試代理工具GDBserver,其工作原理并不是將stub編譯在被調試應用程序內,而是把被調試程序作為GDBserver的子進程,這樣GDBserver就可以利用內核提供的代碼跟蹤機制(ptrace)監控被調試進程的運行,從而來完成調試任務。此工作原理同GDB本地調試相似。其調試模型如圖5。GDBserver的工作流程是:GDBserver創建子進程->綁定跟蹤ptrace(ptrace_traceme,,)->從主機傳來的各種調試命令通過GDBserver轉化為各種操作需求的ptrace。顯然,如果要用GDBserver來進行遠程調試的話,就需要內核操作系統的支持,包括子進程、代碼跟蹤機制,這樣對于其他嵌入式系統內核工作量會比較大。而且ptrace也有其局限性,比如只能跟蹤它的子進程,在調試進程和被調試進程之間傳送一個長字的數據。使用通用的調試模式工作量會更小。如圖6,將stub編譯在應用程序中,并在應用程序入口處就插入斷點,程序開始就上控制權交給GDB,之后的流程跟內核層調試類似。
圖4. GDBKGDB中handle_exception函數流程
圖5. 使用GDBserver對應用程序進行調試
圖6. 使用stub對應用程序進行調試
5. 不修改內核前提下調試應用程序
GDB實現設置斷點的方式是使用內存的讀寫,即將原指令用一個trap指令代替,使得程序執行到該指令時產生單步調試中斷,然后就進入異常處理函數,針對調試器的各種操作處理函數需要作出相應的操作。不同的系統提供不同的調試異常指令,如int3,trap2等,顯然對于使用這些硬件平臺提供的斷點指令為了實現GDBstub調試功能需要改寫這些指令異常處理函數。因此一般的調試系統器或調試代理都需要涉及單步調試指令的處理函數,需要系統內核的支持。上面提到的KGDB修改了異常處理函數,GDBserver需要系統內核提供ptrace函數。這種方法存在一些不足之處:修改內核工作量大,移植性差。針對這些情況我們可以采用另一種斷點實現方案:在stub中定義一個設置斷點函數。
斷點函數模擬調試異常指令,實現保護現場、調用異常處理函數、恢復現場并將控制權交給被調試程序。斷點函數的基本流程如下。
#define BREAKPOINT __asm__ __volatile__(" bl ent_exception\n)
void debug_trap()
{ __asm__ __volatile__(
" ent_exception: \n"
保存現場
" bl handle_exception \n"
" out_exception: \n"
恢復現場
);
}
handle_exception()函數流程類似圖4中的虛線框部分。其中有一點,也是這種方法實現的關鍵部分是:斷點指令的替換。斷點設置時從GDB傳過來的硬件平臺提供的斷點異常指令的二進制碼,必須將此二進制碼替換成在stub中新定義的BREAKPOINT二進制碼,這樣才能進入調試異常處理函數。因此在handle_exception()函數中,如果收到的請求是“M”,則需要作些處理,流稱如圖7:
圖7. 替換指令
這種方法理論上在內核調試和應用程序調試中都可以使用,但在應用程序的調試中其優點更明顯。這種方法在寫stub時候不涉及內核,在調試應用程序時不需要切換到內核模式下,直接在用戶模式中就可以完成。這種方法也存在些不足之處。為了實現現場保護,要求用戶了解系統內的寄存器。隨著stub本身復雜度的增加,它的正確性需要更多的檢驗。
6、結束語
加stub的遠程調試方法方便而有效,而且可以降低項目成本,在實際工作中得到廣泛的研究和應用。本文提到在不修改內核前提下調試應用程序的方法已成功應用于我們自己開發的微內核結構的操作系統里,為該系統的開發應用提供良好的調試手段。當然加stub的遠程調試方法也存在一些不足。顯然stub的應用是在串口通信的基礎上,因此串口處理函數以及stub自身處理函數的正確性是確保stub安全調試的前提。
參考文獻
[1].李紅衛李翠萍,kgdb調試Linux內核肋剖析與改進,微型機與應用,2004年第10期
[2].郭勝超,GDB遠程調試及其在嵌入式Linux系統中的應用,計算機工程與應用,2004年第26卷第10期.
[3].彭進展,GRDBS:一種針對嵌入式系統的通用遠程調試系統,計算機工程,2003年2月第29卷第2期.
[4] .Gatliff, Bill, Embedding with GNU: the gdb Remote Serial Protocol, Embedded Systems Programming, September 1999, p. 109.
[5].Gilmore J, Shebs S, GDB Internals: A Guild to the Internals of the GNU Debugger, Free Software Foundation Inc.,1999.
作者單位:浙江大學計算機系
地址:浙江大學玉泉校區4舍230 310027
Email:liulin@zju.edu.cn
標簽:
上一篇:單片機破解的常用方法及應對策略
傳動網版權與免責聲明:凡本網注明[來源:傳動網]的所有文字、圖片、音視和視頻文件,版權均為傳動網(www.cdcst56.com)獨家所有。如需轉載請與0755-82949061聯系。任何媒體、網站或個人轉載使用時須注明來源“傳動網”,違反者本網將追究其法律責任。
本網轉載并注明其他來源的稿件,均來自互聯網或業內投稿人士,版權屬于原版權人。轉載請保留稿件來源及作者,禁止擅自篡改,違者自負版權法律責任。
產品新聞
更多>2025-12-10
2025-11-20
2025-11-10
2025-11-10
2025-11-07
2025-10-31