Linux 系統內核的調試

2020-12-17 電子產品世界

  本文將首先介紹 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、內核的配置與編譯

  [root@lisl tmp]# tar -jxvf linux-2.6.7.tar.bz2
  [root@lisl tmp]#tar -jxvf linux-2.6.7-kgdb-2.2.tar.tar
  [root@lisl 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文件是選擇乙太網口作為調試的連接埠時需要運用的補丁。 
  應用補丁的命令如下所示:

    [root@lisl tmp]#patch -p1 <../linux-2.6.7-kgdb-2.2/core-lite.patch
 
  如果內核正確,那麼應用補丁時應該不會出現任何問題(不會產生*.rej文件)。為Linux內核添加了補丁之後,需要進行內核的配置。內核的配置可以按照你的習慣選擇配置Linux內核的任意一種方式。

  [root@lisl 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
   [root@lisl 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)。

    [root@lisl tmp]#scp arch/i386/boot/bzImage root@192.168.6.13:/boot/vmlinuz-2.6.7-kgdb
    [root@lisl tmp]#scp System.map root@192.168.6.13:/boot/System.map-2.6.7-kgdb
 
  如果系統啟動使所需要的某些設備驅動沒有編譯進內核的情況下,那麼還需要執行如下操作:

    [root@lisl tmp]#mkinitrd /boot/initrd-2.6.7-kgdb 2.6.7
    [root@lisl tmp]#scp initrd-2.6.7-kgdb root@192.168.6.13:/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目錄下,此時無需更改內核的啟動配置文件,直接使用命令:

       [root@lisl 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 kgdboe=@192.168.
    5.13/,@192.168. 6.13/
 
  其他的過程與使用串口作為連接埠時的設置過程相同。

  注意:儘管可以使用乙太網口作為kgdb的調試埠,使用串口作為連接埠更加簡單易行,kgdb項目組推薦使用串口作為調試埠。

2.2.4 模塊的調試方法

  內核可加載模塊的調試具有其特殊性。由於內核模塊中各段的地址是在模塊加載進內核的時候才最終確定的,所以develop機的gdb無法得到各種符號地址信息。所以,使用kgdb調試模塊所需要解決的一個問題是,需要通過某種方法獲得可加載模塊的最終加載地址信息,並把這些信息加入到gdb環境中。

I、在Linux 2.4內核中的內核模塊調試方法

  在Linux2.4.x內核中,可以使用insmod -m命令輸出模塊的加載信息,例如:

       [root@lisl 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用戶來執行。

[root@lisl tmp]#chmod +x  arm-uclinux-tools-base-gcc3.4.0-20040713.sh
[root@lisl 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內核的過程。

[root@lisl tmp]# tar -jxvf uClinux-dist-20041215.tar.bz2
[root@lisl uClinux-dist]# tar -jxvf  linux-2.6.9.tar.bz2
[root@lisl uClinux-dist]# gzip -dc linux-2.6.9-hsc0.patch.gz | patch -p0
 
或者使用:
[root@lisl uClinux-dist]# gunzip linux-2.6.9-hsc0.patch.gz
[root@lisl 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。

[root@lisl uClinux-dist]# rm -rf linux-2.6.x/
[root@lisl uClinux-dist]# mv linux-2.6.9 linux-2.6.x
 
III、配置和編譯μcLinux內核

  因為只是出於調試μcLinux內核的目的,這裡沒有生成uClibc庫文件及romfs.img文件。在發布μcLinux時,已經預置了某些常用嵌入式開發板的配置文件,因此這裡直接使用這些配置文件,過程如下:

[root@lisl uClinux-dist]# cd linux-2.6.x
[root@lisl linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux- atmel_
deconfig
 
  atmel_deconfig文件是μcLinux發布時提供的一個配置文件,存放於目錄linux-2.6.x /arch/armnommu/configs/中。

[root@lisl linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux-
oldconfig
 
下面編譯配置好的內核:

[root@lisl 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內核原始碼分析大全 機械工業出版社

作者簡介

  李樹雷,清華大學計算機系碩士研究生,主要從事作業系統與中間件的研究。通過lisl03@mails.tsinghua.edu.cn 可以跟他聯繫

  陳渝, 清華大學,通過 yuchen@tsinghua.edu.cn 可以和他聯繫。
 

相關焦點

  • Linux內核常用的動態調試手段
    linux內核中幾種常用的動態調試手段,也都是我常用的,都是在生產環境中直接使用,不需要藉助工具,依照我的經驗,去客戶生產環境中解決問題,很多都不會預裝perf、BPF工具,有的即使有perf這樣的工具,也因為工具性能影響客戶幾千億美金的正常業務,也會不讓你使用。
  • Linux 內核的測試和調試(6)
    寫好代碼後,編譯它。把 make 過程產生的輸出保存到文檔中,查看新代碼有沒有警告信息。找到所有的警告信息,處理掉。當你的代碼編譯過程沒有任何不正常的輸出,安裝這個內核,然後啟動測試。如果啟動正常,查看 dmesg 裡面有沒於錯誤,與老內核生成的 dmesg 日誌做個比較。運行一些壓力測試,請參考我們以前講過的測試內容。如果這個補丁用於修復某個 bug,請確保真的已經修復了。
  • Linux 內核學習:環境搭建和內核編譯
    在開始我們的linux內核學習之前。首先需要搭建我們的工作學習環境,即安裝linux系統。關於linux系統構建本身的學問已經複雜到可以成為一個獨立的學科了,畢竟這不是我們學習的內容,所以我選擇最簡單的方法:選擇一個linux發行版進行安裝。
  • linux-3.18內核系統調用
  • ARM Linux內核啟動要求
    arm.linux.org.uk/developer/booting.php本文引用地址:http://www.eepw.com.cn/article/201611/316973.htmAuthor: Russell KingInitial date: May 18, 2002Revision:1 - 17 September 20042
  • 使用Kdump檢查Linux內核崩潰
    Crash note Crash notes 是每個 CPU 中用於在系統崩潰的情況下存儲 CPU 狀態的區域;它有關於當前 PID 和 CPU 寄存器的信息。 vmcoreinfo 該 note 段具有各種內核調試信息,如結構體大小、符號值、頁面大小等。這些值由捕獲內核解析並嵌入到 /proc/vmcore 中。
  • 淺談分析Arm linux 內核移植及系統初始化的過程二
    );//setup processor and machine and destinate some pointers for do_initcalls() s5、淺談分析Arm linux 內核移植及系統初始化的過程 諮詢QQ:313807838// for example init_machine
  • Linux 內核 TCP MSS 機制詳細分析
    所以本文將通過Linux內核源碼對TCP的MSS機制進行詳細分析。作業系統版本:Ubuntu 18.04 內核版本:4.15.0-20-generic地址:192.168.11.112內核源碼:$ sudo apt install linux-source-4.15.0$ ls /usr/src/linux-source-4.15.0.tar.bz2
  • Linux 內核如何處理中斷|Linux 中國
    接下來會調用在內核啟動時設備驅動註冊的對應的中斷服務程序(ISR)。 軟體中斷 當你在播放一個視頻時,音頻和視頻是同步播放是相當重要的,這樣音樂的速度才不會變化。這是由軟體中斷實現的,由精確的計時器系統(稱為jiffies)重複發起的。這個計時器會使得你的音樂播放器同步。
  • Ubuntu中升級Linux內核
    2.linux-headers-4.2.0-xxx-generic_xxx_i386/amd64.deb  3.linux-image-4.2.0-xxx-generic_xxx_i386/amd64.deb  安裝內核後,在終端((Ctrl+Alt+T))運行sudo update-grub命令來更新grub boot-loader。
  • 用Visual Studio調試Linux程序
    當然如果你說VS2015及以上版本自帶的linux調試插件,那就算了。這些自帶的插件調試一個有簡單的main函數程序還湊合,稍微複雜點的程序,根本無法編譯調試。而本文介紹的主角是VS的另外一款插件Visual GDB,讓我們歡迎主角登場,下面是正文。
  • Linux內核概述
    μC/OS-IIμC/OS-II是在μC-OS的基礎上發展起來的,是美國嵌入式系統專家 Jean J.Labrosse 用 C 語言編寫的一個結構小巧、支持搶佔式的多任務實時內核。μClinuxμClinux 是一種優秀的嵌入式 Linux 版本,其全稱為 micro-control Linux,從字面意思看是指微控制 Linux。
  • 嵌入式Linux NFS 根文件系統的構建及研究
    摘要:在嵌入式Linux系統開發過程中,根文件系統是構建嵌入式Linux系統的重要組成部分。為了方便和簡化嵌入式Linux開發過程中的調試過程,主要研究了如何使用Busybox構建出基本的嵌入式Linux根文件系統,包括Busybox的配置、編譯和安裝。
  • 「正點原子Linux連載」第三十七章Linux內核移植
    >Linux內核啟動以後是需要根文件系統的,根文件系統存在哪裡是由uboot的bootargs環境變量指定,bootargs會傳遞給Linux內核作為命令行參數。這個問題是很常見的,我們在實際的工作中開發一個產品,這個產品的第一版硬體出來以後我們是沒有對應的根文件系統可用的,必須要自己做根文件系統。在構建出對應的根文件系統之前Linux內核是沒有根文件系統可用的,此時Linux內核啟動以後會出現什麼問題呢?
  • linux內核移植-移植2.6.35.4內核到s3c2440
    本來是想移植最新的內核2.6.39但是總是在編譯快完成的時候報錯,有人說是新的內核對arm平臺的支持不好,所以就降低了一下版本,這裡移植2.6.35.4內核一、準備工作1、下載 解壓內核從官網上下載linux-2.6.35的內核, ftp://ftp.kernel.org/pub/linux/kernel/v2.6/ ,文件不大,約85M。
  • Linux內核編譯初體驗
    下載內核在ftp://ftp.kernel.org/pub/linux/kernel/下載原版內核本文引用地址:http://www.eepw.com.cn/article/201611/319326.htm此處使用linux-2.6.22.6.tar.bz22.
  • 改善Linux內核實時性方法的研究與實現
    但由於其最初的設計目標為通用分時作業系統,對於實時系統而言,Linux仍然存在核心不可搶佔、關中斷、時鐘粒度粗糙等缺陷。為了使其應用於嵌入式系統,實時控制等領域,越來越多的廠家和研究機構熱衷於改善其實時性,構建基於Linux的實時作業系統。
  • linux內核啟動流程
    Linux內核啟動及文件系統加載過程   當u-boot開始執行bootcmd命令,就進入Linux內核啟動階段,與u-boot類似,普通Linux內核的啟動過程也可以分為兩個階段,但針對壓縮了的內核如uImage就要包括內核自解壓過程了。本文以linux-2.6.37版源碼為例分三個階段來描述內核啟動全過程。
  • linux配置、編譯內核實用工具
    同時include/linux/autoconf.h依照.config的內容生成。split-include根據include/linux/autoconf.h在include/config/下建立相關的目錄和.h文件。
  • 嵌入式Linux內核啟動主要分為這三個階段
    【IT168 資訊】嵌入式linux內核的啟動全過程主要分為三個階段。第一階段為內核自解壓過程,第二階段主要工作是設置ARM處理器工作模式、使能MMU、設置一級頁表等,而第三階段則主要為C代碼,包括內核初始化的全部工作,下面是詳細介紹。