一文解決內(nèi)核調(diào)試之Kdump的原理及配置

生產(chǎn)內(nèi)核(production kernel):產(chǎn)品或者線上服務(wù)器當(dāng)前運(yùn)行的內(nèi)核。
捕獲內(nèi)核(capture kernel):系統(tǒng)崩潰時(shí),使用kexec啟動(dòng)的內(nèi)核,該內(nèi)核用于捕獲生產(chǎn)內(nèi)核當(dāng)前內(nèi)存中的運(yùn)行狀態(tài)和數(shù)據(jù)信息。

Kdump是系統(tǒng)崩潰的時(shí)候,用來(lái)轉(zhuǎn)儲(chǔ)運(yùn)行內(nèi)存的一個(gè)工具。系統(tǒng)一旦崩潰,生產(chǎn)內(nèi)核就沒(méi)法正常工作,這時(shí)Kdump啟動(dòng)捕獲內(nèi)核,將此時(shí)內(nèi)存中的所有運(yùn)行狀態(tài)和數(shù)據(jù)信息收集到一個(gè)dump core文件中,以便之后分析崩潰原因。一旦內(nèi)存信息收集完成,系統(tǒng)將自動(dòng)重啟。

Kdump的核心實(shí)現(xiàn)基于Kexec(Kernel execution),kexec類(lèi)似于Linux的exec系統(tǒng)調(diào)用。
Kexec可以快速啟動(dòng)一個(gè)新的內(nèi)核(捕獲內(nèi)核),它會(huì)跳過(guò)BIOS或者Bootloader等引導(dǎo)程序的初始化階段,這個(gè)特性可以讓系統(tǒng)崩潰時(shí)快速切換到捕獲內(nèi)核,這樣生產(chǎn)內(nèi)核的內(nèi)存就得到保留,Kdump的工作流程如下圖所示:

捕獲內(nèi)核啟動(dòng)后,會(huì)像一般內(nèi)核一樣,去運(yùn)行為它創(chuàng)建的ramdisk上的init程序。而各種轉(zhuǎn)儲(chǔ)機(jī)制都可以事先在init中實(shí)現(xiàn)。
為了在生產(chǎn)內(nèi)核崩潰時(shí)能順利啟動(dòng)捕獲內(nèi)核,捕獲內(nèi)核以及它的ramdisk是事先放到生產(chǎn)內(nèi)核的內(nèi)存中的(保留內(nèi)存)。
生產(chǎn)內(nèi)核的內(nèi)存是通過(guò)/proc/vmcore這個(gè)文件交給捕獲內(nèi)核的,為了生成它,用戶(hù)工具在生產(chǎn)內(nèi)核中分析出內(nèi)存的使用和分布等情況,然后把這些信息綜合起來(lái)生成一個(gè)ELF頭文件保存起來(lái)。捕獲內(nèi)核被引導(dǎo)時(shí)會(huì)被同時(shí)傳遞這個(gè)ELF文件頭的地址,通過(guò)分析它,捕獲內(nèi)核就可以生成出/proc/vmcore。有了/proc/vmcore這個(gè)文件,捕獲內(nèi)核的ramdisk中的腳本就可以通過(guò)通常的文件讀寫(xiě)和網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)各種策略了。

Crash是一個(gè)用于分析內(nèi)核轉(zhuǎn)儲(chǔ)文件的工具,和Kdump配套使用。Kdump會(huì)在內(nèi)存中保留一塊區(qū)域,這個(gè)區(qū)域用于存放捕獲內(nèi)核,生產(chǎn)內(nèi)核運(yùn)行過(guò)程中崩潰時(shí),Kdump通過(guò)Kexec機(jī)制自動(dòng)啟動(dòng)捕獲內(nèi)核,對(duì)生產(chǎn)內(nèi)核的完整信息(CPU寄存器、棧幀數(shù)據(jù)等)進(jìn)行保存,生產(chǎn)內(nèi)核重啟后,使用Crash工具來(lái)分析這個(gè)轉(zhuǎn)儲(chǔ)的文件。

使用對(duì)象:Linux物理機(jī)或者虛擬機(jī);
使用場(chǎng)景:主要用于分析Linux系統(tǒng)崩潰問(wèn)題;注意,在系統(tǒng)無(wú)法熱啟動(dòng)的情況下,Kdump是不適用的,如硬件的異常導(dǎo)致CPU宕機(jī),也就是只能通過(guò)重新關(guān)閉開(kāi)啟電源才能啟動(dòng)的情況。

