【學習時間:1小時 總結博客時間:1小時15分】linux
【學習內容:出現bug的緣由、內核調試器gdb、使用Git進行二分查找】git
內核級開發的調試工做遠比用戶級開發艱難,它帶來的風險比用戶級別更高。算法
1. 準備工做須要:sass
2. 在用戶級程序中bug經常表現得清晰(執行foo就會讓程序當即產生核心信息轉儲)可是內核中的bug表現得不像用戶級程序中那麼清晰。由於內核、用戶以及硬件之間的交互很微妙。安全
3. 調試的主要思想是讓bug重現,可是在內核中這並非很容易作到的。所以,在跟蹤bug的時候,掌握的信息越多越好。架構
1. 內核bug產生的緣由:app
2. 內核bug發做的症狀可能有:ide
3. 從隱藏在源代碼中的錯誤到展示在目睹者面前的bug,每每是經歷一系列連鎖反應的事件纔可能觸發的。函數
4. 內核開發比起用戶開發要多考慮一些獨特的問題,如定時限制、競爭條件等,它們都是容許多個線程在內核中同時運行產生的結果。工具
內核提供的格式化打印函數printk()有一些特殊功能:
健壯性——在任什麼時候候、任何地方都能調用它,彈性極佳。能夠在中斷上下文和進程上下文中被調用;能夠在任何持有鎖時被調用;能夠在多處理器上同時被調用,而且沒必要使用鎖。
漏洞:在系統啓動過程當中,中斷尚未初始化以前,在某些地方不能使用它。
解決方法:調試啓動過程最開始的步驟時,可用early_printk()代替,功能與printk()徹底相同,能更早工做。
1. printk()和printf()在使用上最主要的區別就是前者能夠指定一個日誌級別,內核根據這個級別來判斷是否在終端上打印消息。內核把級別比某個特定值低的全部消息顯示在終端上。
2. KERN_ WARNING和KERN_ DEBUG都是簡單的宏定義,加進printk()函數要打印的消息的開頭。內核用這個指定的記錄等級和當前終端的記錄等級console_loglevel來決定是否是向終端上打印。
3. 若是沒有特別特別指定,函數會選用默認的DEFAULT_ MESSAGE_ LOGLEVEL,在當前來看是KERN_ WARNING,即一個警告。最好仍是給本身的消息指定一個記錄等級。
4. 內核把最重要的記錄等級KERN_EMERG定義爲"<0>",將可有可無的記錄等級KERN _ DEBUG定義爲"<7>"
5. 調試信息時兩種賦予記錄等級的方法:
1. 內核消息都被記錄在環形隊列中,以隊列方式進行讀寫;大小能夠經過設置CONFIGLOGBUF_SHIFT進行調整。
2. 在單處理器上,該緩衝區大小默認爲16KB,也就是說,超過的消息將覆蓋舊消息。
3. 優點:
4. 劣勢:可能會丟失消息。
syslogd和klogd是兩個用戶空間的守護進程。klogd從記錄緩衝區中獲取內核消息,再經過syslogd守護進程將他們保存在系統日誌文件中。
1. klogd
2. syslogd
syslogd將它接收到的全部消息添加到一個文件中,默認是/var/log/messages。
1. oops是內核告知用戶有不幸發生的最經常使用的方式。內核很難自我修復,也不能將本身殺死(由於內核是整個系統的管理者,不能將本身殺死,也很難自行修復),只能發佈oops。
2. 發佈oops的過程
3. oops發生的時機:
4. oops發生的可能緣由:
5. oops中包含的重要信息對於全部體系結構都是相同的:寄存器上下文和回溯線索。
回溯線索中的地址須要轉化成有意義的符號名稱才能使用,這須要調用ksymoops命令,而且還必須提供編譯內核時產生的System.map。若是用的是模塊,還須要一些模塊信息。
調用方式:
kysmoop saved_oops.txt
如今不須要使用sysmoops工具,由於用戶使用時可能會發生不少問題。新版本中引入了kallsyms特性,能夠經過定義CONFIG_KALLSYMS配置選項啓用。
編譯時爲了方便調試和測試內核代碼,內核提供了許多配置選項。這些選項都在內核配置編譯器的內核開發菜單中,都依賴於CONFIG_ DEBUG_ KERNEL。
經常使用選項:
slab layer debugging slab層調試選項 high-memory debugging 高端內存調試選項 I/O mapping debugging I/O映射調試選項 spin-lock debugging 自旋鎖調試選項 stack-overflow debugging 棧溢出檢查選項 sleep-inside-spinlock checking 自旋鎖內睡眠選項 ……
1. 一些內核調用能夠用來方便標記bug,提供斷言並輸出信息。最經常使用的兩個是BUG()和BUG_ON()。當被調用時會引起oops,致使棧的回溯和錯誤信息的打印。
大部分體系把BUG()和BUG_ON()定義成某種非法操做,這樣天然會產生須要的oops。能夠把這些調用當作斷言使用,想要斷言某種狀況不應發生:
if (bad_thing) BUG();
或使用更好的形式:
BUG_ON(bad_thing);
2. BUILD_ BUG_ ON() 與BUG_ ON()做用相同,僅在編譯時調用。
3. panic()能夠引起更嚴重的錯誤,不但會打印錯誤信息,還會掛起整個系統。
4. dump_stack()只在終端上打印寄存器上下文和函數的跟蹤線索。
這個功能能夠經過定義CONFIG_ MAGIC _SYSRQ配置選項來啓用。SysRq(系統請求)鍵在大多數鍵盤上都是標準鍵。
該功能被啓用時,不管內核出於什麼狀態,均可以經過特殊的組合鍵和內核進行通訊。
除了配置選項之外,還要經過一個sysctl用來標記該特性的開或關,啓動命令以下:
echo 1 > /proc/sys/kernel/sysrq
支持Sysrq的幾個命令:
1. 可使用標準的GNU調試器對正在運行的內核進行查看。 針對內核啓動調試器的方法與針對進程的方法大體相同:
gdb vmlinux /proc/kcore
其中vmlinx文件是未經壓縮的內核映像,區別於zImage或bImage,它存放於源代碼樹的根目錄上。
/proc/kcore做爲一個參數選項,是做爲core文件來用的,經過它可以訪問到內核駐留的高端內存。只有超級用戶才能讀取此文件的數據可使用gdb的全部命令來獲取信息。如:
p global_variable //打印一個變量的值 disassemble function //反彙編一個函數
2. 若是編譯內核的時候使用了-g參數(在內核的Makefile文件的CFLAGS變量中加入-g)gdb還能夠提供更多的信息。
3. gdb的侷限性:
1. kgdb是一個補丁,可讓咱們在遠程主機上經過串口利用gdb的全部功能對內核進行調試。這須要兩臺計算機:第一臺運行帶有kgdb補丁的內核,第二臺經過串行線使用gdb對第一臺進行調試。
2. 經過kgdb,gdb的全部功能都能使用:
1. 通常狀況下,加入特性時,只要保留原有的算法而把新算法加入到其餘位置上,基本就能保證安全。能夠把用戶id(UID)做爲選擇條件來實現這種功能,經過某種選擇條件,安排到底執行哪一種算法:
if(current->uid != 7777) { /*老算法*/ else { /*新算法*/ }
若是代碼與進程無關,或者但願有一個針對全部狀況都能使用的機制來控制某個特性,可使用條件變量。這種方式比使用UID更簡單,只須要建立一個全局變量做爲一個條件選擇開關:若是該變量爲0,就使用某一個分支上的代碼;不然,選擇另一個分支。
能夠經過某種接口提供對這個變量的操控,也能夠直接經過調試器進行操控。
這種方法經常使用於使用者須要掌握某個特定事件的發生規律的時候。 經過建立統計量並提供某種機制訪問其統計結果。
注意:這種方法不是SMP安全的,理想的辦法是經過原子操做進行實現。
當系統的調試信息過多的時候,有兩種技巧能夠防止這類問題發生:
注意:
在問題內核和良好內核之間使用二分法,能很容易地對引起bug的代碼進行定位。
Git源碼管理工具提供了一個有用的二分搜索機制,若是使用Git來控制Linux源碼樹的副本,則Git將自動運行二分搜索進程。此外,Git會在修訂版本中進行二分搜索,能夠具體找到哪次提交的代碼引起了bug。
git bisect start //告知git要進行二分搜索 git bisect bad <revision> //已知出現問題的最先內核版本 git bisect bad //當前版本就是引起bug的最第一版本的狀況下使用這條命令 git bisect good <revision> //最新的可正常運行的內核版本
以後Git就會利用二分搜索法在Linux源碼樹中,自動檢測正常的版本內核和有bug的內核版本之間哪一個版本有隱患,而後再編譯、運行以及測試正被檢測的版本。
若是版本運行正常: git bisect good 若是版本運行異常: git bisect bad
對於每個命令,Git將在每個版本的基礎上反覆二分搜索源碼樹,而且返回所查的下一個內核版本,直到不能再進行二分搜索位置,最終Git會打印出有問題的版本號。
指定Git僅僅在與錯誤相關的目錄列表中去二分搜索提交的補丁: git bisect start - arch/x86
經過對本章的學習,我瞭解到調試過程實際上是一種尋求實現與目標誤差的行爲,從內核內置的調試架構到調試程序,從記錄日誌到用git二分法查找。此時夯實基礎,爲之後的學習積累經驗。