一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Linux內(nèi)核常用調(diào)試方法

 mynotebook 2022-10-03 發(fā)布于湖南
 1

內(nèi)核開發(fā)比用戶空間開發(fā)更難的一個因素就是內(nèi)核調(diào)試艱難。內(nèi)核錯誤往往會導(dǎo)致系統(tǒng)宕機(jī),很難保留出錯時的現(xiàn)場。調(diào)試內(nèi)核的關(guān)鍵在于你的對內(nèi)核的深刻理解。

嵌入式進(jìn)階教程分門別類整理好了,看的時候十分方便,由于內(nèi)容較多,這里就截取一部分圖吧。

調(diào)試前的準(zhǔn)備

在調(diào)試一個bug之前,我們所要做的準(zhǔn)備工作有:

有一個被確認(rèn)的bug,包含這個bug的內(nèi)核版本號,需要分析出這個bug在哪一個版本被引入,這個對于解決問題有極大的幫助。可以采用二分查找法來逐步鎖定bug引入版本號。

對內(nèi)核代碼理解越深刻越好,同時還需要一點點運氣,該bug可以復(fù)現(xiàn)。如果能夠找到規(guī)律,那么離找到問題的原因就不遠(yuǎn)了;最小化系統(tǒng)。把可能產(chǎn)生bug的因素逐一排除掉。

內(nèi)核中的bug

內(nèi)核中的bug也是多種多樣的。它們的產(chǎn)生有無數(shù)的原因,同時表象也變化多端。從隱藏在源代碼中的錯誤到展現(xiàn)在目擊者面前的bug,其發(fā)作往往是一系列連鎖反應(yīng)的事件才可能觸發(fā)的。雖然內(nèi)核調(diào)試有一定的困難,但是通過你的努力和理解,說不定你會喜歡上這樣的挑戰(zhàn)。

內(nèi)核調(diào)試配置選項

學(xué)習(xí)編寫驅(qū)動程序要構(gòu)建安裝自己的內(nèi)核(標(biāo)準(zhǔn)主線內(nèi)核)。最重要的原因之一是:內(nèi)核開發(fā)者已經(jīng)建立了多項用于調(diào)試的功能。但是由于這些功能會造成額外的輸出,并導(dǎo)致能量下降,因此發(fā)行版廠商通常會禁止發(fā)行版內(nèi)核中的調(diào)試功能。

內(nèi)核配置

為了實現(xiàn)內(nèi)核調(diào)試,在內(nèi)核配置上增加了幾項:

Kernel hacking ---> [*] Magic SysRq key [*] Kernel debugging [*]
Debug slab memory allocaTIons [*]
Spinlock and rw-lock debugging: basic checks [*]
Spinlock debugging: sleep-inside-spinlock checking [*]
Compile the kernel with debug info Device Drivers ---> Generic Driver Options ---> [*]
Driver Core verbose debug messages General setup ---> [*]
Configure standard kernel features (for small systems) ---> [*]
Load all symbols for debugging/ksymoops

調(diào)試原子操作

從內(nèi)核2.5開發(fā),為了檢查各類由原子操作引發(fā)的問題,內(nèi)核提供了極佳的工具。
內(nèi)核提供了一個原子操作計數(shù)器,它可以配置成,一旦在原子操作過程中,經(jīng)常進(jìn)入睡眠或者做了一些可能引起睡眠的操作,就打印警告信息并提供追蹤線索。
所以,包括在使用鎖的時候調(diào)用schedule(),正使用鎖的時候以阻塞方式請求分配內(nèi)存等,各種潛在的bug都能夠被探測到。

下面這些選項可以最大限度地利用該特性:

CONFIG_PREEMPT = y 
CONFIG_DEBUG_KERNEL = y
CONFIG_KLLSYMS = y CONFIG_SPINLOCK_SLEEP = y

引發(fā)bug并打印信息

BUG()和BUG_ON()

一些內(nèi)核調(diào)用可以用來方便標(biāo)記bug,提供斷言并輸出信息。最常用的兩個是BUG()和BUG_ON()。定義在中:

#ifndef HAVE_ARCH_BUG #define BUG() do {
printk('BUG: failure at %s:%d/%s()! ', __FILE__, __LINE__, __FUNCTION__);
panic('BUG!'); /* 引發(fā)更嚴(yán)重的錯誤,不但打印錯誤消息,而且整個系統(tǒng)業(yè)會掛起 */
} while (0) #endif #ifndef HAVE_ARCH_BUG_ON #define BUG_ON(condiTIon)
do {
if (unlikely(condiTIon)) BUG();
}
while(0)
#endif

當(dāng)調(diào)用這兩個宏的時候,它們會引發(fā)OOPS,導(dǎo)致棧的回溯和錯誤消息的打印。
※ 可以把這兩個調(diào)用當(dāng)作斷言使用,如:BUG_ON(bad_thing);

WARN(x) 和 WARN_ON(x)

而WARN_ON則是調(diào)用dump_stack,打印堆棧信息,不會OOPS。定義在中:

