標籤:epc linux kernel panic exception
韓大衛@吉林師範大學
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, 開啟原始碼, 根據分析彙編代碼得到的資訊進行定位.
希望以上資訊對您有協助.
根據epc 定位linux kernel panic 位置