1. 程式人生 > >《Linux核心設計與實現》讀書筆記(十八)- 核心除錯

《Linux核心設計與實現》讀書筆記(十八)- 核心除錯

核心除錯的難點在於它不能像使用者態程式除錯那樣打斷點,隨時暫停檢視各個變數的狀態。

也不能像使用者態程式那樣崩潰後迅速的重啟,恢復初始狀態。

使用者態程式和核心互動,使用者態程式的各種狀態,錯誤等可以由核心來捕獲並顯示。

而核心是直接和硬體互動的,核心出錯之後整個系統就無法正常運行了,所以要想熟練的進行核心除錯,

首先要熟悉核心已經給我們提供的工具,然後實實在在的去做一些核心功能的開發,在開發的過程中不斷熟悉核心程式碼,增加核心除錯的經驗。

主要內容:

  • 核心除錯的難點
  • 核心除錯的工具和方法
  • 總結

1. 核心除錯的難點

核心除錯的難點大致有以下幾個:

  1. 重現bug困難 - 如果能夠重現一個bug, 相當於成功了一半. (特別是有些bug和硬體相關, 執行幾百萬次之後才有一次錯誤)
  2. 除錯風險比較大 - 稍有不慎, 即造成系統崩潰
  3. 定位bug的初始版本困難 - 核心版本更新很快, 很難確定bug是在那個版本開始出現的

2. 核心除錯的工具和方法

核心除錯雖然困難, 但同時也極具挑戰性, 如果能夠解決一個困擾大家多時的核心bug, 那將會給自己帶來極大的成就感. :)

而且, 隨著核心的不斷髮展, 核心除錯的手段和方法也在不斷進步, 下面是書中提到的一些常用的除錯手段.

2.1 輸出 LOG

輸出LOG不光是核心除錯, 即使是在使用者態程式的除錯中, 也是經常使用的一個除錯手段.

通過在可疑的程式碼周圍加上一些LOG輸出, 可以準確的瞭解bug發生前後的一些重要資訊.

2.1.1 LOG等級

linux核心中輸出LOG的函式是 printk (語法和printf幾乎雷同, 唯一的區別是printk可以指定日誌級別)

printk之所以好用, 就在與它隨時都可以被呼叫, 沒有任何限制條件.

printk的輸出日誌級別如下:

等級

描述

KERN_EMERG 一個緊急情況
KERN_ALERT 一個需要立即被注意到的錯誤
KERN_CRIT 一個臨界情況
KERN_ERR 一個錯誤
KERN_WARNING 一個警告
KERN_NOTICE 一個普通的, 不過也有可能需要注意的情況
KERN_INFO 一條非正式的訊息
KERN_DEBUG 一條除錯資訊--一般是冗餘資訊

輸出示例:

printk(KERN_WARNING "This is a warning!\n");
printk(KERN_DEBUG "This is a debug notice!\n");
2.1.2 LOG記錄

標準linux系統上, printk 輸出log之後, 由使用者空間的守護程序klogd從緩衝區中讀取核心訊息, 然後再通過syslogd守護程序將它們儲存在系統日誌檔案中.

syslogd 將接受到的所有核心訊息新增到一個檔案中, 該檔案預設為: /var/log/dmesg (系統Centos6.4 x86_64)

PS. 上篇部落格中的核心模組的輸出LOG, 都可以在 /var/log/dmesg 中看到

2.2 oops

oopss是個擬聲詞, 類似 "哎喲" 的意思. 它是核心通知使用者有不幸發生的最常用方式.

觸發一個oops很簡單, 其實只要在上篇部落格中的那些核心模組示例中隨便找一個, 裡面加上一段給未初始化的指標賦值的程式碼, 就能觸發一個oops

oops中包含錯誤發生時的一些重要資訊(比如, 暫存器上下文和回溯線索), 對除錯bug很有幫助!

除錯核心時, 還可以開啟核心編譯引數中的各種和核心除錯相關的選項, 那樣還可以給我們提供核心崩潰時的一些額外資訊.

2.3 主動觸發bug

除錯中有時將某些情況下標記為bug, 執行到這些情況時, 提供斷言並輸出資訊.

BUG 和 BUG_ON 就是2個可以主動觸發oops的核心呼叫.

在不應該被執行到的地方使用 BUG 或者 BUG_ON 來捕獲.

比如:

if (bad_thing)
    BUG();
// 或者
BUG_ON(bad_thing);

如果想要觸發更為嚴重的錯誤, 可以使用 panic() 函式

比如:

if (terrible_thing)
    panic("terrible thing is %ld\n", terrible_thing);

此外, 還有dump_stack 函式可以列印暫存器上下文和回溯資訊.

比如:

if (!debug_check) {
    printk(KERN_DEBUG "provide some information...\n");
    dump_statck();
}

2.4 神奇的系統請求鍵

這個系統請求鍵之所以神奇, 在於它可以在一個快掛了的系統上輸出一些有用的資訊.

這個按鍵一般就是標準鍵盤上的 [SysRq] 鍵 (就在 F12 鍵右邊, 其實就是windows中截整個螢幕的按鍵)