#ifndef __WARN_TAINT#ifndef __ASSEMBLY__extern void warn_slowpath_fmt(
const char *file, const int line, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
extern void warn_slowpath_fmt_taint(const char *file, const int line, unsigned taint, const char *fmt, ...) __attribute__((format(printf, 4, 5)));
extern void warn_slowpath_null(const char *file, const int line);
#define WANT_WARN_ON_SLOWPATH
#endif#define __WARN() warn_slowpath_null(__FILE__, __LINE__)
#define __WARN_printf(arg...) warn_slowpath_fmt(__FILE__, __LINE__, arg)
#define __WARN_printf_taint(taint, arg...) warn_slowpath_fmt_taint(__FILE__, __LINE__, taint, arg)
#else#define __WARN() __WARN_TAINT(TAINT_WARN)
#define __WARN_printf(arg...) do { printk(arg); __WARN();
} while (0)#define __WARN_printf_taint(taint, arg...)
do { printk(arg); __WARN_TAINT(taint);
}
while (0)
#endif#ifndef WARN_ON#define WARN_ON(condition) ({
int __ret_warn_on = !!(condition); if (unlikely(__ret_warn_on)) __WARN();
unlikely(__ret_warn_on); })#endif#ifndef WARN#define WARN(condition, format...) ({
int __ret_warn_on = !!(condition); if (unlikely(__ret_warn_on)) __WARN_printf(format);
unlikely(__ret_warn_on); })
#endif

dump_stack()

有些時候,只需要在終端上打印一下棧的回溯信息來幫助你調(diào)試。這時可以使用dump_stack()。這個函數(shù)只是在終端上打印寄存器上下文和函數(shù)的跟蹤線索。

if (!debug_check) { printk(KERN_DEBUG “provide some information…/n”); dump_stack(); }

printk()

內(nèi)核提供的格式化打印函數(shù)。

printk函數(shù)的健壯性

健壯性是printk最容易被接受的一個特質(zhì),幾乎在任何地方,任何時候內(nèi)核都可以調(diào)用它(中斷上下文、進(jìn)程上下文、持有鎖時、多處理器處理時等)。

printk函數(shù)脆弱之處

在系統(tǒng)啟動過程中,終端初始化之前,在某些地方是不能調(diào)用的。如果真的需要調(diào)試系統(tǒng)啟動過程最開始的地方,有以下方法可以使用:

使用串口調(diào)試,將調(diào)試信息輸出到其他終端設(shè)備。

使用early_printk(),該函數(shù)在系統(tǒng)啟動初期就有打印能力。但它只支持部分硬件體系。

LOG等級

printk和printf一個主要的區(qū)別就是前者可以指定一個LOG等級。內(nèi)核根據(jù)這個等級來判斷是否在終端上打印消息。內(nèi)核把比指定等級高的所有消息顯示在終端。
可以使用下面的方式指定一個LOG級別:

printk(KERN_CRIT “Hello, world! ”);

注意,第一個參數(shù)并不一個真正的參數(shù),因為其中沒有用于分隔級別(KERN_CRIT)和格式字符的逗號(,)。KERN_CRIT本身只是一個普通的字符串(事實上,它表示的是字符串 '<2>';表 1 列出了完整的日志級別清單)。作為預(yù)處理程序的一部分,C 會自動地使用一個名為 字符串串聯(lián) 的功能將這兩個字符串組合在一起。組合的結(jié)果是將日志級別和用戶指定的格式字符串包含在一個字符串中。

內(nèi)核使用這個指定LOG級別與當(dāng)前終端LOG等級console_loglevel來決定是不是向終端打印。 下面是可使用的LOG等級:

#define KERN_EMERG '<0>' /* system is unusable */#define KERN_ALERT '<1>' /* action must be taken immediately */ #define KERN_CRIT '<2>' /* critical conditions */#define KERN_ERR '<3>' /* error conditions */#define KERN_WARNING '<4>' /* warning conditions */#define KERN_NOTICE '<5>' /* normal but significant condition */#define KERN_INFO '<6>' /* informational */#define KERN_DEBUG '<7>' /* debug-level messages */#define KERN_DEFAULT '' /* Use the default kernel loglevel */

注意,如果調(diào)用者未將日志級別提供給 printk,那么系統(tǒng)就會使用默認(rèn)值 KERN_WARNING '<4>'(表示只有KERN_WARNING 級別以上的日志消息會被記錄)。由于默認(rèn)值存在變化,所以在使用時最好指定LOG級別。有LOG級別的一個好處就是我們可以選擇性的輸出LOG。比如平時我們只需要打印KERN_WARNING級別以上的關(guān)鍵性LOG,但是調(diào)試的時候,我們可以選擇打印KERN_DEBUG等以上的詳細(xì)LOG。而這些都不需要我們修改代碼,只需要通過命令修改默認(rèn)日志輸出級別:

mtj@ubuntu :~$ cat /proc/sys/kernel/printk4 4 1 7mtj@ubuntu :~$ cat /proc/sys/kernel/printk_delay0mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit5mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit_burst10

第一項定義了 printk API 當(dāng)前使用的日志級別。這些日志級別表示了控制臺的日志級別、默認(rèn)消息日志級別、最小控制臺日志級別和默認(rèn)控制臺日志級別。printk_delay 值表示的是 printk 消息之間的延遲毫秒數(shù)(用于提高某些場景的可讀性)。注意,這里它的值為 0,而它是不可以通過的 /proc 設(shè)置的。printk_ratelimit 定義了消息之間允許的最小時間間隔(當(dāng)前定義為每 5 秒內(nèi)的某個內(nèi)核消息數(shù))。消息數(shù)量是由 printk_ratelimit_burst 定義的(當(dāng)前定義為 10)。如果您擁有一個非正式內(nèi)核而又使用有帶寬限制的控制臺設(shè)備(如通過串口), 那么這非常有用。注意,在內(nèi)核中,速度限制是由調(diào)用者控制的,而不是在printk 中實現(xiàn)的。如果一個 printk 如果用戶要求進(jìn)行速度限制,那么該用戶就需要調(diào)用printk_ratelimit 函數(shù)。

