天天看點

Linux 系統核心的調試

Linux 系統核心的調試

作者:李樹雷  陳渝   時間:2008-01-11  來源:電子産品世界   浏覽評論   推薦給好友   我有問題  個性化定制

關鍵詞:

  本文将首先介紹 Linux 核心上的一些核心代碼監視和錯誤跟蹤技術,這些調試和跟蹤方法因所要求的使用環境和使用方法而各有不同,然後重點介紹三種 Linux 核心的源代碼級的調試方法。

  調試是軟體開發過程中一個必不可少的環節,在 Linux 核心開發的過程中也不可避免地會面對如何調試核心的問題。但是,Linux 系統的開發者出于保證核心代碼正确性的考慮,不願意在 Linux 核心源代碼樹中加入一個調試器。他們認為核心中的調試器會誤導開發者,進而引入不良的修正[1]。是以對 Linux 核心進行調試一直是個令核心程式員感到棘手的問題,調試工作的艱苦性是核心級的開發差別于使用者級開發的一個顯著特點。

  盡管缺乏一種内置的調試核心的有效方法,但是 Linux 系統在核心發展的過程中也逐漸形成了一些監視核心代碼和錯誤跟蹤的技術。同時,許多的更新檔程式應運而生,它們為标準核心附加了核心調試的支援。盡管這些更新檔有些并不被 Linux 官方組織認可,但他們确實功能完善,十分強大。調試核心問題時,利用這些工具與方法跟蹤核心執行情況,并檢視其記憶體和資料結構将是非常有用的。

  本文将首先介紹 Linux 核心上的一些核心代碼監視和錯誤跟蹤技術,這些調試和跟蹤方法因所要求的使用環境和使用方法而各有不同,然後重點介紹三種 Linux 核心的源代碼級的調試方法。

1. Linux 系統核心級軟體的調試技術

  printk() 是調試核心代碼時最常用的一種技術。在核心代碼中的特定位置加入printk() 調試調用,可以直接把所關心的資訊打列印到螢幕上,進而可以觀察程式的執行路徑和所關心的變量、指針等資訊。 Linux 核心調試器(Linux kernel debugger,kdb)是 Linux 核心的更新檔,它提供了一種在系統能運作時對核心記憶體和資料結構進行檢查的辦法。Oops、KDB在文章掌握 Linux 調試技術有詳細介紹,大家可以參考。 Kprobes 提供了一個強行進入任何核心例程,并從中斷處理器無幹擾地收集資訊的接口。使用 Kprobes 可以輕松地收集處理器寄存器和全局資料結構等調試資訊,而無需對Linux核心頻繁編譯和啟動,具體使用方法,請參考使用 Kprobes 調試核心。

  以上介紹了進行Linux核心調試和跟蹤時的常用技術和方法。當然,核心調試與跟蹤的方法還不止以上提到的這些。這些調試技術的一個共同的特點在于,他們都不能提供源代碼級的有效的核心調試手段,有些隻能稱之為錯誤跟蹤技術,是以這些方法都隻能提供有限的調試能力。下面将介紹三種實用的源代碼級的核心調試方法。

2. 使用KGDB建構Linux核心調試環境

  kgdb提供了一種使用 gdb調試 Linux 核心的機制。使用KGDB可以象調試普通的應用程式那樣,在核心中進行設定斷點、檢查變量值、單步跟蹤程式運作等操作。使用KGDB調試時需要兩台機器,一台作為開發機(Development Machine),另一台作為目标機(Target Machine),兩台機器之間通過序列槽或者以太網口相連。序列槽連接配接線是一根RS-232接口的電纜,在其内部兩端的第2腳(TXD)與第3腳(RXD)交叉相連,第7腳(接地腳)直接相連。調試過程中,被調試的核心運作在目标機上,gdb調試器運作在開發機上。

  目前,kgdb釋出支援i386、x86_64、32-bit PPC、SPARC等幾種體系結構的調試器。有關kgdb更新檔的下載下傳位址見參考資料[4]。

2.1 kgdb的調試原理

  安裝kgdb調試環境需要為Linux核心應用kgdb更新檔,更新檔實作的gdb遠端調試所需要的功能包括指令處理、陷阱處理及序列槽通訊3個主要的部分。kgdb更新檔的主要作用是在Linux核心中添加了一個調試Stub。調試Stub是Linux核心中的一小段代碼,提供了運作gdb的開發機和所調試核心之間的一個媒介。gdb和調試stub之間通過gdb串行協定進行通訊。gdb串行協定是一種基于消息的ASCII碼協定,包含了各種調試指令。當設定斷點時,kgdb負責在設定斷點的指令前增加一條trap指令,當執行到斷點時控制權就轉移到調試stub中去。此時,調試stub的任務就是使用遠端串行通信協定将目前環境傳送給gdb,然後從gdb處接受指令。gdb指令告訴stub下一步該做什麼,當stub收到繼續執行的指令時,将恢複程式的運作環境,把對CPU的控制權重新交還給核心。

 

