歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> Linux內核調試方法總結

Linux內核調試方法總結

日期:2017/3/1 11:51:51   编辑:Linux內核

內核開發比用戶空間開發更難的一個因素就是內核調試艱難。內核錯誤往往會導致系統宕機,很難保留出錯時的現場。調試內核的關鍵在於你的對內核的深刻理解。

一 調試前的准備

在調試一個bug之前,我們所要做的准備工作有:

有一個被確認的bug。

包含這個bug的內核版本號,需要分析出這個bug在哪一個版本被引入,這個對於解決問題有極大的幫助。可以采用二分查找法來逐步鎖定bug引入版本號。

對內核代碼理解越深刻越好,同時還需要一點點運氣。

該bug可以復現。如果能夠找到復現規律,那麼離找到問題的原因就不遠了。

最小化系統。把可能產生bug的因素逐一排除掉。

二 內核中的bug

內核中的bug也是多種多樣的。它們的產生有無數的原因,同時表象也變化多端。從隱藏在源代碼中的錯誤到展現在目擊者面前的bug,其發作往往是一系列連鎖反應的事件才可能出發的。雖然內核調試有一定的困難,但是通過你的努力和理解,說不定你會喜歡上這樣的挑戰。

三 內核調試配置選項

學習編寫驅動程序要構建安裝自己的內核(標准主線內核)。最重要的原因之一是:內核開發者已經建立了多項用於調試的功能。但是由於這些功能會造成額外的輸出,並導致能下降,因此發行版廠商通常會禁止發行版內核中的調試功能。

1 內核配置

為了實現內核調試,在內核配置上增加了幾項:

Kernelhacking--->

[*]MagicSysRqkey

[*]Kerneldebugging

[*]Debugslabmemoryallocations

[*]Spinlockandrw-lockdebugging:basicchecks

[*]Spinlockdebugging:sleep-inside-spinlockchecking

[*]Compilethekernelwithdebuginfo

DeviceDrivers--->

GenericDriverOptions--->

[*]DriverCoreverbosedebugmessages

Generalsetup--->

[*]Configurestandardkernelfeatures(forsmallsystems)--->

[*]Loadallsymbolsfordebugging/ksymoops

啟用選項例如:

slablayerdebugging(slab層調試選項)

high-memorydebugging(高端內存調試選項)

I/Omappingdebugging(I/O映射調試選項)

spin-lockdebugging(自旋鎖調試選項)

stack-overflowchecking(棧溢出檢查選項)

sleep-inside-spinlockchecking(自旋鎖內睡眠選項)

2 調試原子操作

從內核2.5開發,為了檢查各類由原子操作引發的問題,內核提供了極佳的工具。

內核提供了一個原子操作計數器,它可以配置成,一旦在原子操作過程中,進城進入睡眠或者做了一些可能引起睡眠的操作,就打印警告信息並提供追蹤線索。

所以,包括在使用鎖的時候調用schedule(),正使用鎖的時候以阻塞方式請求分配內存等,各種潛在的bug都能夠被探測到。

下面這些選項可以最大限度地利用該特性:

CONFIG_PREEMPT=y

CONFIG_DEBUG_KERNEL=y

CONFIG_KLLSYMS=y

CONFIG_SPINLOCK_SLEEP=y

四 引發bug並打印信息

1 BUG()和BUG_ON()

一些內核調用可以用來方便標記bug,提供斷言並輸出信息。最常用的兩個是BUG()和BUG_ON()。

定義在中:

#ifndefHAVE_ARCH_BUG

#defineBUG()do{

printk("BUG:failureat%s:%d/%s()!",__FILE__,__LINE__,__FUNCTION__);

panic("BUG!");/*引發更嚴重的錯誤,不但打印錯誤消息,而且整個系統業會掛起*/

}while(0)

#endif

#ifndefHAVE_ARCH_BUG_ON

#defineBUG_ON(condition)do{if(unlikely(condition))BUG();}while(0)

#endif

當調用這兩個宏的時候,它們會引發OOPS,導致棧的回溯和錯誤消息的打印。

※ 可以把這兩個調用當作斷言使用,如:BUG_ON(bad_thing);

2 dump_stack()

有些時候,只需要在終端上打印一下棧的回溯信息來幫助你調試。這時可以使用dump_stack()。這個函數只在終端上打印寄存器上下文和函數的跟蹤線索。

if(!debug_check){

printk(KERN_DEBUG“providesomeinformation…/n”);

dump_stack();

}

五 printk()

內核提供的格式化打印函數。

1 printk函數的健壯性

健壯性是printk最容易被接受的一個特質,幾乎在任何地方,任何時候內核都可以調用它(中斷上下文、進程上下文、持有鎖時、多處理器處理時等)。