記錄緩沖區(qū)

內(nèi)核消息都被保存在一個LOG_BUF_LEN大小的環(huán)形隊列中。
關(guān)于LOG_BUF_LEN定義:

#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)

※ 變量CONFIG_LOG_BUF_SHIFT在內(nèi)核編譯時由配置文件定義,對于i386平臺,其值定義如下(在
linux26/arch/i386/defconfig中):

CONFIG_LOG_BUF_SHIFT=18

記錄緩沖區(qū)操作:
① 消息被讀出到用戶空間時,此消息就會從環(huán)形隊列中刪除。
② 當(dāng)消息緩沖區(qū)滿時,如果再有printk()調(diào)用時,新消息將覆蓋隊列中的老消息。
③ 在讀寫環(huán)形隊列時,同步問題很容易得到解決。

※ 這個紀(jì)錄緩沖區(qū)之所以稱為環(huán)形,是因為它的讀寫都是按照環(huán)形隊列的方式進(jìn)行操作的。

syslogd/klogd

在標(biāo)準(zhǔn)的Linux系統(tǒng)上,用戶空間的守護(hù)進(jìn)程klogd從紀(jì)錄緩沖區(qū)中獲取內(nèi)核消息,再通過syslogd守護(hù)進(jìn)程把這些消息保存在系統(tǒng)日志文件中。klogd進(jìn)程既可以從/proc/kmsg文件中,也可以通過syslog()系統(tǒng)調(diào)用讀取這些消息。默認(rèn)情況下,它選擇讀取/proc方式實現(xiàn)。klogd守護(hù)進(jìn)程在消息緩沖區(qū)有新的消息之前,一直處于阻塞狀態(tài)。一旦有新的內(nèi)核消息,klogd被喚醒,讀出內(nèi)核消息并進(jìn)行處理。默認(rèn)情況下,處理例程就是把內(nèi)核消息傳給syslogd守護(hù)進(jìn)程。syslogd守護(hù)進(jìn)程一般把接收到的消息寫入/var/log/messages文件中。不過,還是可以通過/etc/syslog.conf文件來進(jìn)行配置,可以選擇其他的輸出文件。

dmesg

dmesg 命令也可用于打印和控制內(nèi)核緩沖區(qū)。這個命令使用 klogctl 系統(tǒng)調(diào)用來讀取內(nèi)核環(huán)緩沖區(qū),并將它轉(zhuǎn)發(fā)到標(biāo)準(zhǔn)輸出(stdout)。這個命令也可以用來清除內(nèi)核環(huán)緩沖區(qū)(使用 -c 選項),設(shè)置控制臺日志級別(-n 選項),以及定義用于讀取內(nèi)核日志消息的緩沖區(qū)大?。?s 選項)。注意,如果沒有指定緩沖區(qū)大小,那么 dmesg 會使用 klogctl 的SYSLOG_ACTION_SIZE_BUFFER 操作確定緩沖區(qū)大小。

注意

a) 雖然printk很健壯,但是看了源碼你就知道,這個函數(shù)的效率很低:做字符拷貝時一次只拷貝一個字節(jié),且去調(diào)用console輸出可能還產(chǎn)生中斷。所以如果你的驅(qū)動在功能調(diào)試完成以后做性能測試或者發(fā)布的時候千萬記得盡量減少printk輸出,做到僅在出錯時輸出少量信息。否則往console輸出無用信息影響性能。
b) printk的臨時緩存printk_buf只有1K,所有一次printk函數(shù)只能記錄<1K的信息到log buffer,并且printk使用的“ringbuffer”.

內(nèi)核printk和日志系統(tǒng)的總體結(jié)構(gòu)

動態(tài)調(diào)試

動態(tài)調(diào)試是通過動態(tài)的開啟和禁止某些內(nèi)核代碼來獲取額外的內(nèi)核信息。
首先內(nèi)核選項CONFIG_DYNAMIC_DEBUG應(yīng)該被設(shè)置。所有通過pr_debug()/dev_debug()打印的信息都可以動態(tài)地顯示或不顯示。
可以通過簡單的查詢語句來篩選需要顯示的信息。

-源文件名

-函數(shù)名

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

-模塊名

-格式化字符串

將要打印信息的格式寫入/dynamic_debug/control中。

nullarbor:~ # echo 'file svcsock.c line 1603 +p' > /dynamic_debug/control

參考:
1 內(nèi)核日志及printk結(jié)構(gòu)淺析 -- Tekkaman Ninja
2 內(nèi)核日志:API 及實現(xiàn)
3 printk實現(xiàn)分析
4 dynamic-debug-howto.txt

內(nèi)存調(diào)試工具

MEMWATCH

MEMWATCH 由 Johan Lindh 編寫,是一個開放源代碼 C 語言內(nèi)存錯誤檢測工具,您可以自己下載它。只要在代碼中添加一個頭文件并在 gcc 語句中定義了 MEMWATCH 之后,您就可以跟蹤程序中的內(nèi)存泄漏和錯誤了。MEMWATCH 支持ANSIC,它提供結(jié)果日志紀(jì)錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒有釋放的內(nèi)存(unfreedmemory)、溢出和下溢等等。

