韩大卫@吉林师范大学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, 打开源代码, 根据分析汇编代码获得的信息进行定位.