《電子技術(shù)應(yīng)用》
您所在的位置:首頁(yè) > 嵌入式技術(shù) > 設(shè)計(jì)應(yīng)用 > kgdb調(diào)試Linux內(nèi)核的剖析與改進(jìn)
kgdb調(diào)試Linux內(nèi)核的剖析與改進(jìn)
李紅衛(wèi)1 李翠萍1 韓紅宇2
1. 常州江蘇技術(shù)師范學(xué)院(213001) ; 2. 阜新遼寧工程技術(shù)大學(xué)(123000)
摘要: 在Linux內(nèi)核中加入kgdb,通過(guò)開(kāi)發(fā)機(jī)上的gdb對(duì)目標(biāo)機(jī)上的內(nèi)核進(jìn)行源代碼級(jí)的調(diào)試技術(shù)。
Abstract:
Key words :

摘   要: 在Linux內(nèi)核中加入kgdb,通過(guò)開(kāi)發(fā)機(jī)上的gdb對(duì)目標(biāo)機(jī)上的內(nèi)核進(jìn)行源代碼級(jí)的調(diào)試技術(shù)。
關(guān)鍵詞: 遠(yuǎn)程調(diào)試  kgdb  串口通信協(xié)議  異常處理

  操作系統(tǒng)的內(nèi)核調(diào)試器除完成一般的調(diào)試功能外,還必須工作在內(nèi)核中。因此,內(nèi)核調(diào)試器與用戶級(jí)的調(diào)試器有很大的區(qū)別。在Linux系統(tǒng)中,對(duì)內(nèi)核的調(diào)試有很多種方法,例如:可在內(nèi)核中插入printk( )函數(shù),將調(diào)試信息輸出,然后針對(duì)輸出的信息進(jìn)行分析;可以使用/proc文件系統(tǒng)對(duì)內(nèi)核進(jìn)行分析;可以利用strace命令對(duì)系統(tǒng)調(diào)用進(jìn)行分析。但這些方法都不能直接對(duì)內(nèi)核源代碼級(jí)進(jìn)行調(diào)試。因此,本文介紹一種在內(nèi)核源代碼中插入kgdb(Kernel GNU Debuger,GNU內(nèi)核調(diào)試器),通過(guò)開(kāi)發(fā)機(jī)上的gdb調(diào)試器對(duì)運(yùn)行于目標(biāo)機(jī)上的內(nèi)核進(jìn)行源代碼級(jí)調(diào)試的技術(shù)。
1  kgdb調(diào)試內(nèi)核的實(shí)現(xiàn)原理
1.1 通信協(xié)議
  kgdb提供了一種使用gdb調(diào)試Linux內(nèi)核的機(jī)制。使用kgdb調(diào)試內(nèi)核需要二臺(tái)機(jī)器,一臺(tái)作為開(kāi)發(fā)機(jī),另一臺(tái)作為目標(biāo)機(jī),通過(guò)串口將它們連接起來(lái)。在將要調(diào)試的內(nèi)核中插入kgdb,重新編譯內(nèi)核,使其運(yùn)行在目標(biāo)機(jī)上。而gdb在開(kāi)發(fā)機(jī)上運(yùn)行,gdb通過(guò)串口與要調(diào)試的內(nèi)核進(jìn)行通信,對(duì)目標(biāo)機(jī)上的內(nèi)核進(jìn)行控制,從而實(shí)現(xiàn)遠(yuǎn)程調(diào)試內(nèi)核的目的。
  要使gdb有效地控制調(diào)試目標(biāo)機(jī)上的內(nèi)核,必須與目標(biāo)機(jī)上的kgdb約定相互的通信協(xié)議。而gdb本身帶有一個(gè)遠(yuǎn)程串行通信協(xié)議,所以在kgdb中包含相同的協(xié)議即可實(shí)現(xiàn)開(kāi)發(fā)機(jī)與目標(biāo)機(jī)之間的通信。
  開(kāi)發(fā)機(jī)上的gdb可以向目標(biāo)機(jī)發(fā)送一些命令數(shù)據(jù)包,如果kgdb能夠?qū)崿F(xiàn)g、G、m、M、c和s等主要命令,則在使用gdb對(duì)目標(biāo)機(jī)上的內(nèi)核進(jìn)行調(diào)試時(shí)就像在本機(jī)上調(diào)試程序一樣。這六個(gè)命令的功能描述如下:
  g:查看CPU寄存器的值;
  G:設(shè)置CPU寄存器的值;
  maddr,count:從addr位置開(kāi)始讀count個(gè)字節(jié)的數(shù)據(jù);
  Maddr,count:從addr位置開(kāi)始寫(xiě)count個(gè)字節(jié)的數(shù)據(jù);
  c/caddr:在當(dāng)前位置上繼續(xù)執(zhí)行程序或從地址addr處開(kāi)始執(zhí)行;
  s/saddr:?jiǎn)尾綀?zhí)行當(dāng)前的指令,或者執(zhí)行到指定的addr位置。
