深入 kernel panic 流程

2021-08-04 06:06:44 字數 4512 閱讀 3667

我們在專案開發過程中,很多時候會出現由於某種原因經常會導致手機系統宕機重啟的情況(重啟分android重啟跟kernel重啟,而我們這裡只討論kernel重啟也就是kernel panic的情況),宕機重啟基本算是影響最嚴重的系統問題了,有穩定復現的,也有概率出現的,解題難度也千差萬別,出現問題後,通常我們會拿到類似這樣的kernel log資訊(下面log僅以呼叫bug()為例,其它異常所致的宕機log資訊會有一些不同之處):

.35+ #3

[ 3.052242] <2>sp : df04bd30 ip : 00000000 fp : df04bd4c

這是linux 核心在宕機之前輸出的相關重要資訊,包括pc指標、呼叫棧等在內的非常重要的便於debug的線索,比如我們可以借助gun tools(add2line)工具結合核心符號對映表vmlinux來定位當前pc指標所在的**具體行數(定位到出錯**行並不意味著就找到了問題的根本原因跟修復異常,這個需要根據異常的複雜程度而論)。深入理解這些關鍵列印log資訊的含義和機制非常有助於我們對於此類宕機問題的定位和分析(對於記憶體被踩、硬體不穩定導致的一類問題分析有侷限性),這也是我們需要深入學習核心異常流程的初衷。

這裡我們必須弄清楚幾個問題

。這些宕機前留下的關鍵register資訊是怎麼來的,有什麼用,具體含義是什麼?

。如何利用這些遺留的線索找到出問題**具體在哪支檔案,在哪一行?

。核心發生致命異常到宕機的總流程是怎樣的,類似宕機問題應該如何著手分析?

為此,本文就從最常見的主動觸發bug()為例解析上面的疑問及分析整個kernel panic流程。

有過驅動除錯經驗的人肯定都知道這個東西,這裡的bug跟我們一般認為的「軟體缺陷」可不是一回事,這裡說的bug()其實是linux kernel中用於攔截核心程式超出預期的行為,屬於軟體主動匯報異常的一種機制。這裡有個疑問,就是什麼時候會用到呢?一般來說有兩種用到的情況,一是軟體開發過程中,若發現**邏輯出現致命fault後就可以呼叫bug()讓kernel死掉(類似於assert),這樣方便於定位問題,從而修正**執行邏輯;另外一種情況就是,由於某種特殊原因(通常是為了debug而需抓ramdump),我們需要系統進入kernel panic的情況下使用.

。原形是什麼?

bug()跟bug_on(1)其實本質是一回事,後者只是在前者的基礎上做了簡單的封裝而已,bug()的實現 本質是埋入一條未定義指令:0xe7f001f2,觸發arm發起undefined instruction異常(ps:arm有分10種異常型別,詳細可以複習arm異常模型章節).

3.18/arch/arm/include/asm/bug.h>

#define bug_instr_value 0xe7f001f2

#define bug_instr(__value) __inst_arm(__value)

#define bug() _bug(__file__, __line__, bug_instr_value)

#define _bug(file, line, value) __bug(file, line, value)

#define __bug(__file, __line, __value) \

do while (0)

。bug()到系統重啟的總流程圖呼叫bug()會向cpu下發一條未定義指令而觸發arm發起未定義指令異常,隨後進入kernel異常處理流程歷經 oops,die(),__die()等流程輸出用於除錯分析的關鍵線索,最後進入panic()結束自己再獲得重生的過程,這個就是整個過程的基本流程,下面先來看die()具體做了什麼呢?

void

die(const

char *str, struct pt_regs *regs, int err)

總流程大致如下: 

通常來說,**分析過程結合kernel log一起看會理解來得更加深刻,如果是bug()/bug_on(1)導致的異常,那麼走到report_bug 就可以看到下面標誌性 log:

enum bug_trap_type report_bug

(unsigned

long bugaddr, struct pt_regs *regs)

; returnatomic_notifier_call_chain(&die_chain, val, &args);

}

