天天看點

根據epc 定位linux kernel panic 位置

韓大衛@吉林師範大學

2014.12.10

轉載請表明出處

*****************************************************

    關于核心報錯 “Unable to handle kernel paging request at virtual address” 的問題, 絕大多數都是由于程式使用了不可用的指針而引起的, 定位這類問題的辦法很簡單,也希望我的描述足夠簡單實用. 

以我下面的一個執行個體說明:

根據epc 定位linux kernel panic 位置

epc :exception program counter  , 異常程式計數器,  ra : return address 傳回位址

我們可以根據 “CPU 0 Unable to handle kernel paging request at virtual address 0000000000000078, epc == ffffffff805e96e8, ra == ffffffff80ec73d0”  找到epc的具體位置,  再根據具體的彙程式設計式定位出引起epc的具體原因, 另外, 如有必須知道全部的調用路徑. 那麼重複定位epc的方法,根據call trace 逐漸定位即可. 

在編譯linux 的時候, 會産生一個System.map, vmlinux, 以及vmlinux.o

我們使用System.map 和vmlinux.o 即可. 因為vmlinux可能是被特定壓縮工具壓縮過的(根據您的makefile), 無法使用objdump工具做反彙編. 

先打開System.map:

将 epc == ffffffff805e96e8 裡的 ffffffff805e96e8 位址拷貝下來,  直接在System.map 裡面查找, 如果沒有找到, 那麼将ffffffff805e96e8 最後兩位删掉, 即ffffffff805e96,  查找這個位址, 絕大多數情況都可以找到, 我的如下:  

根據epc 定位linux kernel panic 位置

看來問題是出在 add_mtd_device 這個函數裡面. 

epc 的位置是在ffffffff805e96e8,  add_mtd_device 的位址是在ffffffff805e96c0 , 那麼應該是在add_mtd_device裡的ffffffff805e96e8 - ffffffff805e96c0 = 0x28 這個偏移位置出了問題. 

 現在我們需要觀察 vmlinux.o 的彙編代碼, 找到 add_mtd_device 函數的彙編,  觀察 0x28 位置的彙編語言. 

先使用xxx-objdump(xxx為具體的交叉編譯工具字首)  将vmlinux.o 反彙編出來, 我的做法是:

mips64-octeon-linux-gnu-objdump -dr vmlinux.o >> linux-dr

之後打開linux-dr 這個檔案,  找到 add_mtd_device 的定義處: 

根據epc 定位linux kernel panic 位置

可以看到, 0x28 位置的彙編:

28:   dc820078    ld  v0,120(a0)

ld v0, 120(a0) 的含義是:

先取寄存器a0的數值的位址, 再将該位址後120位元組處的數值加載到v0 寄存器.

(a0)是取a0寄存器的位址, a0是負責傳遞函數的第一個參數的寄存器.

dc820078  就是ld v0, 120(a0) 對應的機器碼.  

根據CPU 0 Unable to handle kernel paging request at virtual address 0000000000000078 這句話的提示可以知道,  

是在對a0的0x78(120) 位址取值的時候發生了錯誤,  很可能是a0位址本身不可用. 如果能确認的話, 就可以證明add_mtd_device的第一個參數使用一個不可用的指針.  

這時候就可以檢查源代碼,  相信您有能力很快到定位問題. 

但如果該函數很大,  不容易定位,  那麼我們可以通過120這個資訊定位到該函數裡具體的語句. 

我的執行個體:

打開linux核心源代碼,  計算120位元組在add_mtd_device() 第一個參數類型裡的位置, 得到參數成員:

vi -t  add_mtd_device

如下圖: 

根據epc 定位linux kernel panic 位置

找到第一個參數的類型struct mtd_info 定義, 通過逐漸計算每個成員偏移(注意填充位元組),  

可以算出第120位元組的成員為backing_dev_info. 那麼, 在代碼裡, 出現epc的程式就是第一個出現該成員的地方. 

如果這個偏移太大, 很難計算的話, 不妨在代碼裡, 在調用該函數前自定義一個該參數類型的變量, 

估計一個大概的成員, 計算他們的偏移, 在知道這個成員偏移量的基礎上, 再計算120的成員位置. 會容易一些. 

我的做法是:

struct mtd_info my = {0};  

unsigned long len = (unsigned long)&(my.backing_dev_info) - (unsigned long)&my;

printk("sizeof is = 0x%lu\n", len);

當然, 這需要重新開機裝置, 并load新編譯的linux. 

關于定位epc 位置, 總結一下:

1, 打開System.map, 找到epc之前的最近函數的位址.計算出epc距離該函數的偏移值. 

2, 使用objdump 找到該函數, 分析 epc 偏移處的彙編代碼. 

3, 打開源代碼, 根據分析彙編代碼得到的資訊進行定位. 

希望以上資訊對您有幫助. 

繼續閱讀