洪永學(xué), 余紅英, 姜世杰, 林麗蓉
(中北大學(xué)信息與通信工程學(xué)院, 山西太原 030051)
Linux操作系統(tǒng)最大的優(yōu)勢在于其源碼的開放性,人們可以根據(jù)應(yīng)用的需要對其內(nèi)核驅(qū)動進(jìn)行必要的裁剪或修改,但是由于內(nèi)核是整個計算機(jī)軟件系統(tǒng)的基礎(chǔ),其與無操作系統(tǒng)的調(diào)試有著很大的差異。調(diào)試中所獲得的錯誤信息也不同,尤其是涉及到Oops信息的段錯誤。為了能有效地調(diào)試Linux內(nèi)核,人們采用了很多方法,下面介紹兩種常用的Linux內(nèi)核調(diào)試方法。
調(diào)試內(nèi)核、驅(qū)動是最簡單,最常用,也是效率較高的方法,是使用printk函數(shù)打印信息。printk函數(shù)與用戶空間的printf函數(shù)的格式完全相同,它所打印的字符串頭部可以加入"
在內(nèi)核代碼include/linux/kernel.h中,下面幾個宏控制了printk函數(shù)所輸出的信息記錄級別。
#define console_loglevel (console_printk[0])#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
掛接在proc文件系統(tǒng)后,讀取/proc/sys/kernel/printk文件可以得知console_loglevel,fault_message_loglevel,minimum_console_loglevel 和default_console_loglevel這個4個值。
可以修改/proc/sys/kernel/printk文件來改變這4個值,比如:
# echo "1 4 1 7" > /proc/sys/kernel/printk
這使得console_loglevel被改為1,于是所有的printk信息都不會被打印。
在內(nèi)核代碼include/linux/kernel.h中有如下代碼,它們表示0~7這個8個記錄級別的名稱。
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /*warning conditions */
#define KERN_NOTICE "<5>" /*normal but significant condition */
#define KERN_INFO "<6>" / *informational */
#define KERN_DEBUG "<7>" /* debuglevel messages */
在使用printk函數(shù)時,可以這樣的使用記錄級別。
printk(KERN_WARNING"there is a warning here ! ")
在被調(diào)試的內(nèi)核中相應(yīng)的位置加入printk函數(shù),可以根據(jù)串口中打印出的信息獲得其錯誤的位置從而實(shí)施相應(yīng)的解決方法。
在進(jìn)行Linux內(nèi)核驅(qū)動調(diào)試時,經(jīng)常會出現(xiàn)Segmentation fault錯誤信息,其大部分都是NULL指針引用或使用其他不正確的指針數(shù)值。這些錯誤通常會導(dǎo)致一個Oops消息。由于處理器使用的地址都是“虛”地址,而且通過一個復(fù)雜的稱為頁表的結(jié)構(gòu)映射為物理地址。當(dāng)引用一個非法指針時,頁面映射機(jī)制就不能將”虛”地址映射到物理地址,因此處理器向操作系統(tǒng)發(fā)出一個“頁面失效”。如果地址確實(shí)是非法的,內(nèi)核就無法從失效地址上“換頁”;如果此時處理器工作在超級用戶態(tài),系統(tǒng)就會產(chǎn)生一個“Oops”消息。Oops顯示故障時的處理器狀態(tài),模塊CPU寄存器內(nèi)容,頁描述符表的位置,以及其他較為難以理解的信息。利用ksymoopsJ工具可以將這些十六進(jìn)制數(shù)據(jù)解析為內(nèi)核符號,然后再進(jìn)一步判斷錯誤的所在。
Oops這個單詞含義為“驚訝”,當(dāng)內(nèi)核出錯時(比如訪問非法地址)打印出來的信息被稱為Oops信息。 Oops信息包含以下幾部分內(nèi)容:
(1)一段文本描述信息。比如類似”Unable to handle kernel NULL pointer dereference at virtual address 00000000”的信息,他說明了發(fā)生的是哪類錯誤。
(2)Oops信息的序號。比如是第幾次等。這些信息與下面類似,括號內(nèi)的數(shù)據(jù)表示序號。
Internal error: Oops: 806 [#1]
(3)內(nèi)核中加載的模塊名稱,也可能沒有,以下面字樣開頭。
Modules linked in:
(4)發(fā)生錯誤的CPU的序號,對于單處理器系統(tǒng),序號為0,如:
CPU: 0 Not tainted (2.6.22.6 #36)
(5)發(fā)生錯誤時CPU的各個寄存器值。
(6)當(dāng)前進(jìn)程的名字及進(jìn)程ID,比如:
Process swapper (pid: 1, stack limit =0xc0480258)
這并不是說發(fā)生錯誤的是這個進(jìn)程,而是表示發(fā)生錯誤時,當(dāng)前進(jìn)程是它。錯誤可能發(fā)生在內(nèi)核代碼、驅(qū)動程序,也可能就是這個進(jìn)程的錯誤。
(7)棧信息。
(8)?;厮菪畔?,可以從中看出函數(shù)調(diào)用關(guān)系,形式如下:
Backtrace:
[
(9)出錯指令附近的指令機(jī)器碼,比如(出錯指令在小括號內(nèi)):
Code: e24cb004 e24dd010 e59f34e0 e3a07000(e5873000)
從 Oops 信息的 PC寄存器值可知得知崩潰發(fā)生時的函數(shù)、出錯指令。但是錯誤有可能是它的調(diào)用者引入的,所以還要找出函數(shù)的調(diào)用關(guān)系。由于內(nèi)核配置了 CONFIG_FRAME_POINTER,當(dāng)出現(xiàn) Oops 信息時,會打印?;厮菪畔?。如果內(nèi)核沒有配置 CONFIG_FRAME_POINTER,這時可以自己分析棧信息,找到函數(shù)的調(diào)用關(guān)系。
一個程序包含代碼段、數(shù)據(jù)段、BSS 段、堆、棧;其中數(shù)據(jù)段用來中存儲初始值不為 0 的全局?jǐn)?shù)據(jù),BSS 段用來存儲初始值為 0 的全局?jǐn)?shù)據(jù),堆用于動態(tài)內(nèi)存分配,棧用于實(shí)現(xiàn)函數(shù)調(diào)用、存儲局部變量。被調(diào)用函數(shù)在執(zhí)行之前,它會將一些寄存器的值保存在棧中,其中包括返回地址寄存器lr。如果知道了所保存的 lr 寄存的值,那么就可以知道它的調(diào)用者是誰。在棧信息中,一個函數(shù)一個函數(shù)地往上找出所有保存的 lr 值,就可以知道各個調(diào)用函數(shù),這就是?;厮莸脑怼?/p>
故意修改 LCD 驅(qū)動程序 drivers/video/s3c2410fb.c,加入錯誤代碼:在 s3c2410fb_ probe函數(shù)的開頭增加下面兩條代碼:
根據(jù) pc 寄存器值找到第一個函數(shù),確定它的棧大小,確定調(diào)用函數(shù)。
從 Oops 信息 :
可知 pc 值為 c0018f78,使用它在內(nèi)核反匯編程序 vmlinux.dis 中可以知道它位于 s3c2410fb_probe 函數(shù)內(nèi)。
lr存放的是函數(shù)返回值地址,為c01d3f88,根據(jù)這個地址,搜索內(nèi)核反匯編程序 vmlinux.dis ,可知它位于:
也就是說,函數(shù)s3c2410fb_probe() 被platform_drv_probe()調(diào)用。再看platform_drv_probe()的反匯編代碼,其中c01d3f70: e92d4010 push {r4, lr} ,棧中存放的是 r4, lr 對應(yīng)(查看上文中的棧的信息) ,其中,lr對應(yīng)的值為c01d2e7c,用此值檢索vmlinux.dis,位于
可知,platform_drv_probe()被driver_probe_device()調(diào)用,再用同樣的方法就可以找出所有函數(shù)調(diào)用關(guān)系。 從而就能找到導(dǎo)致Segmentation fault錯誤的語句。
在Linux內(nèi)核調(diào)試技術(shù)中,由于printk函數(shù)調(diào)試的使用方法簡單,分析問題效率較高,因此內(nèi)核打印函數(shù)printk是眾多開發(fā)者所最喜愛的一種調(diào)試技術(shù)。但在驅(qū)動開發(fā)的情況下出現(xiàn)的錯誤幾乎都會涉及到段錯誤,所以掌握通過Oops信息使用?;厮菁夹g(shù)在Linux嵌入式驅(qū)動開發(fā)中變得不可或缺的一項(xiàng)調(diào)試技能。
[1]毛德操,胡希明.LINUX內(nèi)核代碼情景分析[M].杭州:浙江大學(xué)出版社,2001.
[2]Rubimi A.Linux設(shè)備驅(qū)動程序[M].聊鴻斌譯.北京:中國電力出版社,1999.
[3]宋寶華.Linux設(shè)備驅(qū)動開發(fā)詳解[M].北京:人民郵電出版社,2010.
[4]何紹然,史海濱,吳國成,宋寶華.精通linux設(shè)備驅(qū)動程序開發(fā)[M].北京:人民郵電出版社,2010.
[5]李紅衛(wèi),李翠萍.kgdb調(diào)試Linux內(nèi)核的剖析與改進(jìn)[J].微型機(jī)與應(yīng)用,2004(10):7-10.
[6]張磊,王學(xué)慧.Linux內(nèi)核調(diào)試技術(shù)[J].計算機(jī)工程,2003,29(10):8l-83.
[7]孫悅紅.編譯原理及實(shí)現(xiàn)[M].北京:清華大學(xué)出版社,2005:14-17.
[8]李善平,劉文峰.Linux內(nèi)核2.4版源代碼分析大全[M].北京:機(jī)械工業(yè)出版社,2002.