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 |
※ 可以把這兩個呼叫當作斷言使用,如: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 */ |
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 |
4 記錄緩衝區
核心訊息都被儲存在一個LOG_BUF_LEN大小的環形佇列中。關於LOG_BUF_LEN定義:
1 |
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) |
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 的 memwatch.c。下面是一個 makefile 示例:
test1
1 |
gcc -DMEMWATCH -DMW_STDIO test1.c memwatch
|
2 |
c -o test1 |
清單 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 |
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. 使用 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. |
清單 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 |
} |
1 |
./run-yamd /usr/src/ test /test2/test2 |
清單 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除錯+vmlinuxvmlinux是個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核心除錯技巧三:kallsymskallsyms是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 |