1.2 kgdb遠(yuǎn)程調(diào)試技術(shù)分析
  遠(yuǎn)程調(diào)試常采用插樁(stub)的方法實(shí)現(xiàn)。在目標(biāo)操作系統(tǒng)內(nèi)和開(kāi)發(fā)機(jī)上的調(diào)試器內(nèi)分別加入某些功能模塊(如通信模塊和異常處理模塊等),二者互通信息來(lái)完成調(diào)試工作,這些功能模塊統(tǒng)稱為插樁。gdb本身帶有這些功能模塊,所以在調(diào)試內(nèi)核時(shí),只需要在內(nèi)核中加入stub插樁模塊即可實(shí)現(xiàn)用gdb遠(yuǎn)程調(diào)試內(nèi)核的目的。gdb遠(yuǎn)程調(diào)試器的結(jié)構(gòu)如圖1所示。


  通過(guò)對(duì)kgdb源程序進(jìn)行分析可知,kgdb主要有二大模塊:一個(gè)是初始化模塊,完成初始化過(guò)程,接管所有異常、設(shè)置串口通信等低層實(shí)現(xiàn);另一個(gè)是控制模塊,實(shí)現(xiàn)通信,對(duì)接收到的信息包進(jìn)行解析并執(zhí)行,對(duì)應(yīng)答包進(jìn)行打包發(fā)送。
1.2.1 kgdb初始化模塊的實(shí)現(xiàn)
  在Linux啟動(dòng)時(shí)要調(diào)用start_kernel( )函數(shù)。該函數(shù)調(diào)用一系列初始化函數(shù),完成系統(tǒng)初始化工作。kgdb通過(guò)對(duì)該函數(shù)進(jìn)行修改,使其在內(nèi)核初始化工作完成后將控制權(quán)交給插樁程序kgdb,由插樁程序完成調(diào)試內(nèi)核的任務(wù)。
  (1)設(shè)置異常中斷處理程序入口
  kgdb插樁模塊要對(duì)目標(biāo)機(jī)上被調(diào)試的程序進(jìn)行控制,必須對(duì)目標(biāo)機(jī)上所發(fā)生的所有異常進(jìn)行統(tǒng)一管理。在上述Linux啟動(dòng)過(guò)程中,若要執(zhí)行start_kernel( )函數(shù),則應(yīng)在該函數(shù)中調(diào)用trap_init( )函數(shù)設(shè)置各種入口地址,如異常事件處理程序入口、系統(tǒng)調(diào)用入口等。其中trap0~trap19為各種異常事件錯(cuò)誤入口,如被0除、溢出、存儲(chǔ)器越界等。為了使gdb能夠捕獲這些發(fā)生在目標(biāo)機(jī)上的異常,kgdb要對(duì)各個(gè)需要捕獲的異常處理函數(shù)進(jìn)行修改。當(dāng)發(fā)生異常時(shí)使異常處理事件進(jìn)入異常處理函數(shù)handle_excepton( ),han-
dle_excepton( )就會(huì)將目標(biāo)機(jī)上發(fā)生的異常以信號(hào)“Sxx”的方式通知主機(jī)上的gdb,主機(jī)上的gdb即可知道目標(biāo)機(jī)上發(fā)生的異常。
  (2)串口初始化
  將所有異常入口地址進(jìn)行修改后,應(yīng)該對(duì)串口進(jìn)行初始化,并設(shè)置linux_debug_traps指針變量的值,使其指向異常處理程序入口地址。此工作結(jié)束后,kgdb就接管所有發(fā)生在目標(biāo)機(jī)上的異常事件。