清單 1. 內(nèi)存樣本(test1.c)

#include #include #include 'memwatch.h'int main(void){ char *ptr1; char *ptr2; ptr1 = malloc(512); ptr2 = malloc(512); ptr2 = ptr1; free(ptr2); free(ptr1);}

清單 1 中的代碼將分配兩個 512 字節(jié)的內(nèi)存塊,然后指向第一個內(nèi)存塊的指針被設(shè)定為指向第二個內(nèi)存塊。結(jié)果,第二個內(nèi)存塊的地址丟失,從而產(chǎn)生了內(nèi)存泄漏。
現(xiàn)在我們編譯清單 1 的 memwatch.c。

下面是一個 makefile 示例: 
test1
gcc -DMEMWATCH -DMW_STDIO test1.c memwatchc -o test1

當(dāng)您運行 test1 程序后,它會生成一個關(guān)于泄漏的內(nèi)存的報告。清單 2 展示了示例 memwatch.log 輸出文件。

清單 2. test1 memwatch.log 文件

MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh...double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)...unfreed: <2> test1.c(11), 512 bytes at 0x80519e4{FE FE FE FE FE FE FE FE FE FE FE FE ..............}Memory usage statistics (global): N)umber of allocations made: 2 L)argest memory usage : 1024 T)otal of all alloc() calls: 1024 U)nfreed bytes totals : 512

MEMWATCH 為您顯示真正導(dǎo)致問題出現(xiàn)的信息。如果您釋放一個已經(jīng)釋放過的指針,它會告訴您。對于沒有釋放的內(nèi)存也一樣。日志結(jié)尾部分顯示統(tǒng)計信息,包括泄露了多少內(nèi)存,使用了多少內(nèi)存,以及總共分配了多少內(nèi)存。

YAMD

YAMD 軟件包由 Nate Eldredge 編寫,可以查找 C++ 和 C++ 動態(tài)的、與內(nèi)存分配有關(guān)的問題。在撰寫本文時,YAMD 的最新版本為 0.32。請下載 yamd-0.32.tar.gz。執(zhí)行 make 命令來構(gòu)建程序;然后執(zhí)行 make install 命令安裝程序并設(shè)置工具。

一旦您下載了 YAMD 之后,請在 test1.c 上使用它。請刪除 #include memwatch.h 并對 makefile 進(jìn)行如下小小的修改:

使用 YAMD 的 test1

gcc -g test1.c -o test1

清單 3 展示了來自 test1 上的 YAMD 的輸出。

清單 3. 使用 YAMD 的 test1 輸出

YAMD version 0.32Executable: /usr/src/test/yamd-0.32/test1...INFO: Normal allocation of this blockAddress 0x40025e00, size 512...INFO: Normal allocation of this blockAddress 0x40028e00, size 512...INFO: Normal deallocation of this blockAddress 0x40025e00, size 512...ERROR: Multiple freeing Atfree of pointer already freedAddress 0x40025e00, size 512...WARNING: Memory leakAddress 0x40028e00, size 512WARNING: Total memory leaks:1 unfreed allocations totaling 512 bytes*** Finished at Tue ... 10:07:15 2002Allocated a grand total of 1024 bytes 2 allocationsAverage of 512 bytes per allocationMax bytes allocated at one time: 102424 K alloced internally / 12 K mapped now / 8 K maxVirtual program size is 1416 KEnd.

YAMD 顯示我們已經(jīng)釋放了內(nèi)存,而且存在內(nèi)存泄漏。讓我們在清單 4 中另一個樣本程序上試試 YAMD。

清單 4. 內(nèi)存代碼(test2.c)

#include #include int main(void){ char *ptr1; char *ptr2; char *chptr; int i = 1; ptr1 = malloc(512); ptr2 = malloc(512); chptr = (char *)malloc(512); for (i; i <= 512; i++) { chptr[i] = 'S'; } ptr2 = ptr1; free(ptr2); free(ptr1); free(chptr);}

您可以使用下面的命令來啟動 YAMD:

./run-yamd /usr/src/test/test2/test2

清單 5 顯示了該樣本程序 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在 for 循環(huán)中有“越界(out-of-bounds)”的情況。

清單 5. 使用 YAMD 的 test2 輸出

Running /usr/src/test/test2/test2Temp output to /tmp/yamd-out.1243*********./run-yamd: line 101: 1248 Segmentation fault (core dumped)YAMD version 0.32Starting run: /usr/src/test/test2/test2Executable: /usr/src/test/test2/test2Virtual program size is 1380 K...INFO: Normal allocation of this blockAddress 0x40025e00, size 512...INFO: Normal allocation of this blockAddress 0x40028e00, size 512...INFO: Normal allocation of this blockAddress 0x4002be00, size 512ERROR: Crash...Tried to write address 0x4002c000Seems to be part of this block:Address 0x4002be00, size 512...Address in question is at offset 512 (out of bounds)Will dump core after checking heap.Done.

MEMWATCH 和 YAMD 都是很有用的調(diào)試工具,不過它們的使用方法有所不同。對于 MEMWATCH,您需要添加包含文件memwatch.h 并打開兩個編譯時間標(biāo)記。對于鏈接(link)語句,YAMD 只需要 -g 選項。

Electric Fence