單獨按那個鍵相當於截圖, 按住 ALT + [SysRq] = [SysRq]的功能

啟用這個鍵的功能有2個方法:

  • 開啟核心編譯選項 : CONFIG_MAGIC_SYSRQ
  • 動態啟用: echo 1 > /proc/sys/kernel/sysrq

支援 SysRq 的命令如下: (注意要在控制檯介面下使用這個鍵, 比如通過 ALT+CTRL+F2 進入一個控制檯介面)

主要命令

描述

SysRq-b 重新啟動機器
SysRq-e 向init以外的所有程序傳送SIGTERM訊號
SysRq-h 在控制檯顯示SysRq的幫助資訊
SysRq-i 向init以外的所有程序傳送SIGKILL訊號
SysRq-k 安全訪問鍵:殺死這個控制檯上的所有程式
SysRq-l 向包括init的所有程序傳送SIGKILL訊號
SysRq-m 把記憶體資訊輸出到控制檯
SysRq-o 關閉機器
SysRq-p 把暫存器資訊輸出到控制檯
SysRq-r 關閉鍵盤原始模式
SysRq-s 把所有已安裝檔案系統都重新整理到磁碟
SysRq-t 把任務資訊輸出到控制檯
SysRq-u 解除安裝所有已載入檔案系統

2.5 核心偵錯程式 gdb和kgdb

linux核心的偵錯程式可以使用 gdb或者kgdb, 配置比較麻煩, 準備實際用除錯的時候再去試試效果如何..

2.6 探測系統

下面一些方法是在修改核心後, 用來試探核心反應的小技巧.

2.6.1 用UID控制核心執行

比如在核心中加入了新的特性, 為了測試特性, 可以用UID來控制核心是否執行新特性.

if (current->uid != 7777) {
    /* 原先的程式碼 */
} else {
   /* 新的特性 */
}
2.6.2 用條件變數控制核心執行

也可以設定一些條件變數來控制核心是否執行某段程式碼.

條件變數可以像上篇部落格中那樣, 設定在 sys 檔案系統的某個檔案中. 當檔案中的值變化時, 通知核心執行相應的程式碼.

2.6.3 使用統計量觀察核心執行某段程式碼的頻率

實現思路就是在核心中的設定一個全域性變數, 比如 my_count, 當核心執行到某段程式碼時, 給 my_count + 1 就行.

同時還要將 my_count 打印出來(可以用printk), 便於隨時檢視它的值.

2.6.4 控制核心執行某段程式碼的頻率

有時侯, 我們需要在核心發生錯誤時列印錯誤相關的資訊, 如果這個錯誤不會導致核心崩潰, 並且這個錯誤每秒會發生幾百次甚至更多.

那麼, 用printk輸出的資訊會非常多, 給系統造成額外的負擔.

這時, 我們就需要想辦法控制錯誤輸出的頻率, 有2種方法:

方法1: 隔一段時間才輸出一次錯誤

static unsigned long prev_jiffy = jiffies;  /* 頻率限制 */

if (time_after(jiffies, prev_jiffy + 2*HZ)) {  /* 輸出間隔至少 2HZ */
    prev_jiffy = jiffies;
    printk(KERN_ERR "錯誤資訊....\n");
}

方法2: 輸出 N 次之後, 不再輸出(N是正整數)

static unsigned long limit = 0;

if (limit < 5) { /* 輸出5次錯誤資訊後就不再輸出 */
    limit++;
    printk(KERN_ERR "錯誤資訊....\n");
}

2.7 二分法查詢bug發生的最初核心版本

在核心發生了bug之後, 如果能夠知道是bug從哪個核心版本開始出現的, 那對修正這個bug會有很大的幫助.

由於核心程式碼非常龐大, 即使用二分查詢法, 手工去找哪個版本開始出現bug的話, 仍然是非常耗時和繁瑣的.

好在 Git 給我們提供了一個非常有用的二分搜尋機制.

git bisect start  # 開始二分搜尋
git bisect bad <bad_revision> # 指定一個bug出現的核心版本號
git bisect good <good_revision> # 指定一個沒有bug的核心版本號, 此時git會檢測2個版本直接的隱患

# 根據結果再次設定 bad 和 good 的版本號, 縮小Git檢索範圍, 直至找到可疑之處為止.

2.8 社群

當你在除錯bug時用盡了一切手段仍然無濟於事時, 可以考慮求助linux社群, 求助時注意一定要描述清楚bug的狀況.

(可以參考一下別人彙報bug的格式)

3. 總結

linux核心除錯必須要依靠大量的實踐來掌握, 僅僅靠上面介紹的一些技巧還遠遠不夠, 只有實實在在的去閱讀核心程式碼, 實實在在的去修正一個個bug, 才算真正掌握核心, 真正瞭解核心.

多看看之前linux核心bug的修正案例, 也是個不錯的積累經驗的方法.

PS.

對於初學者來說, 在真機上做核心開發動輒導致機器崩潰(panic), 非常麻煩. 現在的虛擬機器這麼強大, 建議都在虛擬機器上測試linux核心修改的效果.

我之前的關於<<Linux核心設計與實現>>筆記的部落格中的程式碼都是在虛擬機器上執行測試的.