1.2.2 kgdb控制模塊的實(shí)現(xiàn)
  kgdb控制模塊主要完成對(duì)協(xié)議的解析和執(zhí)行,這些功能都在handle_exception( )函數(shù)中實(shí)現(xiàn)。通過(guò)該函數(shù)可實(shí)現(xiàn)在內(nèi)核中設(shè)置斷點(diǎn)、單步執(zhí)行代碼和監(jiān)視變量的值。
  handle_exception( )函數(shù)首先判斷CPU是否處于虛擬86模式或用戶態(tài),若是則返回。由此可見(jiàn),kgdb只對(duì)內(nèi)核態(tài)程序進(jìn)行調(diào)試。其次判斷是否發(fā)生讀/寫(xiě)內(nèi)存異常,若是則為讀/寫(xiě)內(nèi)存操作設(shè)置一個(gè)有效內(nèi)存地址,返回重試,否則向主機(jī)發(fā)送信息“Sxx”,其中xx是發(fā)生異常的信號(hào)量值,即通知開(kāi)發(fā)機(jī)上的gdb在目標(biāo)機(jī)上發(fā)生了什么樣的異常。接下來(lái)handle_exception( )接收來(lái)自主機(jī)的調(diào)試命令,對(duì)來(lái)自主機(jī)的命令進(jìn)行解析并完成命令的執(zhí)行,在kgdb中主要完成的調(diào)試命令有m、M、g、G、s、c。當(dāng)這些命令執(zhí)行完后還需要向開(kāi)發(fā)機(jī)發(fā)送應(yīng)答信息,報(bào)告完成命令的情況。此函數(shù)的流程如圖2所示。


  (1)g讀寄存器命令的實(shí)現(xiàn)。當(dāng)系統(tǒng)發(fā)生異常時(shí),系統(tǒng)首先將發(fā)生異常的進(jìn)程的CPU狀態(tài)保存在核心堆棧中。用g命令讀寄存器時(shí),實(shí)際上就是從核心棧中去取,但gdb所規(guī)定的寄存器的排列與Linux內(nèi)核中對(duì)寄存器的排列順序不一致,需要進(jìn)行寄存器的轉(zhuǎn)換。通過(guò)函數(shù)regs_to_gdb_regs(gdb_regs,&regs)實(shí)現(xiàn)轉(zhuǎn)換,轉(zhuǎn)換結(jié)束后將寄存器的值打包送回主機(jī)。
  (2)G寫(xiě)寄存器命令的實(shí)現(xiàn)。對(duì)接收到的數(shù)據(jù)包進(jìn)行解包,把結(jié)果放入gdb_regs結(jié)構(gòu)中,再通過(guò)gdb_regs_to_regs(gdb_regs,&regs)函數(shù)將接收到的寄存器的值進(jìn)行轉(zhuǎn)換,并放入核心棧中,通過(guò)對(duì)核心棧的修改得到修改寄存器的目標(biāo)。
  (3)讀/寫(xiě)內(nèi)存命令的實(shí)現(xiàn)。對(duì)內(nèi)存的讀寫(xiě)有可能再次發(fā)生讀寫(xiě)內(nèi)存異常。例如對(duì)一個(gè)非法的地址進(jìn)行讀寫(xiě)的kgdb解決方法如下:
  kgdb通過(guò)函數(shù)int get_char(char?鄢char)讀內(nèi)存,通過(guò)函數(shù)void set_char(char?鄢addr,int val)寫(xiě)內(nèi)存。kgdb在讀/寫(xiě)不確定的內(nèi)存時(shí),通過(guò)二個(gè)變量來(lái)判斷是否讀/寫(xiě)內(nèi)存發(fā)生異常。一個(gè)變量是kgdb_memerr_expected,標(biāo)志是否讀寫(xiě)不確定的內(nèi)存;另一個(gè)變量是kgdb_memerr,標(biāo)志在讀寫(xiě)內(nèi)存時(shí)是否發(fā)生了異常。這樣,對(duì)不確定內(nèi)存的讀/寫(xiě)過(guò)程可分三步。
  第一步:對(duì)這二個(gè)變量賦值。將kgdb_memerr_expected賦值為1,表示對(duì)不確定內(nèi)存進(jìn)行讀/寫(xiě)操作。對(duì)kgdb_me-merr賦值為0,表示在讀/寫(xiě)內(nèi)存時(shí)沒(méi)有發(fā)生異常。
  第二步:對(duì)內(nèi)存進(jìn)行讀寫(xiě)操作。若讀寫(xiě)地址是非法地址,則進(jìn)入異常處理程序,轉(zhuǎn)第三步。若正確,則返回,并清變量kgdb_memerr_expected為0,表示讀寫(xiě)內(nèi)存結(jié)束。
  第三步:進(jìn)入異常處理程序中。首先要判斷kgdb_me-merr_expected是否為1。若是1,則說(shuō)明對(duì)內(nèi)存讀/寫(xiě)時(shí)發(fā)生了異常。這時(shí)先清kgdb_ memerr_expected為0,置kgdb_memerr為1,標(biāo)志在讀/寫(xiě)內(nèi)存時(shí)發(fā)生了異常,并為讀/寫(xiě)內(nèi)存設(shè)置一個(gè)有效地址。這個(gè)有效地址是一個(gè)可讀/寫(xiě)的內(nèi)存地址,可以對(duì)其進(jìn)行操作,操作的結(jié)果是無(wú)效的。
  (4)s單步命令的實(shí)現(xiàn)。基于x86 CPU的體系結(jié)構(gòu)支持單步執(zhí)行,但它只是指令級(jí)的單步,而不是源代碼級(jí)的單步。這里的s命令是實(shí)現(xiàn)指令級(jí)的單步,它的處理方法是:將產(chǎn)生異常的進(jìn)程標(biāo)志寄存器的陷阱標(biāo)志位TF置1,異常中斷處理程序結(jié)束返回到異常點(diǎn)繼續(xù)執(zhí)行。因TF標(biāo)志位為1,所以程序執(zhí)行一條指令后又進(jìn)入異常處理程序。主機(jī)處的gdb利用此命令實(shí)現(xiàn)源代碼級(jí)的單步跟蹤。
  (5)c繼續(xù)運(yùn)行命令的實(shí)現(xiàn)。異常處理程序結(jié)束,恢復(fù)異常處的CPU狀態(tài),使程序繼續(xù)執(zhí)行。