多數(shù) Linux 分發(fā)版包含一個 Electric Fence 包,不過您也可以選擇下載它。Electric Fence 是一個由 Bruce Perens 編寫的malloc()調(diào)試庫。它就在您分配內(nèi)存后分配受保護(hù)的內(nèi)存。如果存在 fencepost 錯誤(超過數(shù)組末尾運行),程序就會產(chǎn)生保護(hù)錯誤,并立即結(jié)束。通過結(jié)合 Electric Fence 和 gdb,您可以精確地跟蹤到哪一行試圖訪問受保護(hù)內(nèi)存。ElectricFence 的另一個功能就是能夠檢測內(nèi)存泄漏。

strace

strace 命令是一種強(qiáng)大的工具,它能夠顯示所有由用戶空間程序發(fā)出的系統(tǒng)調(diào)用。strace 顯示這些調(diào)用的參數(shù)并返回符號形式的值。strace 從內(nèi)核接收信息,而且不需要以任何特殊的方式來構(gòu)建內(nèi)核。將跟蹤信息發(fā)送到應(yīng)用程序及內(nèi)核開發(fā)者都很有用。在清單 6 中,分區(qū)的一種格式有錯誤,清單顯示了 strace 的開頭部分,內(nèi)容是關(guān)于調(diào)出創(chuàng)建文件系統(tǒng)操作(mkfs )的。strace 確定哪個調(diào)用導(dǎo)致問題出現(xiàn)。

清單 6. mkfs 上 strace 的開頭部分