2.2 Kgdb的安裝與設定

  下面我們将以Linux 2.6.7核心為例詳細介紹kgdb調試環境的建立過程。

{{分頁}}

2.2.1軟硬體準備

  以下軟硬體配置取自筆者進行試驗的系統配置情況:

 

 

 

  kgdb更新檔的版本遵循如下命名模式:Linux-A-kgdb-B,其中A表示Linux的核心版本号,B為kgdb的版本号。以試驗使用的kgdb更新檔為例,linux核心的版本為linux-2.6.7,更新檔版本為kgdb-2.2。

  實體連接配接好序列槽線後,使用以下指令來測試兩台機器之間序列槽連接配接情況,stty指令可以對序列槽參數進行設定:

  在development機上執行:

       stty ispeed 115200 ospeed 115200 -F /dev/ttyS0

  在target機上執行:

       stty ispeed 115200 ospeed 115200 -F /dev/ttyS0

  在developement機上執行:

       echo hello > /dev/ttyS0

  在target機上執行:

       cat /dev/ttyS0

  如果序列槽連接配接沒問題的話在将在target機的螢幕上顯示"hello"。

2.2.2 安裝與配置

  下面我們需要應用kgdb更新檔到Linux核心,設定核心選項并編譯核心。這方面的資料相對較少,筆者這裡給出詳細的介紹。下面的工作在開發機(developement)上進行,以上面介紹的試驗環境為例,某些具體步驟在實際的環境中可能要做适當的改動:

I、核心的配置與編譯

  [[email protected] tmp]# tar -jxvf linux-2.6.7.tar.bz2

  [[email protected] tmp]#tar -jxvf linux-2.6.7-kgdb-2.2.tar.tar

  [[email protected] tmp]#cd inux-2.6.7

  請參照目錄更新檔包中檔案README給出的說明,執行對應體系結構的更新檔程式。由于試驗在i386體系結構上完成,是以隻需要安裝一下更新檔:core-lite.patch、i386-lite.patch、8250.patch、eth.patch、core.patch、i386.patch。應用更新檔檔案時,請遵循kgdb軟體包内series檔案所指定的順序,否則可能會帶來預想不到的問題。eth.patch檔案是選擇以太網口作為調試的連接配接端口時需要運用的更新檔。

  應用更新檔的指令如下所示:

    [[email protected] tmp]#patch -p1 <../linux-2.6.7-kgdb-2.2/core-lite.patch

  如果核心正确,那麼應用更新檔時應該不會出現任何問題(不會産生*.rej檔案)。為Linux核心添加了更新檔之後,需要進行核心的配置。核心的配置可以按照你的習慣選擇配置Linux核心的任意一種方式。

  [[email protected] tmp]#make menuconfig

  在核心配置菜單的Kernel hacking選項中選擇kgdb調試項,例如:

    [*] KGDB: kernel debugging with remote gdb                                                            

       Method for KGDB communication (KGDB: On generic serial port (8250))  ---> 

    [*] KGDB: Thread analysis                                                                           

    [*] KGDB: Console messages through gdb

   [[email protected] tmp]#make

  編譯核心之前請注意Linux目錄下Makefile中的優化選項,預設的Linux核心的編譯都以-O2的優化級别進行。在這個優化級别之下,編譯器要對核心中的某些代碼的執行順序進行改動,是以在調試時會出現程式運作與代碼順序不一緻的情況。可以把Makefile中的-O2選項改為-O,但不可去掉-O,否則編譯會出問題。為了使編譯後的核心帶有調試資訊,注意在編譯核心的時候需要加上-g選項。

  不過,當選擇"Kernel debugging->Compile the kernel with debug info"選項後配置系統将自動打開調試選項。另外,選擇"kernel debugging with remote gdb"後,配置系統将自動打開"Compile the kernel with debug info"選項。

  核心編譯完成後,使用scp指令進行将相關檔案拷貝到target機上(當然也可以使用其它的網絡工具,如rcp)。

    [[email protected] tmp]#scp arch/i386/boot/bzImage

[email protected]:/boot/vmlinuz-2.6.7-kgdb

    [[email protected] tmp]#scp System.map [email protected]:/boot/System.map-2.6.7-kgdb

  如果系統啟動使所需要的某些裝置驅動沒有編譯進核心的情況下,那麼還需要執行如下操作:

    [[email protected] tmp]#mkinitrd /boot/initrd-2.6.7-kgdb 2.6.7

    [[email protected] tmp]#scp initrd-2.6.7-kgdb [email protected]:/boot/ initrd-2.6.7-kgdb

