目录
当我们的驱动程序报错时,如果我们编译内核的时候配置了CONFIG_FRAME_POINTER,那么报错后回答引出backTrace回溯信息,能够看到函数的调用关系,但是如果我们的内核没有配置CONFIG_FRAME_POINTER,那么报错时不会打印函数调用关系,这时候我们可以根据栈信息自己分析函数调用过程。

# ./firstdrvtest on
Unable to handle kernel paging request at virtual address 56000050
pgd = c3e78000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0 Not tainted (2.6.22.6 #48)
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [
sp : c3e69e88 ip : c3e69e98 fp : c3e69e94
r10: 00000000 r9 : c3e68000 r8 : c0490620
r7 : 00000000 r6 : 00000000 r5 : c3e320a0 r4 : c06a8300
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33e78000 DAC: 00000015
Process firstdrvtest (pid: 752, stack limit = 0xc3e68258)
pc= bf000018,那么根据内核的System.map可以看到这个地址不属于内核,那么这个pc属于 insmod的模块。
然后看加载的驱动程序的函数的地址范围,cat /proc/kallsyms > kallsyms.txt (内核函数、加载的函数的地址)kallsyms:k指内核,all所有的,syms符号。
从这些信息里找到一个相近的地址, 这个地址<=0xbf000018, 比如找到了:
bf000000 t first_drv_open [first_drv]
那么这个bf000018应该是属于first_drv这个模块。那么bf000018属于哪个函数呢。
反汇编first_drv.ko,得到
- first_drv.ko: file format elf32-littlearm
-
- Disassembly of section .text:
-
- 00000000
: - 0: e1a0c00d mov ip, sp
- 4: e92dd800 stmdb sp!, {fp, ip, lr, pc}
- 8: e24cb004 sub fp, ip, #4 ; 0x4
- c: e59f1024 ldr r1, [pc, #36] ; 38 <__mod_vermagic5>
- 10: e3a00000 mov r0, #0 ; 0x0
- 14: e5912000 ldr r2, [r1]
- 18: e5923000 ldr r3, [r2] // 在这里出错 r2=56000050
-
- 1c: e3c33c3f bic r3, r3, #16128 ; 0x3f00
- 20: e5823000 str r3, [r2]
- 24: e5912000 ldr r2, [r1]
- 28: e5923000 ldr r3, [r2]
- 2c: e3833c15 orr r3, r3, #5376 ; 0x1500
- 30: e5823000 str r3, [r2]
- 34: e89da800 ldmia sp, {fp, sp, pc}
- 38: 00000000 andeq r0, r0, r0
-
- 0000003c
: - 3c: e1a0c00d mov ip, sp
出错的时候就把栈信息打印出来,我们先从反汇编文件看一下栈信息,从
4: e92dd800 stmdb sp!, {fp, ip, lr, pc}
可以看到这是往栈sp里面存了四个值(注意stmdb意思是地址先减然后完成操作),那么fp ip lr pc这四个保存顺序是怎么样的,我们之前知道pc是r15寄存器的别名,lr是r14寄出器的别名,ip是r12寄存器的别名,fp是r11寄存器的别名,那么先存哪一个呢,先存pc,因为高地址保存高标号,所以先存r15,然后sp地址减4字节之后再存r14/lr,那么从下面打印的栈信息可以看到c008c888就是返回地址,这个地址就是first_drv_open调用者的地址,first_drv_open执行完之后就返回这里。那么c008c888是哪一个函数呢,这个是属于内核的函数,这时候要去反汇编内核。
然后在反汇编文件中搜索这个地址,发现属于chrdev_open函数,然后看到chrdev_open的栈,在这里面首先存了9个值,然后sp又减了4,所以一共10个值,而第8个是lr=c0088e48,这个就是调用者的地址,

然后同样看一下c0088e48 在哪里,发现属于__dentry_open函数,所以调用者是__dentry_open
然后这个__dentry_open函数栈占用了10*4个字节,然后lr寄存器是第9个,lr=c0088f64 ,这个值属于
然后这个nameidata_to_filp的栈占据6个位置,然后找到lr=c0088fb8 ,这就是调用者的地址,继续找,发现这个c0088fb8 属于
然后do_filp_open的栈占据(6+84/4)=27个位置,继续找到lr=c00892f4 ,这个地址属于
然后这个do_sys_open又占据11个位置,从来继续找到 lr= c00893a8,继续找到调用者是

然后sys_open的栈占4个位置,继续找到 lr = c002aea0 ,

- Stack: (0xc3e69e88 to 0xc3e6a000)
- 9e80: c3e69ebc c3e69e98 c008c888 bf000010 00000000 c0490620
- first_drv_open'sp lr chrdev_open'sp
-
- 9ea0: c3e320a0 c008c73c c0465e20 c3e36cb4 c3e69ee4 c3e69ec0 c0088e48 c008c74c
- lr
-
- 9ec0: c0490620 c3e69f04 00000003 ffffff9c c002b044 c06e0000 c3e69efc c3e69ee8
- __dentry_open'sp
-
- 9ee0: c0088f64 c0088d58 00000000 00000002 c3e69f68 c3e69f00 c0088fb8 c0088f40
- lr nameidata_to_filp'sp lr
-
- 9f00: c3e69f04 c3e36cb4 c0465e20 00000000 00000000 c3e79000 00000101 00000001
- do_filp_open'sp
-
- 9f20: 00000000 c3e68000 c04c1468 c04c1460 ffffffe8 c06e0000 c3e69f68 c3e69f48
- 9f40: c008916c c009ec70 00000003 00000000 c0490620 00000002 be94eee0 c3e69f94
- 9f60: c3e69f6c c00892f4 c0088f88 00008520 be94eed4 0000860c 00008670 00000005
- lr do_sys_open'sp
-
- 9f80: c002b044 4013365c c3e69fa4 c3e69f98 c00893a8 c00892b0 00000000 c3e69fa8
- lr sys_open'sp
-
- 9fa0: c002aea0 c0089394 be94eed4 0000860c 00008720 00000002 be94eee0 00000001
- lr ret_fast_syscall'sp
-
- 9fc0: be94eed4 0000860c 00008670 00000002 00008520 00000000 4013365c be94eea8
- 9fe0: 00000000 be94ee84 0000266c 400c98e0 60000010 00008720 00000000 00000000