execve('/sbin/mkfs.jfs', ['mkfs.jfs', '-f', '/dev/test1'], &...open('/dev/test1', O_RDWR|O_LARGEFILE) = 4stat64('/dev/test1', {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)write(2, 'mkfs.jfs: warning - cannot setb' ..., 98mkfs.jfs: warning -cannot set blocksize on block device /dev/test1: Invalid argument ) = 98stat64('/dev/test1', {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0open('/dev/test1', O_RDONLY|O_LARGEFILE) = 5ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)write(2, 'mkfs.jfs: can't determine device'..., ..._exit(1) = ?

清單 6 顯示 ioctl 調(diào)用導(dǎo)致用來格式化分區(qū)的 mkfs 程序失敗。 ioctl BLKGETSIZE64 失敗。( BLKGET-SIZE64 在調(diào)用 ioctl的源代碼中定義。) BLKGETSIZE64 ioctl 將被添加到 Linux 中所有的設(shè)備,而在這里,邏輯卷管理器還不支持它。因此,如果BLKGETSIZE64 ioctl 調(diào)用失敗,mkfs 代碼將改為調(diào)用較早的 ioctl 調(diào)用;這使得 mkfs 適用于邏輯卷管理器。

OOPS

OOPS(也稱 Panic)消息包含系統(tǒng)錯誤的細(xì)節(jié),如 CPU 寄存器的內(nèi)容等。是內(nèi)核告知用戶有不幸發(fā)生的最常用的方式。

內(nèi)核只能發(fā)布OOPS,這個過程包括向終端上輸出錯誤消息,輸出寄存器保存的信息,并輸出可供跟蹤的回溯線索。通常,發(fā)送完OOPS之后,內(nèi)核會處于一種不穩(wěn)定的狀態(tài)。
OOPS的產(chǎn)生有很多可能原因,其中包括內(nèi)存訪問越界或非法的指令等。

※ 作為內(nèi)核的開發(fā)者,必定將會經(jīng)常處理OOPS。

※ OOPS中包含的重要信息,對所有體系結(jié)構(gòu)的機(jī)器都是完全相同的:寄存器上下文和回溯線索(回溯線索顯示了導(dǎo)致錯誤發(fā)生的函數(shù)調(diào)用鏈)。

ksymoops

在 Linux 中,調(diào)試系統(tǒng)崩潰的傳統(tǒng)方法是分析在發(fā)生崩潰時發(fā)送到系統(tǒng)控制臺的 Oops 消息。一旦您掌握了細(xì)節(jié),就可以將消息發(fā)送到 ksymoops 使用程序,它將試圖將代碼轉(zhuǎn)換為指令并將堆棧值映射到內(nèi)核符號。

※ 如:回溯線索中的地址,會通過ksymoops轉(zhuǎn)化成名稱可見的函數(shù)名。

ksymoops需要幾項內(nèi)容:Oops 消息輸出、來自正在運行的內(nèi)核的 System.map 文件,還有 /proc/ksyms、vmlinux和/proc/modules。

關(guān)于如何使用 ksymoops,內(nèi)核源代碼
/usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手冊頁上有完整的說明可以參考。Ksymoops 返回編代碼部分,指出發(fā)生錯誤的指令,并顯示一個跟蹤部分表明代碼如何被調(diào)用。
首先,將 Oops 消息保存在一個文件中以便通過 ksymoops 使用程序運行它。清單 7 顯示了由安裝 JFS 文件系統(tǒng)的 mount命令創(chuàng)建的 Oops 消息。

清單 7. ksymoops 處理后的 Oops 消息

ksymoops 2.4.0 on i686 2.4.17. Options used... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference atvirtual address 0000000... 15:59:37 sfb1 kernel: c01588fc... 15:59:37 sfb1 kernel: *pde = 0000000... 15:59:37 sfb1 kernel: Oops: 0000... 15:59:37 sfb1 kernel: CPU: 0... 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704]... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688] [get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208][do_page_fault+0/1264]... 15:59:37 sfb1 kernel: Call Trace: []...... 15:59:37 sfb1 kernel: [>EIP; c01588fc <=====...Trace; c0106cf3 33/40>Code; c01588fc 00000000 <_EIP>:Code; c01588fc <===== 0: 8b 2d 00 00 00 00 mov 0x0,%ebp <=====Code; c0158902 42/2c0> 6: 55 push %ebp

接下來,您要確定 jfs_mount 中的哪一行代碼引起了這個問題。Oops 消息告訴我們問題是由位于偏移地址 3c 的指令引起的。做這件事的辦法之一就是對 jfs_mount.o 文件使用 objdump 使用程序,然后查看偏移地址 3c。Objdump 用來反匯編模塊函數(shù),看看您的 C 源代碼會產(chǎn)生什么匯編指令。清單 8 顯示了使用 objdump 后您將看到的內(nèi)容,接著,我們查看jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移地址 3c 之所以這么重要,是因為 Oops 消息將該處標(biāo)識為引起問題的位置。

清單 8. jfs_mount 匯編程序清單

109 printk('%d ',*ptr);objdump jfs_mount.ojfs_mount.o: file format elf32-i386Disassembly of section .text:00000000 : 0:55 push %ebp ... 2c: e8 cf 03 00 00 call 400 31: 89 c3 mov %eax,%ebx 33: 58 pop %eax 34: 85 db test %ebx,%ebx 36: 0f 85 55 02 00 00 jne 291 0x291> 3c: 8b 2d 00 00 00 00 mov 0x0,%ebp << problem line above 42: 55 push %ebp

kallsyms

開發(fā)版2.5內(nèi)核引入了kallsyms特性,它可以通過定義CONFIG_KALLSYMS編譯選項啟用。該選項可以載入內(nèi)核鏡像所對應(yīng)的內(nèi)存地址的符號名稱(即函數(shù)名),所以內(nèi)核可以打印解碼之后的跟蹤線索。相應(yīng),解碼OOPS也不再需要System.map和ksymoops工具了。另外, 這樣做,會使內(nèi)核變大些,因為地址對應(yīng)符號名稱必須始終駐留在內(nèi)核所在內(nèi)存上。

#cat /proc/kallsyms c0100240 T _stext c0100240 t run_init_process c0100240 T stext c0100269 t init …

Kdump

什么是 kexec ?

Kexec 是實現(xiàn) kdump 機(jī)制的關(guān)鍵,它包括 2 一是組成部分:一是內(nèi)核空間的系統(tǒng)調(diào)用 kexec_load,負(fù)責(zé)在生產(chǎn)內(nèi)核(production kernel 或 first kernel)啟動時將捕獲內(nèi)核(capture kernel 或 sencond kernel)加載到指定地址。二是用戶空間的工具 kexec-tools,他將捕獲內(nèi)核的地址傳遞給生產(chǎn)內(nèi)核,從而在系統(tǒng)崩潰的時候能夠找到捕獲內(nèi)核的地址并運行。沒有 kexec 就沒有 kdump。先有 kexec 實現(xiàn)了在一個內(nèi)核中可以啟動另一個內(nèi)核,才讓 kdump 有了用武之地。kexec 原來的目的是為了節(jié)省時間 kernel 開發(fā)人員重啟系統(tǒng)的時間,誰能想到這個“偷懶”的技術(shù)卻孕育了最成功的內(nèi)存轉(zhuǎn)存機(jī)制呢?

什么是 kdump ?

Kdump 的概念出現(xiàn)在 2005 左右,是迄今為止最可靠的內(nèi)核轉(zhuǎn)存機(jī)制,已經(jīng)被主要的 linux? 廠商選用。kdump是一種先進(jìn)的基于 kexec 的內(nèi)核崩潰轉(zhuǎn)儲機(jī)制。當(dāng)系統(tǒng)崩潰時,kdump 使用 kexec 啟動到第二個內(nèi)核。第二個內(nèi)核通常叫做捕獲內(nèi)核,以很小的內(nèi)存啟動以捕獲轉(zhuǎn)儲鏡像。第一個內(nèi)核保留了內(nèi)存的一部分給第二個內(nèi)核啟動用。由于 kdump 利用 kexec 啟動捕獲內(nèi)核,繞過了 BIOS,所以第一個內(nèi)核的內(nèi)存得以保留。這是內(nèi)核崩潰轉(zhuǎn)儲的本質(zhì)。

kdump 需要兩個不同目的的內(nèi)核,生產(chǎn)內(nèi)核和捕獲內(nèi)核。生產(chǎn)內(nèi)核是捕獲內(nèi)核服務(wù)的對象。捕獲內(nèi)核會在生產(chǎn)內(nèi)核崩潰時啟動起來,與相應(yīng)的 ramdisk 一起組建一個微環(huán)境,用以對生產(chǎn)內(nèi)核下的內(nèi)存進(jìn)行收集和轉(zhuǎn)存。

如何使用 kdump

構(gòu)建系統(tǒng)和 dump-capture 內(nèi)核,此操作有 2 種方式可選:

1)構(gòu)建一個單獨的自定義轉(zhuǎn)儲捕獲內(nèi)核以捕獲內(nèi)核轉(zhuǎn)儲;

2) 或者將系統(tǒng)內(nèi)核本身作為轉(zhuǎn)儲捕獲內(nèi)核,這就不需要構(gòu)建一個單獨的轉(zhuǎn)儲捕獲內(nèi)核。

方法(2)只能用于可支持可重定位內(nèi)核的體系結(jié)構(gòu)上;目前 i386,x86_64,ppc64 和 ia64 體系結(jié)構(gòu)支持可重定位內(nèi)核。構(gòu)建一個可重定位內(nèi)核使得不需要構(gòu)建第二個內(nèi)核就可以捕獲轉(zhuǎn)儲。但是可能有時想構(gòu)建一個自定義轉(zhuǎn)儲捕獲內(nèi)核以滿足特定要求。

如何訪問捕獲內(nèi)存

在內(nèi)核崩潰之前所有關(guān)于核心映像的必要信息都用 ELF 格式編碼并存儲在保留的內(nèi)存區(qū)域中。ELF 頭所在的物理地址被作為命令行參數(shù)(fcorehdr=)傳遞給新啟動的轉(zhuǎn)儲內(nèi)核。

在 i386 體系結(jié)構(gòu)上,啟動的時候需要使用物理內(nèi)存開始的 640K,而不管操作系統(tǒng)內(nèi)核轉(zhuǎn)載在何處。因此,這個640K 的區(qū)域在重新啟動第二個內(nèi)核的時候由 kexec 備份。

在第二個內(nèi)核中,“前一個系統(tǒng)的內(nèi)存”可以通過兩種方式訪問:

1) 通過 /dev/oldmem 這個設(shè)備接口。

