1. 程式人生 > >linux 核心除錯技術

linux 核心除錯技術

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

一  除錯前的準備

在除錯一個bug之前,我們所要做的準備工作有:
  • 有一個被確認的bug。
  • 包含這個bug的核心版本號,需要分析出這個bug在哪一個版本被引入,這個對於解決問題有極大的幫助。可以採用二分查詢法來逐步鎖定bug引入版本號。
  • 對核心程式碼理解越深刻越好,同時還需要一點點運氣。
  • 該bug可以復現。如果能夠找到復現規律,那麼離找到問題的原因就不遠了。
  • 最小化系統。把可能產生bug的因素逐一排除掉。

二  核心中的bug

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

三  核心除錯配置選項

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

1  核心配置

為了實現核心除錯,在核心配置上增加了幾項:

01
Kernel hacking  --->      
02 [*]   Magic SysRq key 
03 [*]   Kernel debugging 
04 [*]   Debug slab memory allocations   
05 [*]   Spinlock and rw-lock debugging: basic checks 
06 [*]   Spinlock debugging:
sleep-inside-spinlock checking 
07 [*]   Compile the kernel with debug info   
08 Device Drivers  --->   
09 Generic Driver Options  ---> 
10 [*]   Driver Core verbose debug messages 
11 General setup  ---> 
12 [*]   Configure standard kernel features (for small systems)  ---> 
13 [*]   Load all symbols for debugging/ksymoops
啟用選項例如:
1 slab layer debugging(slab層除錯選項) 
2 high-memory debugging(高階記憶體除錯選項) 
3 I/O mapping debugging(I/O對映除錯選項) 
4 spin-lock debugging(自旋鎖除錯選項) 
5 stack-overflow checking(棧溢位檢查選項) 
6 sleep-inside-spinlock checking(自旋鎖內睡眠選項)

2  除錯原子操作

從核心2.5開發,為了檢查各類由原子操作引發的問題,核心提供了極佳的工具。
核心提供了一個原子操作計數器,它可以配置成,一旦在原子操作過程中,進城進入睡眠或者做了一些可能引起睡眠的操作,就列印警告資訊並提供追蹤線索。
所以,包括在使用鎖的時候呼叫schedule(),正使用鎖的時候以阻塞方式請求分配記憶體等,各種潛在的bug都能夠被探測到。
下面這些選項可以最大限度地利用該特性:
1 CONFIG_PREEMPT = y 
2 CONFIG_DEBUG_KERNEL = y 
3 CONFIG_KLLSYMS = y 
4 CONFIG_SPINLOCK_SLEEP = y

四  引發bug並列印資訊

1  BUG()和BUG_ON()

一些核心呼叫可以用來方便標記bug,提供斷言並輸出資訊。最常用的兩個是BUG()和BUG_ON()。


定義在<include/asm-generic>中:

01 #ifndef HAVE_ARCH_BUG 
02 #define BUG() do { 
03 printk("BUG: failure at %s:%d/%s()! ", __FILE__, __LINE__, __FUNCTION__); 
04 panic("BUG!");   /* 引發更嚴重的錯誤,不但列印錯誤訊息,而且整個系統業會掛起 */ 
05 } while (0) 
06 #endif 
07
08 #ifndef HAVE_ARCH_BUG_ON 
09 #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0) 
10 #endif
當呼叫這兩個巨集的時候,它們會引發OOPS,導致棧的回溯和錯誤訊息的列印。
※ 可以把這兩個呼叫當作斷言使用,如:BUG_ON(bad_thing);

2  dump_stack()

有些時候,只需要在終端上列印一下棧的回溯資訊來幫助你除錯。這時可以使用dump_stack()。這個函式只在終端上列印暫存器上下文和函式的跟蹤線索。
1 if (!debug_check) { 
2 printk(KERN_DEBUG “provide some information…/n”); 
3 dump_stack(); 
4 }

