調試內核艱難且風險高,關鍵在於對內核的深入理解。linux
須要的是:git
內核中的bug不是很清晰,調試成功的關鍵點在於精確的重現一個bug。算法
內核bug多種多樣,表象也變化無窮:sass
bug展示出來每每是經歷一系列的連鎖反應,內核調試沒有特別難,但也有一些獨特的問題:如定時限制和競爭條件,這些是容許多個線程在內核中同時運行產生的結果。安全
內核提供的打印函數printk()和C庫提供的printf()功能幾乎相同。app
printk()特質:健壯性:任什麼時候候任何地方都能調用。編輯器
日誌等級ide
printk()指定一個日誌級別,內核把級別比某個特定值低的消息顯示在終端上。函數
有7個等級:KERN_EMERG~KERN_DEBUG(宏),對應<0>~<7> 默認是DEFAULT_MESSAGE_LOGLEVEL(如今是KERN_WARNING,可能改)
記錄緩衝區工具
內核消息被保存在一個LOG_BUF_LEN大小的環形隊列中,大小可在編譯時設置CONFIG_LOG_BUF_SHIFT進行調整。 單處理器系統上默認值是16KB,超過最大值時,新消息將覆蓋老消息。
環形隊列好處:
缺點:可能會丟失信息
syslogd和klogd是用戶空間守護進程。
klogd從記錄緩衝區獲取內核信息(會先堵塞,到有新信息可讀),傳給syslogd,syslogd把它們保存在系統日誌文件中(默認是/var/log/messages,可經過/etc/syslog.conf配置文件從新指定)。
讀取兩種方式:
啓動klogd時,可經過-c改變終端的記錄等級。
oops是內核告知用戶有不幸發生的最經常使用的方式,oops包括:
發送完oops後,內核會處於一種不穩定狀態,若是oops在idle進程(pid=0)或init進程(pid=1)時發生,系統將陷入混亂,如果其餘進程,內核會殺死該進程並嘗試繼續執行。
ksymoops命令:將回溯線索中的地址轉化爲有意義的符號名稱,必須提供編譯內核時產生的System.map,ksymoops會自行解析,獲得解碼版:
ksymoops saved_oops.txt
kallsyms特性:內核引入kallsyms特性,經過定義CONFIG_KALLSYMS配置選項啓用,存放着內核鏡像中相應函數地址的符號名稱,內核能夠打印解碼好的跟蹤線索。再也不須要System.map或ksymoops了。
爲了方便調試和測試內核代碼,內核提供了許多配置選項:
位於內核配置編輯器的內核開發菜單項中,依賴於CONFIG_DEBUG_KERNEL。
如:
原子操做:指那些可以不分隔執行的東西;在執行時不能中斷不然就是完不成的代碼。這時睡眠就是引起死鎖的元兇。
解決:內核提供了一個原子操做計數器,在進程要進入睡眠時打印警告信息並提供追蹤線索。
BUG()和BUG_ON():用來方便標記bug,提供斷言並輸出信息,會引起oops,致使棧的回溯和錯誤信息的打印。能夠把這些調用當作斷言使用,想要斷言某種狀況不應發生:
if (bad_thing) BUG(); 或 BUG_ON(bad_thing);(更清晰更可讀,會將其聲明做爲一個語句放入unlikely()中) 另:BUILD_BUG_ON():與BUG_ON()做用相同,僅在編譯時調用。
panic():引起更嚴重的錯誤,不但會打印錯誤信息,還會掛起整個系統。
dump_stack():只在終端上打印寄存器上下文和函數的跟蹤線索。
神奇的系統請求鍵是調試和挽救垂危系統所必需的一種工具:該功能被啓用時,不管內核出於什麼狀態,均可以經過特殊的組合鍵和內核進行通訊。
經過定義CONFIG_MAGIC_SYSRQ配置選項來啓用。 除了配置選項之外,還要經過一個sysctl用來標記該特性的開或關,啓動命令以下: echo 1 > /proc/sys/kernel/sysrq
輸入Sysrq-h可獲取可用的選項列表,三鍵組合可從新啓動瀕臨死亡的系統:
SysRq-s:將「髒」緩衝區跟硬盤交換分區同步 SysRq-u:卸載全部的文件系統 SysRq-b:重啓設備
因爲沒有用於內核的調試器,許多補丁應運而生,爲標準內核附加上了內核調試的支持,這些補丁功能完善,十分強大。
gdb
可使用標準的GNU調試器對正在運行的內核進行查看。針對內核啓動調試器的方法與針對進程的方法大體相同:
gdb vmlinux /proc/kcore vmlinx:未經壓縮的內核映像,不是壓縮過的zImage或bImage,它存放於源代碼樹的根目錄上。 /proc/kcore:做爲一個參數選項,是做爲core文件來用的,經過它可以訪問到內核駐留的高端內存。只有超級用戶才能讀取此文件的數據。
可使用gdb的命令來獲取信息。如:
打印一個變量的值:p global_variable 反彙編一個函數:disassemble function 編譯內核時使用了-g參數gdb還能夠提供更多的信息。(不能當習慣,這樣編譯的內核會很大)
侷限性:沒有辦法修改內核數據,不能單步執行內核代碼,不能加斷點
kgdb
是一個補丁 ,可讓咱們在遠程主機上經過串口利用gdb的全部功能對內核進行調試。這須要兩臺計算機:第一臺運行帶有kgdb補丁的內核,第二臺經過串行線使用gdb對第一臺進行調試。
這樣經過kgdb,gdb的全部功能都能使用:
使用uid做爲選擇條件
提供替代物的同時不打破原有代碼的可執行性:把用戶id(UID)做爲選擇條件來實現:
if (current-> uid !=7777) { //新建立的uid=7777的用戶,專門用來測試新算法。 /* 老算法…… */ } else { /* 新算法…… */ }
使用條件變量
若是代碼與進程無關,或者但願有一個針對全部狀況都能使用的機制來控制某個特性,可使用條件變量。
只須要建立一個全局變量做爲一個條件選擇開關:若是該變量爲0,就使用某一個分支上的代碼;不然,選擇另一個分支。經過某種接口或者調試器直接操控。
使用統計量
須要掌握某個特定事件的發生規律時使用:建立統計量,並提供某種機制訪問其統計結果。
重複頻率限制
當系統要顯示的調試信息過多的時候,有兩種技巧能夠防止這類問題發生:
另:用到的變量都應該是靜態的,並限制在函數局部範圍之內。
注意:以上都不是SMP(對稱多處理結構)安全的,理想的方式是用原子操做。
找到bug是何時引入內核源代碼的,就是從哪個版本開始有bug的。
在一個確保沒有問題的內核和一個確定有問題的內核之間使用二分法,重複篩選,將問題侷限在兩個相繼發行的版本之間,對引起bug的代碼變動進行定位。
Git可自動進行二分搜索進程找到具體哪次提交的代碼引起了bug:
git bisect start //告訴git要進行二分搜索 git bisect bad <revision> //提供出現問題的最先內核版本,若是就是當前版本:git bisect bad git bisect good <revision> //提供最新的可正常運行的內核版本 git利用二分搜索法在Linux源碼樹中,自動檢測正常的版本內核和有bug的內核版本之間那個版本有隱患,再編譯、運行以及測試正被檢測的版本。 若是這個版本正常:git bisect good 若是這個版本運行有異常:git bisect bad 注意:對於每個命令,git將在一個版本的基礎上反覆二分搜索源碼樹,而且返回所查的下一個內核版本。反覆執行直到不能再進行二分搜索爲止,最終git會打印出有問題的版本號。 可指定git僅在與錯誤相關的目錄列表中(這裏是arch/x86)去二分搜索提交的補丁:git bisect start - arch/x86
在內核開發社區中尋找其餘開發者的幫助。