II、kgdb的啟動

  在将編譯出的核心拷貝的到target機器之後,需要配置系統引導程式,加入核心的啟動選項。以下是kgdb核心引導參數的說明:

 

  如表中所述,在kgdb 2.0版本之後核心的引導參數已經與以前的版本有所不同。使用grub引導程式時,直接将kgdb參數作為核心vmlinuz的引導參數。下面給出引導器的配置示例。

{{分頁}}

     title 2.6.7 kgdb

     root (hd0,0)

     kernel /boot/vmlinuz-2.6.7-kgdb ro root=/dev/hda1 kgdbwait kgdb8250=1,115200

  在使用lilo作為引導程式時,需要把kgdb參放在由append修飾的語句中。下面給出使用lilo作為引導器時的配置示例。

       image=/boot/vmlinuz-2.6.7-kgdb

       label=kgdb

         read-only

         root=/dev/hda3

       append="gdb gdbttyS=1 gdbbaud=115200"

  儲存好以上配置後重新啟動計算機,選擇啟動帶調試資訊的核心,核心将在短暫的運作後在建立init核心線程之前停下來,列印出以下資訊,并等待開發機的連接配接。

       Waiting for connection from remote gdb...

在開發機上執行:

       gdb

       file vmlinux

       set remotebaud 115200

       target remote /dev/ttyS0

  其中vmlinux是指向源代碼目錄下編譯出來的Linux核心檔案的連結,它是沒有經過壓縮的核心檔案,gdb程式從該檔案中得到各種符号位址資訊。

  這樣,就與目标機上的kgdb調試接口建立了聯系。一旦建立聯接之後,對Linux内的調試工作與對普通的運用程式的調試就沒有什麼差別了。任何時候都可以通過鍵入ctrl+c打斷目标機的執行,進行具體的調試工作。

  在kgdb 2.0之前的版本中,編譯核心後在arch/i386/kernel目錄下還會生成可執行檔案gdbstart。将該檔案拷貝到target機器的/boot目錄下,此時無需更改核心的啟動配置檔案,直接使用指令:

       [[email protected] boot]#gdbstart -s 115200 -t /dev/ttyS0

  可以在KGDB核心引導啟動完成後建立開發機與目标機之間的調試聯系。

2.2.3 通過網絡接口進行調試

  kgdb也支援使用以太網接口作為調試器的連接配接端口。在對Linux核心應用更新檔包時,需應用eth.patch更新檔檔案。配置核心時在Kernel hacking中選擇kgdb調試項,配置kgdb調試端口為以太網接口,例如:

    [*]KGDB: kernel debugging with remote gdb

    Method for KGDB communication (KGDB: On ethernet)  --->

    ( ) KGDB: On generic serial port (8250)

    (X) KGDB: On ethernet

  另外使用eth0網口作為調試端口時,grub.list的配置如下:

    title 2.6.7 kgdb

    root (hd0,0)

    kernel /boot/vmlinuz-2.6.7-kgdb ro root=/dev/hda1 kgdbwait

[email protected]

    5.13/,@192.168. 6.13/

  其他的過程與使用序列槽作為連接配接端口時的設定過程相同。

  注意:盡管可以使用以太網口作為kgdb的調試端口,使用序列槽作為連接配接端口更加簡單易行,kgdb項目組推薦使用序列槽作為調試端口。

2.2.4 子產品的調試方法

  核心可加載子產品的調試具有其特殊性。由于核心子產品中各段的位址是在子產品加載進核心的時候才最終确定的,是以develop機的gdb無法得到各種符号位址資訊。是以,使用kgdb調試子產品所需要解決的一個問題是,需要通過某種方法獲得可加載子產品的最終加載位址資訊,并把這些資訊加入到gdb環境中。

I、在Linux 2.4核心中的核心子產品調試方法

  在Linux2.4.x核心中,可以使用insmod -m指令輸出子產品的加載資訊,例如:

       [[email protected] tmp]# insmod -m hello.ko >modaddr

  檢視子產品加載資訊檔案modaddr如下:

.this           00000060  c88d8000  2**2

.text           00000035  c88d8060  2**2

.rodata         00000069  c88d80a0  2**5

……

.data           00000000  c88d833c  2**2

.bss            00000000  c88d833c  2**2

……

  在這些資訊中,我們關心的隻有4個段的位址:.text、.rodata、.data、.bss。在development機上将以上位址資訊加入到gdb中,這樣就可以進行子產品功能的測試了。

(gdb) Add-symbol-file hello.o 0xc88d8060 -s .data 0xc88d80a0 -s