本實(shí)驗(yàn)所使用的環(huán)境為VirtualBox+Ubuntu18.04。
Ubuntu配置的內(nèi)核已經(jīng)使能Kdump的支持,僅需要安裝linux-crashdump包即可,運(yùn)行命令如下:
sudo apt install linux-crashdump
該軟件包含crash,kdump-tools,grub等相關(guān)依賴(lài),安裝中選擇默認(rèn)啟動(dòng),如下圖所示。

安裝過(guò)程中會(huì)修改kernel cmdline已預(yù)留kdump轉(zhuǎn)儲(chǔ)轉(zhuǎn)儲(chǔ)內(nèi)核內(nèi)存空間,可以通過(guò)修改/etc/default/grub.d/kdump-tools.cfg文件修改保留內(nèi)存的大小,默認(rèn)為192M,如下圖所示:

配置完成后更新grub(sudo update-grub),重啟操作系統(tǒng)(reboot)。
使用kdump-config show命令查看kdump相關(guān)配置,如下圖所示。

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??



使用如下命令簡(jiǎn)單快速測(cè)試:
echo c > /proc/sysrq-trigger
如果Kdump配置正確,上述命令會(huì)讓系統(tǒng)快速重啟并且啟動(dòng)捕獲內(nèi)核進(jìn)行轉(zhuǎn)儲(chǔ),轉(zhuǎn)儲(chǔ)完成后會(huì)自動(dòng)切換為生產(chǎn)內(nèi)核,在進(jìn)入生產(chǎn)內(nèi)核的操作系統(tǒng)后,查看/var/crash目錄中是否有根據(jù)年月日時(shí)分命名的文件夾,如下所示。

進(jìn)入時(shí)間對(duì)應(yīng)的目錄,目錄包含dmesg文件和dump文件,文件后綴為內(nèi)核崩潰的時(shí)間,其中dmesg是生產(chǎn)內(nèi)核發(fā)生崩潰時(shí)生成的內(nèi)核日志信息,dump時(shí)捕獲內(nèi)核轉(zhuǎn)儲(chǔ)的文件,如下圖所示。


打開(kāi)Crash工具進(jìn)行分析,Crash工具的使用方式如下:
crash [dump] [vmlinux]
Crash需要指定兩個(gè)文件:
dump:轉(zhuǎn)儲(chǔ)的內(nèi)核文件,通常在/var/crash目錄下;
vmlinux:帶調(diào)試內(nèi)核符號(hào)信息的內(nèi)核映像。
注意,Ubuntu系統(tǒng)默認(rèn)不包含vmlinux文件,筆者已經(jīng)對(duì)Ubuntu系統(tǒng)的內(nèi)核進(jìn)行更換,更換過(guò)程中生成帶調(diào)試符號(hào)的vmlinux文件,具體步驟可以關(guān)注《話說(shuō)Linux》訂閱號(hào),查看文章《Linux內(nèi)核更換》。
crash啟動(dòng)dump文件界面如下。


9.1 help命令
help命令用于在線查看crash命令的幫助,在crash命令行中輸入help命令可以查看crash支持的所有子命令。

help命令不僅可以查看crash支持的命令,還可以查看某個(gè)子命令的幫助說(shuō)明,如查看bt命令的幫助說(shuō)明。

9.2 bt命令
bt命令查看一個(gè)進(jìn)程的內(nèi)核棧的函數(shù)調(diào)用關(guān)系,包括所有異常棧的信息,常用參數(shù)如下。
-f:顯示每一棧幀里的數(shù)據(jù)。
-l:顯示文件名和行號(hào)。
-t:顯示棧中所有的文本符號(hào)。
pid:顯示指定pid的進(jìn)程的內(nèi)核棧的函數(shù)調(diào)用信息。

上圖可以看到,bt命令將系統(tǒng)崩潰瞬間正在運(yùn)行的進(jìn)程內(nèi)核棧信息全部顯示出來(lái)。當(dāng)前進(jìn)程的id是4265,task_struct數(shù)據(jù)結(jié)構(gòu)的地址是0xffff8d026b198000,當(dāng)前進(jìn)程運(yùn)行在CPU1上,當(dāng)前進(jìn)程的運(yùn)行命令是bash。后面的棧幀列出了該進(jìn)程在內(nèi)核態(tài)的調(diào)用關(guān)系,執(zhí)行順序是自下而上。
9.3 mod命令
mod命令不僅可以用來(lái)顯示當(dāng)前系統(tǒng)加載的內(nèi)核模塊信息,還可以用來(lái)加載某個(gè)內(nèi)核模塊的符號(hào)信息和調(diào)試信息等,常用參數(shù)如下。
-s:加載某個(gè)內(nèi)核模塊的符號(hào)信息。
-S:從某個(gè)指定目錄加載所有內(nèi)核模塊的符號(hào)信息,默認(rèn)目錄是/lib/modules/目錄查找并加載內(nèi)核模塊的符號(hào)信息。
-d:刪除某個(gè)內(nèi)核模塊的符號(hào)信息。