五  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等級:
1 #define KERN_EMERG      "<0>"   /* system is unusable                            */
2 #define KERN_ALERT        "<1>"   /* action must be taken immediately     */ 
3 #define KERN_CRIT           "<2>"   /* critical conditions                                */
4 #define KERN_ERR            "<3>"   /* error conditions                                   */
5 #define KERN_WARNING  "<4>"   /* warning conditions                              */
6 #define KERN_NOTICE       "<5>"   /* normal but significant condition         */
7 #define KERN_INFO            "<6>"   /* informational                                       */
8 #define KERN_DEBUG        "<7>"   /* debug-level messages                       */
9 #define KERN_DEFAULT     "<d>"   /* Use the default kernel loglevel           */
注意,如果呼叫者未將日誌級別提供給 printk,那麼系統就會使用預設值 KERN_WARNING "<4>"(表示只有KERN_WARNING 級別以上的日誌訊息會被記錄)。由於預設值存在變化,所以在使用時最好指定LOG級別。有LOG級別的一個好處就是我們可以選擇性的輸出LOG。比如平時我們只需要列印KERN_WARNING級別以上的關鍵性LOG,但是除錯的時候,我們可以選擇列印KERN_DEBUG等以上的詳細LOG。而這些都不需要我們修改程式碼,只需要通過命令修改預設日誌輸出級別:
1 [email protected] :~$ cat /proc/sys/kernel/printk
2 4 4 1 7
3 [email protected] :~$ cat /proc/sys/kernel/printk_delay
4 0
5 [email protected] :~$ cat /proc/sys/kernel/printk_ratelimit
6 5
7 [email protected] :~$ cat /proc/sys/kernel/printk_ratelimit_burst
8 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定義:
1 #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
  ※ 變數CONFIG_LOG_BUF_SHIFT在核心編譯時由配置檔案定義,對於i386平臺,其值定義如下(在linux26/arch/i386/defconfig中):
1 CONFIG_LOG_BUF_SHIFT=18
  記錄緩衝區操作:
  ① 訊息被讀出到使用者空間時,此訊息就會從環形佇列中刪除。
  ② 當訊息緩衝區滿時,如果再有printk()呼叫時,新訊息將覆蓋佇列中的老訊息。
  ③ 在讀寫環形佇列時,同步問題很容易得到解決。

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


5  syslogd/klogd

在標準的Linux系統上,使用者空間的守護程序klogd從紀錄緩衝區中獲取核心訊息,再通過syslogd守護程序把這些訊息儲存在系統日誌檔案中。klogd程序既可以從/proc/kmsg檔案中,也可以通過syslog()系統呼叫讀取這些訊息。預設情況下,它選擇讀取/proc方式實現。klogd守護程序在訊息緩衝區有新的訊息之前,一直處於阻塞狀態。一旦有新的核心訊息,klogd被喚醒,讀出核心訊息並進行處理。預設情況下,處理例程就是把核心訊息傳給syslogd守護程序。syslogd守護程序一般把接收到的訊息寫入/var/log/messages檔案中。不過,還是可以通過/etc/syslog.conf檔案來進行配置,可以選擇其他的輸出檔案。


6  dmesg

dmesg 命令也可用於列印和控制核心環緩衝區。這個命令使用 klogctl 系統呼叫來讀取核心環緩衝區,並將它轉發到標準輸出(stdout)。這個命令也可以用來清除核心環緩衝區(使用 -c 選項),設定控制檯日誌級別(-n 選項),以及定義用於讀取核心日誌訊息的緩衝區大小(-s 選項)。注意,如果沒有指定緩衝區大小,那麼 dmesg 會使用 klogctl 的SYSLOG_ACTION_SIZE_BUFFER 操作確定緩衝區大小。

7 注意 

a) 雖然printk很健壯,但是看了原始碼你就知道,這個函式的效率很低:做字元拷貝時一次只拷貝一個位元組,且去呼叫console輸出可能還產生中斷。所以如果你的驅動在功能除錯完成以後做效能測試或者釋出的時候千萬記得儘量減少printk輸出,做到僅在出錯時輸出少量資訊。否則往console輸出無用資訊影響效能。
b) printk的臨時快取printk_buf只有1K,所有一次printk函式只能記錄<1K的資訊到log buffer,並且printk使用的“ringbuffer”.

8 核心printk和日誌系統的總體結構

9  動態除錯