一個“捕捉”設(shè)備可以使用“raw”(裸的)方式 “讀”這個設(shè)備文件并寫出到文件。這是關(guān)于內(nèi)存的 “裸”的數(shù)據(jù)轉(zhuǎn)儲,同時這些分析 / 捕捉工具應(yīng)該足夠“智能”從而可以知道從哪里可以得到正確的信息。ELF 文件頭(通過命令行參數(shù)傳遞過來的 elfcorehdr)可能會有幫助。

2) 通過 /proc/vmcore。

這個方式是將轉(zhuǎn)儲輸出為一個 ELF 格式的文件,并且可以使用一些文件拷貝命令(比如 cp,scp 等)將信息讀出來。同時,gdb 可以在得到的轉(zhuǎn)儲文件上做一些調(diào)試(有限的)。這種方式保證了內(nèi)存中的頁面都以正確的途徑被保存 ( 注意內(nèi)存開始的 640K 被重新映射了 )。

kdump 的優(yōu)勢

1) 高可靠性

崩潰轉(zhuǎn)儲數(shù)據(jù)可從一個新啟動內(nèi)核的上下文中獲取,而不是從已經(jīng)崩潰內(nèi)核的上下文。

2) 多版本支持

LKCD(Linux Kernel Crash Dump),netdump,diskdump 已被納入 LDPs(Linux Documen-tation Project) 內(nèi)核。SUSE 和 RedHat 都對 kdump 有技術(shù)支持。

配置 kdump

安裝軟件包和實用程序

Kdump 用到的各種工具都在 kexec-tools 中。kernel-debuginfo 則是用來分析 vmcore 文件。從 rhel5 開始,kexec-tools 已被默認(rèn)安裝在發(fā)行版。而 novell 也在 sles10 發(fā)行版中把 kdump 集成進(jìn)來。所以如果使用的是rhel5 和 sles10 之后的發(fā)行版,那就省去了安裝 kexec-tools 的步驟。而如果需要調(diào)試 kdump 生成的 vmcore文件,則需要手動安裝 kernel-debuginfo 包。檢查安裝包操作:

3.3.2 參數(shù)相關(guān)設(shè)置 uli13lp1:/ # rpm -qa|grep kexec kexec-tools-2.0.0-53.43.10 uli13lp1:/ # rpm -qa 'kernel*debuginfo*' kernel-default-debuginfo-3.0.13-0.27.1 kernel-ppc64-debuginfo-3.0.13-0.27.1

系統(tǒng)內(nèi)核設(shè)置選項和轉(zhuǎn)儲捕獲內(nèi)核配置選擇在《使用 Crash 工具分析 Linux dump 文件》一文中已有說明,在此不再贅述。僅列出內(nèi)核引導(dǎo)參數(shù)設(shè)置以及配置文件設(shè)置。

1) 修改內(nèi)核引導(dǎo)參數(shù),為啟動捕獲內(nèi)核預(yù)留內(nèi)存

通過下面的方法來配置 kdump 使用的內(nèi)存大小。添加啟動參數(shù)'crashkernel=Y@X',這里,Y 是為 kdump 捕捉內(nèi)核保留的內(nèi)存,X 是保留部分內(nèi)存的開始位置。

對于 i386 和 x86_64, 編輯 /etc/grub.conf, 在內(nèi)核行的最后添加'crashkernel=128M' 。

對于 ppc64,在 /etc/yaboot.conf 最后添加'crashkernel=128M'。

在 ia64, 編輯 /etc/elilo.conf,添加'crashkernel=256M'到內(nèi)核行。

2) kdump 配置文件

kdump 的配置文件是 /etc/kdump.conf(RHEL6.2);/etc/sysconfig/kdump(SLES11 sp2)。每個文件頭部都有選項說明,可以根據(jù)使用需求設(shè)置相應(yīng)的選項。

啟動 kdump 服務(wù)

在設(shè)置了預(yù)留內(nèi)存后,需要重啟機(jī)器,否則 kdump 是不可使用的。啟動 kdump 服務(wù):

Rhel6.2:

# chkconfig kdump on # service kdump status Kdump is operational # service kdump start