.rodata 0xc88d80a0 -s .bss 0x c88d833c

  這種方法也存在一定的不足,它不能調試子產品初始化的代碼,因為此時子產品初始化代碼已經執行過了。而如果不執行子產品的加載又無法獲得子產品插入位址,更不可能在子產品初始化之前設定斷點了。對于這種調試要求可以采用以下替代方法。

  在target機上用上述方法得到子產品加載的位址資訊,然後再用rmmod解除安裝子產品。在development機上将得到的子產品位址資訊導入到gdb環境中,在核心代碼的調用初始化代碼之前設定斷點。這樣,在target機上再次插入子產品時,代碼将在執行子產品初始化之前停下來,這樣就可以使用gdb指令調試子產品初始化代碼了。

  另外一種調試子產品初始化函數的方法是:當插入核心子產品時,核心子產品機制将調用函數sys_init_module(kernel/modle.c)執行對核心子產品的初始化,該函數将調用所插入子產品的初始化函數。程式代碼片斷如下:

…… ……

 if (mod->init != NULL)

  ret = mod->init();

…… ……

  在該語句上設定斷點,也能在執行子產品初始化之前停下來。

II、在Linux 2.6.x核心中的核心子產品調試方法

  Linux 2.6之後的核心中,由于module-init-tools工具的更改,insmod指令不再支援-m參數,隻有采取其他的方法來擷取子產品加載到核心的位址。通過分析ELF檔案格式,我們知道程式中各段的意義如下:

.text(代碼段):用來存放可執行檔案的操作指令,也就是說是它是可執行程式在記憶體種的鏡像。

.data(資料段):資料段用來存放可執行檔案中已初始化全局變量,也就是存放程式靜态配置設定的變量和全局變量。

.bss(BSS段):BSS段包含了程式中未初始化全局變量,在記憶體中 bss段全部置零。

.rodata(隻讀段):該段儲存着隻讀資料,在程序映象中構造不可寫的段。

  通過在子產品初始化函數中放置一下代碼,我們可以很容易地獲得子產品加載到記憶體中的位址。

……

int bss_var;

static int hello_init(void)

{

printk(KERN_ALERT "Text location .text(Code Segment):%p/n",hello_init);

static int data_var=0;

printk(KERN_ALERT "Data Location .data(Data Segment):%p/n",&data_var);

printk(KERN_ALERT "BSS Location: .bss(BSS Segment):%p/n",&bss_var);

……

}

Module_init(hello_init);

  這裡,通過在子產品的初始化函數中添加一段簡單的程式,使子產品在加載時列印出在核心中的加載位址。.rodata段的位址可以通過執行指令readelf -e hello.ko,取得.rodata在檔案中的偏移量并加上段的align值得出。

  為了使讀者能夠更好地進行子產品的調試,kgdb項目還釋出了一些腳本程式能夠自動探測子產品的插入并自動更新gdb中子產品的符号資訊。這些腳本程式的工作原理與前面解釋的工作過程相似,更多的資訊請閱讀參考資料[4]。

{{分頁}}

2.2.5 硬體斷點

  kgdb提供對硬體調試寄存器的支援。在kgdb中可以設定三種硬體斷點:執行斷點(Execution Breakpoint)、寫斷點(Write Breakpoint)、通路斷點(Access Breakpoint)但不支援I/O通路的斷點。目前,kgdb對硬體斷點的支援是通過宏來實作的,最多可以設定4個硬體斷點,這些宏的用法如下:

 

  在有些情況下,硬體斷點的使用對于核心的調試是非常友善的。有關硬體斷點的定義和具體的使用說明見參考資料[4]

2.3.在VMware中搭建調試環境

  kgdb調試環境需要使用兩台微機分别充當development機和target機,使用VMware後我們隻使用一台計算機就可以順利完成kgdb調試環境的搭建。以windows下的環境為例,建立兩台虛拟機,一台作為開發機,一台作為目标機。

2.3.1虛拟機之間的序列槽連接配接

  虛拟機中的序列槽連接配接可以采用兩種方法。一種是指定虛拟機的序列槽連接配接到實際的COM上,例如開發機連接配接到COM1,目标機連接配接到COM2,然後把兩個序列槽通過序列槽線相連接配接。另一種更為簡便的方法是:在較高一些版本的VMware中都支援把序列槽映射到命名管道,把兩個虛拟機的序列槽映射到同一個命名管道。例如,在兩個虛拟機中都標明同一個命名管道

//./pipe/com_1,指定target機的COM口為server端,并選擇"The other end is a virtual machine"屬性;指定development機的COM口端為client端,同樣指定COM口的"The other end is a virtual machine"屬性。對于IO mode屬性,在target上選中"Yield CPU on poll"複選擇框,development機不選。這樣,可以無需附加任何硬體,利用虛拟機就可以搭建kgdb調試環境。即降低了使用kgdb進行調試的硬體要求,也簡化了建立調試環境的過程。

 