動態除錯是通過動態的開啟和禁止某些核心程式碼來獲取額外的核心資訊。
首先核心選項CONFIG_DYNAMIC_DEBUG應該被設定。所有通過pr_debug()/dev_debug()列印的資訊都可以動態的顯示或不顯示。
可以通過簡單的查詢語句來篩選需要顯示的資訊。

-原始檔名

-函式名

-行號(包括指定範圍的行號)

-模組名

-格式化字串

將要列印資訊的格式寫入<debugfs>/dynamic_debug/control中。
1 nullarbor:~ # echo 'file svcsock.c line 1603 +p' >      <debugfs>/dynamic_debug/control

參考:
1   核心日誌及printk結構淺析 -- Tekkaman Ninja
2   核心日誌:API 及實現
3   printk實現分析
4   dynamic-debug-howto.txt

六  記憶體除錯工具

1  MEMWATCH

MEMWATCH 由 Johan Lindh 編寫,是一個開放原始碼 C 語言記憶體錯誤檢測工具,您可以自己下載它。只要在程式碼中新增一個頭檔案並在 gcc 語句中定義了 MEMWATCH 之後,您就可以跟蹤程式中的記憶體洩漏和錯誤了。MEMWATCH 支援ANSIC,它提供結果日誌紀錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒有釋放的記憶體(unfreedmemory)、溢位和下溢等等。
清單 1. 記憶體樣本(test1.c)
01 #include <stdlib.h>
02 #include <stdio.h>
03 #include "memwatch.h"
04 int main(void)
05 {
06 char *ptr1;
07 char *ptr2;
08 ptr1 = malloc(512);
09 ptr2 = malloc(512);
10 ptr2 = ptr1;
11 free(ptr2);
12 free(ptr1);
13 }
清單 1 中的程式碼將分配兩個 512 位元組的記憶體塊,然後指向第一個記憶體塊的指標被設定為指向第二個記憶體塊。結果,第二個記憶體塊的地址丟失,從而產生了記憶體洩漏。
現在我們編譯清單 1 的 memwatch.c。下面是一個 makefile 示例:
test1
1 gcc -DMEMWATCH -DMW_STDIO test1.c memwatch
2 c -o test1
當您執行 test1 程式後,它會生成一個關於洩漏的記憶體的報告。清單 2 展示了示例 memwatch.log 輸出檔案。

清單 2. test1 memwatch.log 檔案

01 MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh
02 ...
03 double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)
04 ...
05 unfreed: <2> test1.c(11), 512 bytes at 0x80519e4
06 {FE FE FE FE FE FE FE FE FE FE FE FE ..............}
07 Memory usage statistics (global):
08 N)umber of allocations made: 2
09 L)argest memory usage : 1024
10 T)otal of all alloc() calls: 1024
11 U)nfreed bytes totals : 512
MEMWATCH 為您顯示真正導致問題的行。如果您釋放一個已經釋放過的指標,它會告訴您。對於沒有釋放的記憶體也一樣。日誌結尾部分顯示統計資訊,包括洩漏了多少記憶體,使用了多少記憶體,以及總共分配了多少記憶體。

2  YAMD

