韓大衛@吉林師範大學html
2014.12.10linux
轉載請代表出處編程
*****************************************************函數
關於內核報錯 「Unable to handle kernel paging request at virtual address」 的問題, 絕大多數都是因爲程序使用了不可用的指針而引發的, 定位這類問題的辦法很簡單,也但願個人描述足夠簡單實用. 工具
以我下面的一個實例說明:spa
epc :exception program counter , 異常程序計數器, ra : return address 返回地址.net
咱們能夠根據 「CPU 0 Unable to handle kernel paging request at virtual address 0000000000000078, epc == ffffffff805e96e8, ra == ffffffff80ec73d0」 找到epc的具體位置, 再根據具體的彙編程序定位出引發epc的具體緣由, 另外, 若有必須知道所有的調用路徑. 那麼重複定位epc的方法,根據call trace 逐步定位便可. 3d
在編譯linux 的時候, 會產生一個System.map, vmlinux, 以及vmlinux.o指針
咱們使用System.map 和vmlinux.o 便可. 由於vmlinux多是被特定壓縮工具壓縮過的(根據您的makefile), 沒法使用objdump工具作反彙編. htm
先打開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, 打開源代碼, 根據分析彙編代碼獲得的信息進行定位.