2.3.2 VMware的使用技巧

  VMware虛拟機是比較占用資源的,尤其是象上面那樣在Windows中使用兩台虛拟機。是以,最好為系統配備512M以上的記憶體,每台虛拟機至少配置設定128M的記憶體。這樣的硬體要求,對目前主流配置的PC而言并不是過高的要求。出于系統性能的考慮,在VMware中盡量使用字元界面進行調試工作。同時,Linux系統預設情況下開啟了sshd服務,建議使用SecureCRT登陸到Linux進行操作,這樣可以有較好的使用者使用界面。

2.3.3 在Linux下的虛拟機中使用kgdb

  對于在Linux下面使用VMware虛拟機的情況,筆者沒有做過實際的探索。從原理上而言,隻需要在Linux下隻要建立一台虛拟機作為target機,開發機的工作可以在實際的Linux環境中進行,搭建調試環境的過程與上面所述的過程類似。由于隻需要建立一台虛拟機,是以使用Linux下的虛拟機搭建kgdb調試環境對系統性能的要求較低。(vmware已經推出了Linux下的版本)還可以在development機上配合使用一些其他的調試工具,例如功能更強大的cgdb、圖形界面的DDD調試器等,以友善核心的調試工作。

 

2.4 kgdb的一些特點和不足

  使用kgdb作為核心調試環境最大的不足在于對kgdb硬體環境的要求較高,必須使用兩台計算機分别作為target和development機。盡管使用虛拟機的方法可以隻用一台PC即能搭建調試環境,但是對系統其他方面的性能也提出了一定的要求,同時也增加了搭建調試環境時複雜程度。另外,kgdb核心的編譯、配置也比較複雜,需要一定的技巧,筆者當時做的時候也是費了很多周折。當調試過程結束後時,還需要重新制作所要釋出的核心。使用kgdb并不能進行全程調試,也就是說kgdb并不能用于調試系統一開始的初始化引導過程。

  不過,kgdb是一個不錯的核心調試工具,使用它可以進行對核心的全面調試,甚至可以調試核心的中斷處理程式。如果在一些圖形化的開發工具的幫助下,對核心的調試将更友善。

3. 使用SkyEye建構Linux核心調試環境

  SkyEye是一個開源軟體項目(OPenSource Software),SkyEye項目的目标是在通用的Linux和Windows平台上模拟常見的嵌入式計算機系統。SkyEye實作了一個指令級的硬體模拟平台,可以模拟多種嵌入式開發闆,支援多種CPU指令集。SkyEye 的核心是 GNU 的 gdb 項目,它把gdb和 ARM Simulator很好地結合在了一起。加入ARMulator 的功能之後,它就可以來仿真嵌入式開發闆,在它上面不僅可以調試硬體驅動,還可以調試作業系統。Skyeye項目目前已經在嵌入式系統開發領域得到了很大的推廣。

3.1 SkyEye的安裝和μcLinux核心編譯

3.1.1 SkyEye的安裝

  SkyEye的安裝不是本文要介紹的重點,目前已經有大量的資料對此進行了介紹。有關SkyEye的安裝與使用的内容請查閱參考資料[11]。由于skyeye面目主要用于嵌入式系統領域,是以在skyeye上經常使用的是μcLinux系統,當然使用Linux作為skyeye上運作的系統也是可以的。由于介紹μcLinux 2.6在skyeye上編譯的相關資料并不多,是以下面進行詳細介紹。

{{分頁}}

3.1.2 μcLinux 2.6.x的編譯

  要在SkyEye中調試作業系統核心,首先必須使被調試核心能在SkyEye所模拟的開發闆上正确運作。是以,正确編譯待調試作業系統核心并配置SkyEye是進行核心調試的第一步。下面我們以SkyEye模拟基于Atmel AT91X40的開發闆,并運作μcLinux 2.6為例介紹SkyEye的具體調試方法。

I、安裝交叉編譯環境

  先安裝交叉編譯器。盡管在一些資料中說明使用工具鍊arm-elf-tools-20040427.sh ,但是由于arm-elf-xxx與arm-linux-xxx對宏及連結處理的不同,經驗證明使用arm-elf-xxx工具鍊在連結vmlinux的最後階段将會出錯。是以這裡我們使用的交叉編譯工具鍊是:arm-uclinux-tools-base-gcc3.4.0-20040713.sh,關于該交叉編譯工具鍊的下載下傳位址請參見[6]。注意以下步驟最好用root使用者來執行。