2 printk函數脆弱之處

在系統啟動過程中,終端初始化之前,在某些地方是不能調用的。如果真的需要調試系統啟動過程最開始的地方,有以下方法可以使用:

使用串口調試,將調試信息輸出到其他終端設備。

使用early_printk(),該函數在系統啟動初期就有打印能力。但它只支持部分硬件體系。

3 LOG等級

printk和printf一個主要的區別就是前者可以指定一個LOG等級。內核根據這個等級來判斷是否在終端上打印消息。內核把比指定等級高的所有消息顯示在終端。

可以使用下面的方式指定一個LOG級別:

printk(KERN_CRIT “Hello, world!\n”);

注意,第一個參數並不一個真正的參數,因為其中沒有用於分隔級別(KERN_CRIT)和格式字符的逗號(,)。KERN_CRIT本身只是一個普通的字符串(事實上,它表示的是字符串 "<2>";表 1 列出了完整的日志級別清單)。作為預處理程序的一部分,C 會自動地使用一個名為 字符串串聯 的功能將這兩個字符串組合在一起。組合的結果是將日志級別和用戶指定的格式字符串包含在一個字符串中。

內核使用這個指定LOG級別與當前終端LOG等級console_loglevel來決定是不是向終端打印。

下面是可使用的LOG等級:

#defineKERN_EMERG"<0>"/*systemisunusable*/

#defineKERN_ALERT"<1>"/*actionmustbetakenimmediately*/

#defineKERN_CRIT"<2>"/*criticalconditions*/

#defineKERN_ERR"<3>"/*errorconditions*/

#defineKERN_WARNING"<4>"/*warningconditions*/

#defineKERN_NOTICE"<5>"/*normalbutsignificantcondition*/

#defineKERN_INFO"<6>"/*informational*/

#defineKERN_DEBUG"<7>"/*debug-levelmessages*/

#defineKERN_DEFAULT""/*Usethedefaultkernelloglevel*/

注意,如果調用者未將日志級別提供給 printk,那麼系統就會使用默認值 KERN_WARNING "<4>"(表示只有KERN_WARNING 級別以上的日志消息會被記錄)。由於默認值存在變化,所以在使用時最好指定LOG級別。有LOG級別的一個好處就是我們可以選擇性的輸出LOG。比如平時我們只需要打印KERN_WARNING級別以上的關鍵性LOG,但是調試的時候,我們可以選擇打印KERN_DEBUG等以上的詳細LOG。而這些都不需要我們修改代碼,只需要通過命令修改默認日志輸出級別:

mtj@ubuntu:~$cat/proc/sys/kernel/printk

4417

mtj@ubuntu:~$cat/proc/sys/kernel/printk_delay

0

mtj@ubuntu:~$cat/proc/sys/kernel/printk_ratelimit

5

mtj@ubuntu:~$cat/proc/sys/kernel/printk_ratelimit_burst

10

第一項定義了 printk API 當前使用的日志級別。這些日志級別表示了控制台的日志級別、默認消息日志級別、最小控制台日志級別和默認控制台日志級別。printk_delay 值表示的是 printk 消息之間的延遲毫秒數(用於提高某些場景的可讀性)。注意,這裡它的值為 0,而它是不可以通過 /proc 設置的。printk_ratelimit 定義了消息之間允許的最小時間間隔(當前定義為每 5 秒內的某個內核消息數)。消息數量是由 printk_ratelimit_burst 定義的(當前定義為 10)。如果您擁有一個非正式內核而又使用有帶寬限制的控制台設備(如通過串口), 那麼這非常有用。注意,在內核中,速度限制是由調用者控制的,而不是在printk 中實現的。如果一個 printk 用戶要求進行速度限制,那麼該用戶就需要調用printk_ratelimit 函數。

4 記錄緩沖區

內核消息都被保存在一個LOG_BUF_LEN大小的環形隊列中。

關於LOG_BUF_LEN定義:

#define__LOG_BUF_LEN(1<

※ 變量CONFIG_LOG_BUF_SHIFT在內核編譯時由配置文件定義,對於i386平台,其值定義如下(在linux26/arch/i386/defconfig中):

CONFIG_LOG_BUF_SHIFT=18

記錄緩沖區操作:

① 消息被讀出到用戶空間時,此消息就會從環形隊列中刪除。

② 當消息緩沖區滿時,如果再有printk()調用時,新消息將覆蓋隊列中的老消息。

③ 在讀寫環形隊列時,同步問題很容易得到解決。

※ 這個紀錄緩沖區之所以稱為環形,是因為它的讀寫都是按照環形隊列的方式進行操作的。

Copyright © Linux教程網 All Rights Reserved