韓大衛@吉林師範大學
2014.12.10
轉載請表明出處
*****************************************************
關于核心報錯 “Unable to handle kernel paging request at virtual address” 的問題, 絕大多數都是由于程式使用了不可用的指針而引起的, 定位這類問題的辦法很簡單,也希望我的描述足夠簡單實用.
以我下面的一個執行個體說明:
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, 查找這個位址, 絕大多數情況都可以找到, 我的如下:
看來問題是出在 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 的定義處:
可以看到, 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
如下圖:
找到第一個參數的類型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, 打開源代碼, 根據分析彙編代碼得到的資訊進行定位.
希望以上資訊對您有幫助.