2  kgdb插樁技術(shù)的不足與改進(jìn)
  現(xiàn)有的kgdb不具有使目標(biāo)程序進(jìn)行調(diào)試模式和正常運(yùn)行模式的切換功能。目前的處理方法是:當(dāng)程序調(diào)試好后,將kgdb卸下,重新編譯程序。當(dāng)發(fā)生錯(cuò)誤時(shí)再將kgdb插入目標(biāo)程序中,編譯后再在目標(biāo)機(jī)上調(diào)試。由此可見(jiàn)操作十分不便。本文提出一種新的設(shè)計(jì)思路,使目標(biāo)機(jī)上運(yùn)行的程序可隨時(shí)進(jìn)行調(diào)試模式與正常運(yùn)行模式的切換,這樣更便于調(diào)試。下面討論其實(shí)現(xiàn)方法。
   (1)在目標(biāo)機(jī)方:通過(guò)對(duì)kgdb源代碼分析可知,在進(jìn)入各異常函數(shù)中,首先判斷指向gdb_debug_hook類型的函數(shù)指針變量linux_debug_hook是否為空。若不為空則執(zhí)行異常處理函數(shù)handle_exception( ),否則,執(zhí)行原異常處理程序。這樣通過(guò)修改這個(gè)變量的值,即可實(shí)現(xiàn)調(diào)試模式與正常運(yùn)行模式的轉(zhuǎn)換。當(dāng)設(shè)置linux_debug_hook為(gdb_debug_hook?鄢)NULL時(shí),目標(biāo)機(jī)程序運(yùn)行在正常運(yùn)行狀態(tài);當(dāng)設(shè)置linux_debug_hook為指向異常函數(shù)handle_exception( )時(shí),則目標(biāo)機(jī)程序運(yùn)行在調(diào)試狀態(tài)。
   (2)在主機(jī)方:調(diào)試狀態(tài)到正常運(yùn)行狀態(tài)的轉(zhuǎn)換:在kgdb中設(shè)置一個(gè)變量int stop_gdb。當(dāng)主機(jī)對(duì)該變量進(jìn)行賦值時(shí),目標(biāo)機(jī)上的kgdb獲取信息后,使指向函數(shù)的指針變量linux_debug_hook指向(gdb_debug_hook?鄢)NULL,然后,kgdb構(gòu)造c命令并執(zhí)行,使目標(biāo)機(jī)上的程序脫離調(diào)試運(yùn)行模式進(jìn)入正常運(yùn)行模式。
  主機(jī)端發(fā)送命令:set stop_gdb=1
  這條命令經(jīng)過(guò)主機(jī)端的gdb解析,實(shí)際上是執(zhí)行協(xié)議命令M,即寫(xiě)內(nèi)存命令。這樣就需要在kgdb中對(duì)M命令進(jìn)行修改。
  目標(biāo)端kgdb 執(zhí)行M命令的算法描述如下:
  (1)向主機(jī)返回應(yīng)答信息“OK”;
  (2)如果所寫(xiě)地址與變量stop_gdb的地址相同,則轉(zhuǎn)(4);
  (3)執(zhí)行原M命令,轉(zhuǎn)(8);
  (4)設(shè)置變量initialized為0;
  (5)設(shè)置指針變量linux_debug_hook為(gdb_debug_hook*)NULL;
  (6)構(gòu)造命令c;
   (7)執(zhí)行命令c,目標(biāo)機(jī)上被調(diào)試的程序轉(zhuǎn)為正常運(yùn)行模式;
  (8)結(jié)束。
  正常運(yùn)行狀態(tài)到調(diào)試狀態(tài)的轉(zhuǎn)換:目標(biāo)程序從正常運(yùn)行狀態(tài)切換到調(diào)試狀態(tài),就不像從調(diào)試狀態(tài)到正常運(yùn)行狀態(tài)切換那樣對(duì)一個(gè)變量進(jìn)行設(shè)置而觸發(fā)狀態(tài)的切換。因?yàn)槟繕?biāo)機(jī)已不在主機(jī)端gdb的控制下運(yùn)行,主機(jī)端的gdb與目標(biāo)機(jī)端的kgdb已失去了聯(lián)系。所以要激活目標(biāo)機(jī)進(jìn)入調(diào)試狀態(tài),只能使用中斷技術(shù),利用串口中斷來(lái)激活對(duì)目標(biāo)機(jī)由正常運(yùn)行狀態(tài)切換到調(diào)試狀態(tài)。
  在gdb與目標(biāo)機(jī)建立連接時(shí),gdb首先要向目標(biāo)機(jī)發(fā)送一個(gè)數(shù)據(jù)包$Hc-1#09,它會(huì)觸發(fā)目標(biāo)機(jī)上串口中斷處理程序的執(zhí)行,并判斷接收的數(shù)據(jù)包是否為“Hc-1”。若是則設(shè)置linux_debug_hook指向異常中斷處理程序handle_exception( ),并執(zhí)行breakpoint( )函數(shù),使目標(biāo)機(jī)進(jìn)入調(diào)試狀態(tài)。
  通過(guò)上述方法即可實(shí)現(xiàn)目標(biāo)機(jī)上程序運(yùn)行狀態(tài)的切換,使程序調(diào)試更加靈活。
3  結(jié)束語(yǔ)
  通過(guò)對(duì)kgdb調(diào)試技術(shù)的分析與改進(jìn),增加了調(diào)試的方法和思路。此調(diào)試技術(shù)可以應(yīng)用于嵌入式系統(tǒng)的設(shè)計(jì)和開(kāi)發(fā)中,為嵌入式開(kāi)發(fā)工具增加了一種廉價(jià)而強(qiáng)有力的調(diào)試工具。
參考文獻(xiàn)
1   李善平,劉文峰.Linux內(nèi)核2.4版源代碼分析大全.北京: 機(jī)械工業(yè)出版社,2002
2   李紅衛(wèi),李翠萍.嵌入式軟件的調(diào)試技術(shù).計(jì)算機(jī)時(shí)代,2002;(8)
 

此內(nèi)容為AET網(wǎng)站原創(chuàng),未經(jīng)授權(quán)禁止轉(zhuǎn)載。