mtk的aee異常引擎在kernel初始化的時候會去註冊到die_chain通知鏈,而且我們可以看到其實還註冊了panic通知鏈.

int __initaee_ipanic_init(void)

而對我們除錯追蹤有用的關鍵資訊是在__show_regs() 裡面列印的:

void __show_regs(struct pt_regs *regs)

void dump_stack_print_info(const char *log_lvl)

} printk("%scode: %s\n", lvl, str);

..===>

(e7f001f2)

看到這個e7f001f2了吧,是不是很眼熟?這個就是bug()中埋入的未定義指令!

到這一步,大部分關鍵資訊都已經輸出了,可以通過add2line工具定位出具體死在的**行號,大致看看發生了什麼,如果是bug()導致的異常,那麼就可以考慮分析和修復異常了,因為bug()屬於主動匯報異常,一般來說debug難度會相對其它的被動上報方式容易得多.

例如

從上面log知pc死在的位址 <c04289dc>,通過add2line工具結合核心符號對映表vmlinux就可以定位出具體**所在檔案行號:

arm-linux-androideabi-addr2line -e out/target/product/$project/obj/kernel_obj/vmlinux -f -c c04289dc

ltr553_i2c_probe

/aosp/kernel-3.18

/drivers/misc/mediatek/alsps/ltr553/ltr553.c:

3278

定位到了具體**行號就可以進一步分析**log找出問題原因修復異常了(

一般來說bug()導致的異常比較好解,其它的情況難度就是天差地別了..)。 那麼接下來kernel要幹什麼呢?重要資訊都輸出完了接下來就直接走 kernel panic 流程了.

panic本意是「恐慌」的意思,這裡意旨kernel發生了致命錯誤導致無法繼續執行下去的情況.

流程圖:

相關重要的debug資訊已經在之前的__die()流程輸出完成了,panic()其實要幹的主要事情就是讓系統先死掉再重生,kernel panic有標誌性的log列印,可以作為是否發生panic的搜尋關鍵字.

雖然主要的工作就是讓系統復位,但在去的路上還是會做一些事情,盡可能的不遺餘力給事後分析提供線索,比如atomic_notifier_call_chain()會去遍歷panic_notifier_list鍊錶,依次通知對panic感興趣的模組(

比如mtk的aee機制在這裡會把異常的一小塊記憶體給dump處理放到expdb分割槽,不過後面新的機制是改在reboot後再來做了

)做一些事情,如果開啟了ramdump支援就直接陷入download模式,抓取ramdump供離線分析用,單從這塊來講mtk/qcom平台流程差不多.

[    9.772431] rebooting in 1 seconds..
另外還要說的一點是,以上所有的分析都是基於log資訊的分析,簡單易行,這是系統異常除錯中最基本也是最重要的分析手段,對於bug()導致的問題通常可以比較順利的分析解決,

但是也有

其侷限性

,比如記憶體被踩、硬體不穩定導致的概率宕機等型別問題分析起來就往往很吃力,而這就需要借助ramdump分析手段才能進一步比較順利的分析解題.

kernel panic 除錯方法 1

kernel panic 表示 linux kernel 走到了乙個不知道該怎麼走下一步的狀況,一 旦到這個情況,kernel 就盡可能把它此時能獲取的全部資訊都列印出來,至於能列印出多少 資訊。下面講解幾種用於查詢出錯函式定位的方法 首先看一下出錯的kernel panic 現象,下面是乙個ker...

12 如何分析kernel panic?

description 當kernel發生異常時,會在重啟後生成對應的db,用gat的logviewer可以解開,如果是普通的ke或hwt,並且存在sys mini rdump或者sys coredump,則可以借助gdb crash進一步debug,否則只能檢視log分析問題的可能性了。solut...

關於linux啟動kernel panic錯誤解決

重灌系統未免太麻煩現推薦兩個解決方案 1.系統啟動的時候,按下 e 鍵進入grub編輯介面,編輯grub選單,選擇 kernel vmlinuz 2.6.23.1 42.fc8 ro root dev vogroup00 logvol00 rhgb quiet 一欄,按 e 鍵進入編輯,在末尾增加e...