[[email protected] tmp]#chmod +x  arm-uclinux-tools-base-gcc3.4.0-20040713.sh

[[email protected] tmp]#./arm-uclinux-tools-base-gcc3.4.0-20040713.sh

  安裝交叉編譯工具鍊之後,請確定工具鍊安裝路徑存在于系統PATH變量中。

II、制作μcLinux核心

  得到μcLinux釋出包的一個最容易的方法是直接通路uClinux.org站點[7]。該站點釋出的核心版本可能不是最新的,但你能找到一個最新的μcLinux更新檔以及找一個對應的Linux核心版本來制作一個最新的μcLinux核心。這裡,将使用這種方法來制作最新的μcLinux核心。目前(筆者記錄編寫此文章時),所能得到的釋出包的最新版本是uClinux-dist.20041215.tar.gz。

下載下傳uClinux-dist.20041215.tar.gz,檔案的下載下傳位址請參見[7]。

下載下傳linux-2.6.9-hsc0.patch.gz,檔案的下載下傳位址請參見[8]。

下載下傳linux-2.6.9.tar.bz2,檔案的下載下傳位址請參見[9]。

  現在我們得到了整個的linux-2.6.9源代碼,以及所需的核心更新檔。請準備一個有2GB空間的目錄裡來完成以下制作μcLinux核心的過程。

[[email protected] tmp]# tar -jxvf uClinux-dist-20041215.tar.bz2

[[email protected] uClinux-dist]# tar -jxvf  linux-2.6.9.tar.bz2

[[email protected] uClinux-dist]# gzip -dc linux-2.6.9-hsc0.patch.gz | patch -p0

或者使用:

[[email protected] uClinux-dist]# gunzip linux-2.6.9-hsc0.patch.gz

[[email protected] uClinux-dist]patch -p0 < linux-2.6.9-hsc0.patch

  執行以上過程後,将在linux-2.6.9/arch目錄下生成一個更新檔目錄-armnommu。删除原來μcLinux目錄裡的linux-2.6.x(即那個linux-2.6.9-uc0),并将我們打好更新檔的Linux核心目錄更名為linux-2.6.x。

[[email protected] uClinux-dist]# rm -rf linux-2.6.x/

[[email protected] uClinux-dist]# mv linux-2.6.9 linux-2.6.x

III、配置和編譯μcLinux核心

  因為隻是出于調試μcLinux核心的目的,這裡沒有生成uClibc庫檔案及romfs.img檔案。在釋出μcLinux時,已經預置了某些常用嵌入式開發闆的配置檔案,是以這裡直接使用這些配置檔案,過程如下:

[[email protected] uClinux-dist]# cd linux-2.6.x