9.4 dis命令
dis命令用來(lái)輸出反匯編結(jié)果,常用參數(shù)如下。
-l:顯示反匯編和對(duì)應(yīng)源代碼的行號(hào)。
-r:(反向)顯示從例程開(kāi)始到指定地址(包括指定地址)的所有指令。
-s:顯示對(duì)應(yīng)的源代碼。
如輸出sysrq_handle_crash()接口的反匯編結(jié)果。

以下是查看sysrq_handle_crash()接口源碼的信息。

9.5 rd命令
rd命令用來(lái)讀取內(nèi)存地址中的值,常用參數(shù)如下。
-a:顯示ASCII。
-d:顯示十進(jìn)制。
-p:讀取物理地址。
-s:顯示符號(hào)。
-32:顯示32位寬的值。
-64:顯示64位寬的值。
以下命令用于讀0xffff8d026b198000內(nèi)存中的值,并連續(xù)輸出16個(gè)內(nèi)存地址中的值。

9.6 struct命令
struct命令用于顯示內(nèi)核中數(shù)據(jù)結(jié)構(gòu)的定義或者具體的值,常用的參數(shù)如下。
struct_name:數(shù)據(jù)結(jié)構(gòu)的名稱(chēng)。
.member:數(shù)據(jù)結(jié)構(gòu)的某個(gè)成員。
-o:顯示成員在數(shù)據(jù)結(jié)構(gòu)中的偏移量。
以下命令用于顯示task_struct數(shù)據(jù)結(jié)構(gòu)的定義。

以下命令用于顯示task_struct數(shù)據(jù)結(jié)構(gòu)中每個(gè)成員的偏移量。

此外,struct命令后面還可以指定一個(gè)地址,用來(lái)按照數(shù)據(jù)結(jié)構(gòu)的格式顯示每個(gè)成員的值,例如已知task_struct數(shù)據(jù)結(jié)構(gòu)的地址為0xffff8d026b198000,因此可以通過(guò)struct命令查看該數(shù)據(jù)結(jié)構(gòu)中每個(gè)成員的值。
9.7 irq命令
irq命令用于顯示中斷的相關(guān)信息,常用參數(shù)如下。
index:顯示某個(gè)指定IRQ的信息。
-a:顯示中斷的親和性。
-b:顯示中斷的下半部信息。
-s:顯示系統(tǒng)中斷信息。

9.8 task命令
task命令用于顯示進(jìn)程的task_struct數(shù)據(jù)結(jié)構(gòu)和thread_info數(shù)據(jù)結(jié)構(gòu)的內(nèi)容。以下命令用于顯示task_struct數(shù)據(jù)結(jié)構(gòu)信息,其中-x表示以十六進(jìn)制顯示。

9.9 vm命令
vm命令用于顯示進(jìn)程地址空間的相關(guān)信息,常用參數(shù)如下。
-p:顯示虛擬地址和物理地址。
-m:顯示mm_struct數(shù)據(jù)結(jié)構(gòu)。
-v:顯示進(jìn)程中所有vma(vm_area_struct)數(shù)據(jù)結(jié)構(gòu)的值。
-R:搜索特定字符串或者數(shù)值。
以下命令用于顯示當(dāng)前進(jìn)程的虛擬地址空間信息。

9.10 kmem命令
kmem命令用來(lái)顯示系統(tǒng)內(nèi)存信息,常用參數(shù)如下。
-g:顯示page數(shù)據(jù)結(jié)構(gòu)里flagsde標(biāo)志位。
-i:顯示系統(tǒng)內(nèi)存使用情況。
-p:顯示每個(gè)頁(yè)面的使用情況。
-s:顯示slab使用情況。
-v:顯示vmalloc使用情況。
-z:顯示沒(méi)個(gè)zone使用情況。
-V:顯示系統(tǒng)vm_stat情況。

9.11 sym命令
sym命令用于解析內(nèi)核符號(hào)信息,常用選項(xiàng)如下所示。
-l:顯示所有符號(hào)信息。
-m:顯示某個(gè)內(nèi)核模塊的所有符號(hào)信息。
-q:查詢(xún)符號(hào)信息。
以下代碼用于查看oops模塊的所有符號(hào)信息。

9.12 list命令
list命令用來(lái)遍歷鏈表,并且可以輸出鏈表成員的值,常用的參數(shù)如下。
-h:指定鏈表頭的地址。
-s:用來(lái)輸出鏈表成員的值。
原文作者:話說(shuō)Linux