YAMD 軟體包由 Nate Eldredge 編寫,可以查詢 C 和 C++ 中動態的、與記憶體分配有關的問題。在撰寫本文時,YAMD 的最新版本為 0.32。請下載 yamd-0.32.tar.gz。執行 make 命令來構建程式;然後執行 make install 命令安裝程式並設定工具。
一旦您下載了 YAMD 之後,請在 test1.c 上使用它。請刪除 #include memwatch.h 並對 makefile 進行如下小小的修改:
使用 YAMD 的 test1
1 gcc -g test1.c -o test1
清單 3 展示了來自 test1 上的 YAMD 的輸出。
清單 3. 使用 YAMD 的 test1 輸出
01 YAMD version 0.32
02 Executable: /usr/src/test/yamd-0.32/test1
03 ...
04 INFO: Normal allocation of this block
05 Address 0x40025e00, size 512
06 ...
07 INFO: Normal allocation of this block
08 Address 0x40028e00, size 512
09 ...
10 INFO: Normal deallocation of this block
11 Address 0x40025e00, size 512
12 ...
13 ERROR: Multiple freeing At
14 free of pointer already freed
15 Address 0x40025e00, size 512
16 ...
17 WARNING: Memory leak
18 Address 0x40028e00, size 512
19 WARNING: Total memory leaks:
20 1 unfreed allocations totaling 512 bytes
21 *** Finished at Tue ... 10:07:15 2002
22 Allocated a grand total of 1024 bytes 2 allocations
23 Average of 512 bytes per allocation
24 Max bytes allocated at one time: 1024
25 24 K alloced internally / 12 K mapped now / 8 K max
26 Virtual program size is 1416 K
27 End.
YAMD 顯示我們已經釋放了記憶體,而且存在記憶體洩漏。讓我們在清單 4 中另一個樣本程式上試試 YAMD。
清單 4. 記憶體程式碼(test2.c)
01 #include <stdlib.h>
02 #include <stdio.h>
03 int main(void)
04 {
05 char *ptr1;
06 char *ptr2;
07 char *chptr;
08 int i = 1;
09 ptr1 = malloc(512);
10 ptr2 = malloc(512);
11 chptr = (char *)malloc(512);
12 for (i; i <= 512; i++) {
13 chptr[i] = 'S';
14
15 ptr2 = ptr1;
16 free(ptr2);
17 free(ptr1);
18 free(chptr);
19 }
您可以使用下面的命令來啟動 YAMD:
1 ./run-yamd /usr/src/test/test2/test2
清單 5 顯示了在樣本程式 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在 for 迴圈中有“越界(out-of-bounds)”的情況。
清單 5. 使用 YAMD 的 test2 輸出
01 Running /usr/src/test/test2/test2
02 Temp output to /tmp/yamd-out.1243
03 *********
04 ./run-yamd: line 101: 1248 Segmentation fault (core dumped)
05 YAMD version 0.32
06 Starting run: /usr/src/test/test2/test2
07 Executable: /usr/src/test/test2/test2
08 Virtual program size is 1380 K
09 ...
10 INFO: Normal allocation of this block
11 Address 0x40025e00, size 512
12 ...
13

相關推薦

嵌入式Linux開發——(十六)Linux核心除錯技術

1、核心列印函式printk     ①printk函式與printf函式用法格式完全相同     ②它所列印的字串頭部可以加入“<n>”樣式字元,n=0---7表示這條資訊的記錄  級別     ③對於p

linux 核心除錯技術

核心開發比使用者空間開發更難的一個因素就是核心除錯艱難。核心錯誤往往會導致系統宕機,很難保留出錯時的現場。除錯核心的關鍵在於你的對核心的深刻理解。 一  除錯前的準備 在除錯一個bug之前,我們所要做的準備工作有: 有一個被確認的bug。 包含這個bug的核心版本號,需要分析出這個b

Linux核心除錯技術——jprobe使用與實現

前一篇博文介紹了kprobes的原理與kprobe的使用與實現方式,本文介紹kprobes中的第二種探測技術jprobe,它基於kprobe實現,不能在函式的任意位置插入探測點,只能在函式的入口處探測,一般用於監測函式的入參值。本文首先通過一個簡單的示例介紹jprobe的使

Linux核心除錯技術

Linux除錯技術介紹 對於任何編寫核心程式碼的人來說,最吸引他們注意的問題之一就是如何完成除錯。由於核心是一個不與某個程序相關的功能集,其程式碼不能很輕鬆地放在偵錯程式中執行,而且也不能跟蹤。 本章介紹你可以用來監視核心程式碼和跟蹤錯誤的技術。 用列印資訊除錯 最一般的除錯技術就是監視,就是在應用內部合適的

Linux核心除錯技術——kretprobe使用與實現

前兩篇博文介紹了kprobes探測技術中kprobe和jprobe的使用與實現。本文介紹kprobes中的最後一種探測技術kretprobe,它同樣基於kprobe實現,可用於探測函式的返回值以及計算函式執行的耗時。本文首先通過一個簡單的示例程式介紹kretprobe的使用

linux核心除錯技巧之一 dump_stack【轉】

在核心中程式碼呼叫過程難以跟蹤,上下文關係複雜,確實讓人頭痛 呼叫dump_stack()就會列印當前cpu的堆疊的呼叫函數了。 如此,一目瞭然的就能看到當前上下文環境,呼叫關係了 假設: 遇到uvc_probe_video這麼一個函式,不知道它最終是被誰呼叫到的,根據linux裝置模型,初步推測,p

linux核心除錯環境搭建

版本linux4.17 ubuntu18.04先給系統至少80G記憶體1。編譯核心先配置檔案make mrpropermake menuconfig我這裡需要的依賴有 sudo apt install make cmake gcc g++ clang sudo apt-get install libnc

Linux核心除錯的方式以及工具集錦

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作 因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的除錯工具以供收錄, 鄙人在此謝

linux核心除錯方法

核心開發比使用者空間開發更難的一個因素就是核心除錯艱難。核心錯誤往往會導致系統宕機,很難保留出錯時的現場。除錯核心的關鍵在於你的對核心的深刻理解。 一  除錯前的準備 在除錯一個bug之前,我們所要做的準備工作有: 有一個被確認的bug。 包含這個bug的核心

Linux核心除錯方法總結

核心開發比使用者空間開發更難的一個因素就是核心除錯艱難。核心錯誤往往會導致系統宕機,很難保留出錯時的現場。除錯核心的關鍵在於你的對核心的深刻理解。  一  除錯前的準備 在除錯一個bug之前,我們所要做的準備工作有:  有一個被確認的bug。 包含這

linux核心除錯技巧四:gdb除錯+vmlinux

vmlinux是個elf檔案,它的符號表中包含了所有核心符號。 注意linux中很多檔案是沒有後綴的,比如我見到的這個elf檔案的檔名是“vmlinux-3.10.62”,沒有後綴。 既然是elf檔案

核心除錯技術

kdb:只能在彙編程式碼級進行除錯;   優點是不需要兩臺機器進行除錯。   gdb:在除錯模組時缺少一些至關重要的功能,它可用來檢視核心的執行情況,包括反彙編核心函式。   kgdb:能很方便的在原始碼級對核心進行除錯,缺點是kgdb只能進行遠端除錯,它需要一根串列埠線及兩臺機器來除錯核心(也可以

Linux核心除錯環境搭建(基於ubuntu12.04)

by Netfairy - 2016-05-29 一、測試環境 物理機:ubuntu16.04 LTS target(被除錯機)環境:VirtualBox 5.0.20+ubuntu 12.04 LTS + linux kernel 3.0.4 host

Linux核心除錯

#define    KERN_EMERG    "<0>"   /* system is unusable */#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */#define    

Linux 核心除錯之 printk

問題描述:最近這兩天再除錯 platform 驅動,程式老是有點小問題,得不到自己想要的結果,突然意識到核心除錯重要性,重新整理一下 printk 基本用法。核心通過 printk() 輸出相關資訊

Linux核心除錯工具

一些Linux Kernel的分析除錯工作,主要包換qemu,kprobes和trace等,以作備忘。 Qemu原始碼級除錯Kernel 1. Qemu編譯與安裝 先安裝libsdl的開發庫 $ ./configure $ make # make install Qe

linux 核心除錯方法

kdb:只能在彙編程式碼級進行除錯;   優點是不需要兩臺機器進行除錯。   gdb:在除錯模組時缺少一些至關重要的功能,它可用來檢視核心的執行情況,包括反彙編核心函式。   kgdb:能很方便的在原始碼級對核心進行除錯,缺點是kgdb只能進行遠端除錯,它需要一根串列埠線及兩臺機器來除錯核心(也可以是在同一

linux核心除錯技巧三:kallsyms

kallsyms是linux的一個子系統,顧名思義,kernel_all_syms,也就是核心的所有符號。 kallsyms子系統的功能是把核心程式碼的所有符號(其實不是所有,沒仔細研究,不過重要的都

linux核心除錯+qemu+eclipse

一、除錯環境: 在ubuntu16.04下,在虛擬機器裡邊執行的ubuntu,裝32位的執行較快,選擇較新的ubuntu版本是因為安裝qemu、eclipse比較簡單,在安裝軟體上節約時間。 二、安裝

linux核心除錯環境搭建-2 用busybox搭建

下載linux核心: $cd ~/work/ $wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.35.9.tar.bz2 解壓 $tar -jxvf linux-2.6.35.9.tar.bz2