[[email protected] linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux- atmel_

deconfig

  atmel_deconfig檔案是μcLinux釋出時提供的一個配置檔案,存放于目錄linux-2.6.x /arch/armnommu/configs/中。

[[email protected] linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux-

oldconfig

下面編譯配置好的核心:

[[email protected] linux-2.6.x]# make ARCH=armnommu CROSS_COMPILE=arm-uclinux- v=1

  一般情況下,編譯将順利結束并在Linux-2.6.x/目錄下生成未經壓縮的μcLinux核心檔案vmlinux。需要注意的是為了調試μcLinux核心,需要打開核心編譯的調試選項-g,使編譯後的核心帶有調試資訊。打開編譯選項的方法可以選擇:

  "Kernel debugging->Compile the kernel with debug info"後将自動打開調試選項。也可以直接修改linux-2.6.x目錄下的Makefile檔案,為其打開調試開關。方法如下:。

CFLAGS  += -g

  最容易出現的問題是找不到arm-uclinux-gcc指令的錯誤,主要原因是PATH變量中沒有包含arm-uclinux-gcc指令所在目錄。在arm-linux-gcc的預設安裝情況下,它的安裝目錄是/root/bin/arm-linux-tool/,使用以下指令将路徑加到PATH環境變量中。

Export PATH=$PATH:/root/bin/arm-linux-tool/bin

IV、根檔案系統的制作

  Linux核心在啟動的時的最後操作之一是加載根檔案系統。根檔案系統中存放了嵌入式系統使用的所有應用程式、庫檔案及其他一些需要用到的服務。出于文章篇幅的考慮,這裡不打算介紹根檔案系統的制作方法,讀者可以查閱一些其他的相關資料。值得注意的是,由配置檔案skyeye.conf指定了裝載到核心中的根檔案系統。

3.2 使用SkyEye調試

  編譯完μcLinux核心後,就可以在SkyEye中調試該ELF執行檔案格式的核心了。前面已經說過利用SkyEye調試核心與使用gdb調試運用程式的方法相同。

  需要提醒讀者的是,SkyEye的配置檔案-skyeye.conf記錄了模拟的硬體配置和模拟執行行為。該配置檔案是SkyEye系統中一個及其重要的檔案,很多錯誤和異常情況的發生都和該檔案有關。在安裝配置SkyEye出錯時,請首先檢查該配置檔案然後再進行其他的工作。此時,所有的準備工作已經完成,就可以進行核心的調試工作了。

3.3使用SkyEye調試核心的特點和不足

  在SkyEye中可以進行對Linux系統核心的全程調試。由于SkyEye目前主要支援基于ARM核心的CPU,是以一般而言需要使用交叉編譯工具編譯待調試的Linux系統核心。另外,制作SkyEye中使用的核心編譯、配置過程比較複雜、繁瑣。不過,當調試過程結束後無需重新制作所要釋出的核心。

{{分頁}}

  SkyEye隻是對系統硬體進行了一定程度上的模拟,是以在SkyEye與真實硬體環境相比較而言還是有一定的差距,這對一些與硬體緊密相關的調試可能會有一定的影響,例如驅動程式的調試。不過對于大部分軟體的調試,SkyEye已經提供了精度足夠的模拟了。

  SkyEye的下一個目标是和eclipse結合,有了圖形界面,能為調試和檢視源碼提供一些友善。

4. 使用UML調試Linux核心

  User-mode Linux(UML)簡單說來就是在Linux内運作的Linux。該項目是使Linux核心成為一個運作在 Linux 系統之上單獨的、使用者空間的程序。UML并不是運作在某種新的硬體體系結構之上,而是運作在基于 Linux 系統調用接口所實作的虛拟機。正是由于UML是一個将Linux作為使用者空間程序運作的特性,可以使用UML來進行作業系統核心的調試。有關UML的介紹請查閱參考資料[10]、[12]。

4.1 UML的安裝與調試

  UML的安裝需要一台運作Linux 2.2.15以上,或者2.3.22以上的I386機器。對于2.6.8及其以前版本的UML,采用兩種形式釋出:一種是以RPM包的形式釋出,一種是以源代碼的形式提供UML的安裝。按照UML的說明,以RPM形式提供的安裝包比較陳舊且會有許多問題。以二進制形式釋出的UML包并不包含所需要的調試資訊,這些代碼在釋出時已經做了程度不同的優化。是以,要想利用UML調試Linux系統核心,需要使用最新的UML patch代碼和對應版本的Linux核心編譯、安裝UML。完成UML的更新檔之後,會在arch目錄下産生一個um目錄,主要的UML代碼都放在該目錄下。

  從2.6.9版本之後(包含2.6.9版本的Linux),User-Mode Linux已經随Linux核心源代碼樹一起釋出,它存放于arch/um目錄下。

  編譯好UML的核心之後,直接使用gdb運作已經編譯好的核心即可進行調試。

4.2使用UML調試系統核心的特點和不足

  目前,使用者模式 Linux 虛拟機也存在一定的局限性。由于UML虛拟機是基于Linux系統調用接口的方式實作的虛拟機,是以使用者模式核心不能通路主機系統上的硬體裝置。是以,UML并不适合于調試那些處理實際硬體的驅動程式。不過,如果所編寫的核心程式不是硬體驅動,例如Linux檔案系統、協定棧等情況,使用UML作為調試工具還是一個不錯的選擇。

5. 核心調試配置選項

  為了友善調試和測試代碼,核心提供了許多與核心調試相關的配置選項。這些選項大部分都在核心配置編輯器的核心開發(kernel hacking)菜單項中。在核心配置目錄樹菜單的其他地方也還有一些可配置的調試選項,下面将對他們作一定的介紹。

Page alloc debugging :CONFIG_DEBUG_PAGEALLOC:

  不使用該選項時,釋放的記憶體頁将從核心位址空間中移出。使用該選項後,核心推遲移出記憶體頁的過程,是以能夠發現記憶體洩漏的錯誤。

Debug memory allocations :CONFIG_DEBUG_SLAB:

  該打開該選項時,在核心執行記憶體配置設定之前将執行多種類型檢查,通過這些類型檢查可以發現諸如核心過量配置設定或者未初始化等錯誤。核心将會在每次配置設定記憶體前後時設定一些警戒值,如果這些值發生了變化那麼核心就會知道記憶體已經被操作過并給出明确的提示,進而使各種隐晦的錯誤變得容易被跟蹤。

Spinlock debugging :CONFIG_DEBUG_SPINLOCK:

  打開此選項時,核心将能夠發現spinlock未初始化及各種其他的錯誤,能用于排除一些死鎖引起的錯誤。

Sleep-inside-spinlock checking:CONFIG_DEBUG_SPINLOCK_SLEEP:

  打開該選項時,當spinlock的持有者要睡眠時會執行相應的檢查。實際上即使調用者目前沒有睡眠,而隻是存在睡眠的可能性時也會給出提示。

Compile the kernel with debug info :CONFIG_DEBUG_INFO:

  打開該選項時,編譯出的核心将會包含全部的調試資訊,使用gdb時需要這些調試資訊。

Stack utilization instrumentation :CONFIG_DEBUG_STACK_USAGE:

  該選項用于跟蹤核心棧的溢出錯誤,一個核心棧溢出錯誤的明顯的現象是産生oops錯誤卻沒有列出系統的調用棧資訊。該選項将使核心進行棧溢出檢查,并使核心進行棧使用的統計。

Driver Core verbose debug messages:CONFIG_DEBUG_DRIVER:

  該選項位于"Device drivers-> Generic Driver Options"下,打開該選項使得核心驅動核心産生大量的調試資訊,并将他們記錄到系統日志中。

Verbose SCSI error reporting (kernel size +=12K) :CONFIG_SCSI_CONSTANTS:

  該選項位于"Device drivers/SCSI device support"下。當SCSI裝置出錯時核心将給出詳細的出錯資訊。

Event debugging:CONFIG_INPUT_EVBUG:

  打開該選項時,會将輸入子系統的錯誤及所有事件都輸出到系統日志中。該選項在産生了詳細的輸入報告的同時,也會導緻一定的安全問題。

  以上核心編譯選項需要讀者根據自己所進行的核心程式設計的實際情況,靈活選取。在使用以上介紹的三種源代碼級的核心調試工具時,一般需要選取CONFIG_DEBUG_INFO選項,以使編譯的核心包含調試資訊。

6. 總結

  上面介紹了一些調試Linux核心的方法,特别是詳細介紹了三種源代碼級的核心調試工具,以及搭建這些核心調試環境的方法,讀者可以根據自己的情況從中作出選擇。

  調試工具(例如gdb)的運作都需要作業系統的支援,而此時核心由于一些錯誤的代碼而不能正确執行對系統的管理功能,是以對核心的調試必須采取一些特殊的方法進行。以上介紹的三種源代碼級的調試方法,可以歸納為以下兩種政策:

I、為核心增加調試Stub,利用調試Stub進行遠端調試,這種調試政策需要target及development機器才能完成調試任務。

II、将虛拟機技術與調試工具相結合,使Linux核心在虛拟機中運作進而利用調試器對核心進行調試。這種政策需要制作适合在虛拟機中運作的系統核心。

  由不同的調試政策決定了進行調試時不同的工作原理,同時也形成了各種調試方法不同的軟硬體需求和各自的特點。

  另外,需要說明的是核心調試能力的掌握很大程度上取決于經驗和對整個作業系統的深入了解。對系統核心的全面深入的了解,将能在很大程度上加快對Linux系統核心的開發和調試。

  對系統核心的調試技術和方法絕不止上面介紹所涉及的内容,這裡隻是介紹了一些經常看到和聽到方法。在Linux核心向前發展的同時,核心的調試技術也在不斷的進步。希望以上介紹的一些方法能對讀者開發和學習Linux有所幫助。

參考資料

[1] http://oss.sgi.com/projects/kdb/

[2] http://www.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html

[3] http://www.ibm.com/developerworks/cn/linux/l-kdbug/

[4] http://www.ibm.com/developerworks/cn/linux/l-kprobes.html

[5] http://kgdb.linsyssoft.com/downloads.htm

[6] ftp://166.111.68.183

[8] http://www.uclinux.org/pub/uClinux/dist/

[9] http://opensrc.sec.samsung.com/download/linux-2.6.9-hsc0.patch.gz

[10] http:// www.kernel.org

[11] http://user-mode-linux.sourceforge.net/

[12] http://www.ibm.com/developerworks/cn/linux/l-skyeye/part1/

[13] http://www.ibm.com/developerworks/cn/views/linux/tutorials.jsp?cv_doc_id=84978

參考文獻

[1]Robert Love Linux kernel development機械工業出版社

[2]陳渝 源代碼開發的嵌入式系統軟體分析與實踐 北京航空航天大學出版社

[3]Alessandro Rubini Linux device driver 2se Edition O'Reilly

[4]Jonathan Corbet Linux device driver 3rd Edition O'Reilly

[5]李善平 Linux核心源代碼分析大全 機械工業出版社

作者簡介

  李樹雷,清華大學計算機系碩士研究所學生,主要從事作業系統與中間件的研究。通過[email protected] 可以跟他聯系

  陳渝, 清華大學,通過 [email protected] 可以和他聯系。

繼續閱讀