SLES11SP2:

# chkconfig boot.kdump on # service boot.kdump start

測試配置是否有效

可以通過 kexec 加載內(nèi)核鏡像,讓系統(tǒng)準(zhǔn)備好去捕獲一個崩潰時產(chǎn)生的 vmcore??梢酝ㄟ^ sysrq 強(qiáng)制系統(tǒng)崩潰。

# echo c > /proc/sysrq-trigger

這造成內(nèi)核崩潰,如配置有效,系統(tǒng)將重啟進(jìn)入 kdump 內(nèi)核,當(dāng)系統(tǒng)進(jìn)程進(jìn)入到啟動 kdump 服務(wù)的點時,vmcore 將會拷貝到你在 kdump 配置文件中設(shè)置的位置。RHEL 的缺省目錄是 : /var/crash;SLES 的缺省目錄是 : /var/log/dump。然后系統(tǒng)重啟進(jìn)入到正常的內(nèi)核。一旦回復(fù)到正常的內(nèi)核,就可以在上述的目錄下發(fā)現(xiàn) vmcore 文件,即內(nèi)存轉(zhuǎn)儲文件。可以使用之前安裝的 kernel-debuginfo 中的 crash 工具來進(jìn)行分析(crash 的更多詳細(xì)用法將在本系列后面的文章中有介紹)。

# crash /usr/lib/debug/lib/modules/2.6.17-1.2621.el5/vmlinux /var/crash/2006-08-23-15:34/vmcore crash> bt

載入“轉(zhuǎn)儲捕獲”內(nèi)核

需要引導(dǎo)系統(tǒng)內(nèi)核時,可使用如下步驟和命令載入“轉(zhuǎn)儲捕獲”內(nèi)核:

kexec -p --initrd=for-dump-capture-kernel> --args-linux --append='root= init 1 irqpoll'

裝載轉(zhuǎn)儲捕捉內(nèi)核的注意事項:

轉(zhuǎn)儲捕捉內(nèi)核應(yīng)當(dāng)是一個 vmlinux 格式的映像(即是一個未壓縮的 ELF 映像文件),而不能是 bzImage 格式;

默認(rèn)情況下,ELF 文件頭采用 ELF64 格式存儲以支持那些擁有超過 4GB 內(nèi)存的系統(tǒng)。但是可以指定“--elf32-core-headers”標(biāo)志以強(qiáng)制使用 ELF32 格式的 ELF 文件頭。這個標(biāo)志是有必要注意的,一個重要的原因就是:當(dāng)前版本的 GDB 不能在一個 32 位系統(tǒng)上打開一個使用 ELF64 格式的 vmcore 文件。ELF32 格式的文件頭不能使用在一個“沒有物理地址擴(kuò)展”(non-PAE)的系統(tǒng)上(即:少于 4GB 內(nèi)存的系統(tǒng));

一個“irqpoll”的啟動參數(shù)可以減低由于在“轉(zhuǎn)儲捕獲內(nèi)核”中使用了“共享中斷”技術(shù)而導(dǎo)致出現(xiàn)驅(qū)動初始化失敗這種情況發(fā)生的概率 ;

必須指定 ,指定的格式是和要使用根設(shè)備的名字。具體可以查看 mount 命令的輸出;“init 1”這個命令將啟動“轉(zhuǎn)儲捕捉內(nèi)核”到一個沒有網(wǎng)絡(luò)支持的單用戶模式。如果你希望有網(wǎng)絡(luò)支持,那么使用“init 3”。

后記

Kdump 是一個強(qiáng)大的、靈活的內(nèi)核轉(zhuǎn)儲機(jī)制,能夠在生產(chǎn)內(nèi)核上下文中執(zhí)行捕獲內(nèi)核是非常有價值的。本文僅介紹在 RHEL6.2 和 SLES11 中如何配置 kdump。望拋磚引玉,對閱讀本文的讀者有益。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国产又黄又爽又粗视频在线| 国产麻豆成人精品区在线观看| 欧美日韩三区在线观看| 欧美国产日产综合精品| 97人妻精品一区二区三区男同 | 最新国产欧美精品91| 亚洲第一香蕉视频在线| 中文字幕欧美视频二区| 又黄又色又爽又免费的视频| 五月天婷亚洲天婷综合网| 国产日韩欧美国产欧美日韩| 一区二区三区日本高清| 日韩人妻一区中文字幕| 日本欧美三级中文字幕| 成人午夜视频精品一区| 激情国产白嫩美女在线观看| 91精品视频免费播放| 欧美日韩亚洲国产综合网| 欧美加勒比一区二区三区| 欧美精品在线观看国产| 99久久免费中文字幕| 国产精品久久久久久久久久久痴汉| 日韩欧美一区二区亚洲| 午夜激情视频一区二区| 男人把女人操得嗷嗷叫| 日韩中文字幕视频在线高清版| 日韩无套内射免费精品| 男女午夜福利院在线观看| 国产精品亚洲二区三区| 日韩av生活片一区二区三区| 亚洲国产精品久久综合网| 午夜视频免费观看成人| 亚洲最新av在线观看| 久久re6热在线视频| 91福利视频日本免费看看| 老司机精品线观看86| 欧美丰满大屁股一区二区三区| 成人亚洲国产精品一区不卡| 中文字幕乱码亚洲三区| 老司机精品视频在线免费看| 国产无摭挡又爽又色又刺激|