kernel panic , Oops 等cpu異常的分析與定位

一、kernel panic

二、mips異常機制

三、linuxkernel 對mips異常的處理

四、kernel panic 實例分析

Kernel ?panic

內(nèi)核代碼,相比用戶層代碼更難以調(diào)試,在內(nèi)核程序開發(fā)上更要加倍小心和注意:有的BUG在內(nèi)核或內(nèi)核模塊運行時會導(dǎo)致系統(tǒng)崩潰。當(dāng)崩潰發(fā)生時,收集盡可能多的信息將有助于問題的解決。這就是內(nèi)核OOPS誕生的目的。

OOPS會顯示出CPU在崩潰時的狀態(tài),包括CPU寄存器和其它一些看起來難懂的信息。其實現(xiàn)代碼在內(nèi)核的arch\xxx\kernel\traps.c中。

在linux中對mips體系結(jié)構(gòu)代碼中,主要是通過異常處理的方式,在產(chǎn)生異常時將當(dāng)前的寄存器狀態(tài),線程狀態(tài),加載的模塊及函數(shù)調(diào)用信息,打印處理,為內(nèi)核debug提供盡可能多的信息。

mips異常機制

圖1
圖2

Exception 2/3:TLB Miss Load/Write

Miss Load/Write,如果試圖訪問沒有在MMU的TLB中映射的內(nèi)存地址,會觸發(fā)這個異常。在支持虛擬內(nèi)存的操作系統(tǒng)中,這會觸發(fā)內(nèi)存的頁面倒換,系統(tǒng)的Exception Handler會將所需要的內(nèi)存頁從虛擬內(nèi)存中調(diào)入物理內(nèi)存,并更新相應(yīng)的TLB表項。

Exception 4/5:Address Error Load/Write

如果試圖訪問一個非對齊的地址,例如lw/sw指令的地址非4字節(jié)對齊,或lh/sh的地址非2字節(jié)對齊,就會觸發(fā)這個異常。一般地,操作系統(tǒng)在Exception Handler中對這個異常的處理,是分開兩次讀取/寫入這個地址。雖然一般的操作系統(tǒng)內(nèi)核都處理了這個異常,最后能夠完成期待的操作,但是由于會引起用戶態(tài)到內(nèi)核態(tài)的切換,以及異常的退出,當(dāng)這樣非對齊操作較多時會嚴(yán)重影響程序的運行效率。因此,編譯器在定義局部和全局變量時,都會自動考慮到對齊的情況,而程序員在設(shè)計數(shù)據(jù)結(jié)構(gòu)時,則需要對對齊做特別的斟酌。

Exception 9:Break Point

絕對斷點指令。和syscall指令類似,它也是由專用指令break觸發(fā)的。它指示了系統(tǒng)的一些異常情況,編程人員可以在某些不應(yīng)當(dāng)出現(xiàn)的異常分支里面加入這個指令,便于及早發(fā)現(xiàn)問題和調(diào)試。我們可以用高級語言中的assert機制來類比理解它。最常見的Break異常的子類型為0x07,它是編譯器在編譯除法運算時自動加入的。如果除數(shù)為0則執(zhí)行一條break 0x07指令。這樣,當(dāng)出現(xiàn)被0除的情況時,系統(tǒng)就會拋出一個異常,并執(zhí)行Coredump,以便于程序員定位除0錯誤的根因。

內(nèi)核中的BUG()函數(shù)使用break指指令產(chǎn)生斷點異常

Mips 通用寄存器定義

圖3

Linux kernel 對mips異常的處理

/arch/mips/kernel/trap.c

/* 異常向量初始化 */
void __init trap_init(void)
/* 設(shè)置異常向量 */
void *set_except_vector(int n, void *addr)

異常向量的處理函數(shù):

TLBL/TLBS:

do_page_fault()
{
? ? ? ? printk(KERN_ALERT "CPU %d Unable to handle kernel paging request at ""virtual address %0*lx, epc == ? ? ? ? ? ? ? ? ? ? ?%0*lx, ra == %0*lx\n",raw_smp_processor_id(), field, address, field, regs->cp0_epc,? ? ? field,? regs- ? ? ? ? ? ? ? ? ? ?>regs[31]);
? ? ? ? die("Oops", regs);
}?

ADEL/ADES

Arch/mips/unaligned.c
do_ade(struct pt_regs *regs)
?{ ? ? …
? ? ? ?sigbus:
? ? ? ?die_if_kernel("Kernel unaligned instruction access", regs);
? ? ? ?force_sig(SIGBUS, current);
? ? ? ? …
}

BP

Arch/mips/kernel/trap.c
do_bp(struct pt_regs *regs)
?{
? ? ?do_trap_or_bp(regs, bcode, "Break");
}
do_trap_or_bp()
?{
? ? ?case BRK_BUG:
? ? ?die_if_kernel("Kernel bug detected", regs);
? ? ?force_sig(SIGTRAP, current);
? ? ? break;
}

die_if_kernel

?static inline void die_if_kernel(const char *str, const struct pt_regs *regs)
?{ ????????
? ? ? ?if (unlikely(!user_mode(regs)))
? ? ? ? ? ? ? ? ? ? die(str, regs);

}

die()

圖4

show_register

圖5

kernel panic 實例分析

epc:產(chǎn)生異常時的PC指針的值

ra:子程序的返回地址

cp0 cause

BadVA

我們重點看下epc的分析過程,工具鏈中提供了一系列的工具可以用于二進制的文件的分析,通過這些工具的使用我們可以定位到產(chǎn)生異常的代碼:

內(nèi)核需要開啟CFG_KALLSYM,在產(chǎn)生異常時,能夠打印出對應(yīng)的函數(shù)及相應(yīng)的偏移。函數(shù)地址加上偏移就是對應(yīng)的匯編指令地址。

由于EPC和堆棧中的函數(shù)地址信息都是裝載之后的地址,所以可以使用objdump -S 將相應(yīng)的.o文件反匯編獲取到函數(shù)在編譯時的靜態(tài)地址,從而確定函數(shù)地址。

addr2line 命令可以從.o文件中通過指令在代碼段中的偏移來確定對應(yīng)的C程序所在的行(需要在gcc編譯時加上-g選項)。

實例(實際信息比這多,當(dāng)前截取一部分):

圖6

我們看到當(dāng)前epc指針指向的地址為:xxx_state_context+0xb0/0x5624(偏移為0xb0),使用上面步驟:

1)反匯編命令:你的編譯工具鏈路徑/objdump -S 你的內(nèi)核vmlinx或模塊.ko >dump.txt

2)獲取函數(shù)地址:在dump.txt中查找xxx_state_context,找到該函數(shù)地址,假如為00000034<xxx_state_context>

3)獲取pc指針地址:00000034+0xb0=0x000000e4

4)定位到具體函數(shù)某一行,命令:

你的編譯工具鏈路徑/addr2line 0x000000e4 -e 你的內(nèi)核vmlinx或模塊.ko -f xxx_state_context

后續(xù)

1)找到產(chǎn)生異常的代碼僅僅只是開始,因為產(chǎn)生異常的原因有很多,kernel panic打印處理的系統(tǒng)信息能提供的僅僅是當(dāng)前的一些狀態(tài),更具體的分析需要結(jié)合代碼進行分析。

2)假如編譯工具鏈中有g(shù)db的話,可以更快捷的定位到具體代碼,執(zhí)行命令:

#你的編譯工具鏈gdb路徑/gdb 你的內(nèi)核vmlinx或模塊.ko

(gdb) b *xxx_state_context+0xb0

該指令將同樣可以定位到具體文件的某一行,有興趣的朋友可以試一下。

3)該文章提到的具體命令用法,請自行查閱文檔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容