驱动调试方法
驱动调试方法
内核打印函数printk
调试内核、驱动最简单的方法就是使用printk函数打印信息;
printk函数的打印级别
printk函数与用户空间的printf函数格式完全相同,它所打印的字符串头部可以加入“
在内核代码:include/linux/kernel.h中,下面几个宏控制了printk函数所能输出的信息的记录级别:
#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
分别说明以上各宏的含义:
- 对于printk("
"....),只有n小于console_loglevel时,这个信息才会被打印; - 假设default_message_loglevel的值等于4,如果printk的参数开头没有“
”样式的字符,则在printk函数中进一步处理前会自动加上“<4>”; - minimum_console_loglevel是一个预设值,平时不起作用,当通过其他方式来设置console_loglevel的值时,这个值不能小于minimum_console_loglevel;
- default_console_loglevel是一个预设值,平时不起作用,它表示设置console_loglevel时的默认值,通过其他某种方式来设置console_loglevel的值时会用到这个值;
在用户空间修改printk函数的打印级别
当挂接proc文件系统后,读取/proc/sys/kernel/printk文件就可以得到console_loglevel、default_message_loglevel、minimum_console_loglevel以及default_console_loglevel各自的值;
比如执行命令:
cat /proc/sys/kernel/printk
7 4 1 7 //console_loglevel=7,default_message_loglevel=4
//minimum_console_loglevel=1,default_console_loglevel=7
/* 可以使用下面的命令修改console_loglevel=1,这样所有的printk信息都不会被打印 */
echo "1 4 1 7" > /proc/sys/kernel/printk
启动时打印信息需要耗费一定的时间,可以在传入内核的启动参数中设置loglevel=0,来修改打印级别,这样会屏蔽所有的打印信息(关于内核启动参数的设置可参考内核源码目录下的kernel-parameters.txt文件);
具体的打印级别有如下几种:
在内核代码:/include/linux/kernel.h;
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
log_buf[]中存放着所有的内核打印信息,至于这些信息是否打印出去要看对应的级别,可以使用dmesg命令打印出,临时log_buf[]中的数据打印出来,重现内核的输出信息;
Oops信息及栈回溯
当内核出错时(比如访问了非法地址)打印出来的信息被称为Oops信息;
Oops信息
Unable to handle kernel paging request at virtual address 56000050
pgd = c3ca0000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: jz2440_leds
CPU: 0 Not tainted (2.6.22.6 #4)
PC is at jz2440_led_drv_open+0x3c/0xd0 [jz2440_leds]
LR is at chrdev_open+0x14c/0x164
pc : [<bf00003c>] lr : [<c008d888>] psr: 80000013
sp : c073be88 ip : c073be98 fp : c073be94
r10: 00000000 r9 : c073a000 r8 : c04debe0
r7 : 00000000 r6 : 00000000 r5 : c3ea30c0 r4 : c06f06c0
r3 : 00000000 r2 : 56000050 r1 : bf000bc4 r0 : c3ea30c0
Flags: Nzcv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33ca0000 DAC: 00000015
Process ledtest (pid: 801, stack limit = 0xc073a258)
Stack: (0xc073be88 to 0xc073c000)
be80: c073bebc c073be98 c008d888 bf000010 00000000 c04debe0
bea0: c3ea30c0 c008d73c c0474da0 c3ee1dac c073bee4 c073bec0 c0089e48 c008d74c
bec0: c04debe0 c073bf04 00000003 ffffff9c c002c044 c3d06000 c073befc c073bee8
bee0: c0089f64 c0089d58 00000000 00000002 c073bf68 c073bf00 c0089fb8 c0089f40
bf00: c073bf04 c3ee1dac c0474da0 00000000 00000000 c3ca1000 00000101 00000001
bf20: 00000000 c073a000 c046d508 c046d500 ffffffe8 c3d06000 c073bf68 c073bf48
bf40: c008a16c c009fc70 00000003 00000000 c04debe0 00000002 00000004 c073bf94
bf60: c073bf6c c008a2f4 c0089f88 000085a0 becbced4 000086e8 0000874c 00000005
bf80: c002c044 4013365c c073bfa4 c073bf98 c008a3a8 c008a2b0 00000000 c073bfa8
bfa0: c002bea0 c008a394 becbced4 000086e8 becbcf92 00000002 00000004 becbcf92
bfc0: becbced4 000086e8 0000874c 00000003 000085a0 00000000 4013365c becbcea8
bfe0: 00000000 becbce80 0000266c 400c98e0 60000010 becbcf92 00000000 00000000
Backtrace:
[<bf000000>] (jz2440_led_drv_open+0x0/0xd0 [jz2440_leds]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
r8:c3ee1dac r7:c0474da0 r6:c008d73c r5:c3ea30c0 r4:c04debe0
[<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48)
[<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48)
r4:00000002
[<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4)
r5:00000004 r4:00000002
[<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28)
[<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
Code: bf00007c bf0000a0 e59f1090 e5912000 (e5923000)
Segmentation fault
上述Oops信息主要有:
-
一段文本描述信息;
比如类似“Unable to handle kernel paging request at virtual address 56000050”的信息,说明了发生的是哪类错误;
-
Oops信息的序号;
比如第1次、第2次等,类似“Internal error: Oops: 5 [#1]”,括号内的数字表示序号;
-
内核加载模块的名称,也可能没有,以“Modules linked in”开头的,类似“Modules linked in: jz2440_leds”;
-
发生错误的CPU的序号,对于单处理器的系统,序号为0,比如:
“CPU: 0 Not tainted (2.6.22.6 #4)”
-
发生错误时CPU的各个寄存器值,如下:
pc : [<bf00003c>] lr : [<c008d888>] psr: 80000013 sp : c073be88 ip : c073be98 fp : c073be94 r10: 00000000 r9 : c073a000 r8 : c04debe0 r7 : 00000000 r6 : 00000000 r5 : c3ea30c0 r4 : c06f06c0 r3 : 00000000 r2 : 56000050 r1 : bf000bc4 r0 : c3ea30c0
-
当前进程的名字及进程ID,比如“Process ledtest (pid: 801, stack limit = 0xc073a258)”;
这并不是说发生错误的就是这个进程,而是表示发生错误时,当前进程是它,错误可能发生在内核代码、驱动程序,也可能就是这个进程的错误;
-
栈信息;
Stack: (0xc073be88 to 0xc073c000) be80: c073bebc c073be98 c008d888 bf000010 00000000 c04debe0 bea0: c3ea30c0 c008d73c c0474da0 c3ee1dac c073bee4 c073bec0 c0089e48 c008d74c bec0: c04debe0 c073bf04 00000003 ffffff9c c002c044 c3d06000 c073befc c073bee8 bee0: c0089f64 c0089d58 00000000 00000002 c073bf68 c073bf00 c0089fb8 c0089f40 bf00: c073bf04 c3ee1dac c0474da0 00000000 00000000 c3ca1000 00000101 00000001 bf20: 00000000 c073a000 c046d508 c046d500 ffffffe8 c3d06000 c073bf68 c073bf48 bf40: c008a16c c009fc70 00000003 00000000 c04debe0 00000002 00000004 c073bf94 bf60: c073bf6c c008a2f4 c0089f88 000085a0 becbced4 000086e8 0000874c 00000005 bf80: c002c044 4013365c c073bfa4 c073bf98 c008a3a8 c008a2b0 00000000 c073bfa8 bfa0: c002bea0 c008a394 becbced4 000086e8 becbcf92 00000002 00000004 becbcf92 bfc0: becbced4 000086e8 0000874c 00000003 000085a0 00000000 4013365c becbcea8 bfe0: 00000000 becbce80 0000266c 400c98e0 60000010 becbcf92 00000000 00000000
-
栈回溯信息,可以看到函数的调用关系,如下:
Backtrace: [<bf000000>] (jz2440_led_drv_open+0x0/0xd0 [jz2440_leds]) from [<c008d888>] (chrdev_open+0x14c/0x164) [<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8) r8:c3ee1dac r7:c0474da0 r6:c008d73c r5:c3ea30c0 r4:c04debe0 [<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48) [<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48) r4:00000002 [<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4) r5:00000004 r4:00000002 [<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28) [<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
-
出错指令附近的指令的机器码,比如(出错指令在小括号内):
Code: bf00007c bf0000a0 e59f1090 e5912000 (e5923000)
分析Oops信息
-
从文本描述可直接看出错误原因,“Unable to handle kernel paging request at virtual address 56000050”,可知无法在虚拟地址56000050处理内核分页请求;
-
根据栈回溯找出函数调用关系
内核崩溃时,可以从pc寄存器得知崩溃发生时的函数、出错指令,但是很多情况下,错误有可能时它的调用者引入的,所以找出函数的调用关系也是很重要的;
由于内核配置了CONFIG_FRAME_POINTER,当出现Oops信息时,会打印栈回溯信息;
部分回溯信息如下:
[<bf000000>] (jz2440_led_drv_open+0x0/0xd0 [jz2440_leds]) from [<c008d888>] (chrdev_open+0x14c/0x164)
这行信息为两部分,表示后面的chrdev_open函数调用了前面的jz2440_led_drv_open函数;
前半部分含义为:“bf000000”是jz2440_led_drv_open函数首地址偏移0的地址,这个函数大小为0xd0;
后半部分含义为:“c008d888”是chrdev_open函数首地址偏移0x14c的地址,这个函数大小为0x164;
另外后半部分的“[
] ”表示jz2440_led_drv_open执行后的返回地址; 对于类似下面的栈回溯信息,其中寄存器值就表示jz2440_led_drv_open刚被调用时这些寄存器的值:
pc : [<bf00003c>] lr : [<c008d888>] psr: 80000013 sp : c073be88 ip : c073be98 fp : c073be94 r10: 00000000 r9 : c073a000 r8 : c04debe0 r7 : 00000000 r6 : 00000000 r5 : c3ea30c0 r4 : c06f06c0 r3 : 00000000 r2 : 56000050 r1 : bf000bc4 r0 : c3ea30c0
从上面的栈回溯信息可以知道内核出错时的函数调用关系如下,最后在jz2440_led_drv_open函数内部崩溃:
ret_fast_syscall -> sys_open -> do_sys_open -> do_filp_open -> nameidata_to_filp -> __dentry_open -> chrdev_open -> jz2440_led_drv_open
-
根据PC寄存器的值确定出错位置;
PC is at jz2440_led_drv_open+0x3c/0xd0 [jz2440_leds] LR is at chrdev_open+0x14c/0x164 pc : [<bf00003c>] lr : [<c008d888>] psr: 80000013
"PC is at jz2440_led_drv_open+0x3c/0xd0"表示出错指令为jz2440_led_drv_open函数中偏移为0x3c的指令;
“pc : [
]”表示出错指令的地址为0xbf00003c; 先判断这个指令地址属于内核还是外加模块的?
-
先查看内核编译之后内核源码目录下的System.map文件来确定内核的函数的地址范围为c0004000~c03cecb4,所以0xbf00003c不属于内核而是外加模块的;
-
查看cat /proc/kallsyms(所有内核函数、加载函数的地址)找到一个相近的地址来确定属于哪个模块;
由于这个文件比较大,所以可以cat /proc/kallsyms > kallsyms.txt,导出到kallsyms.txt查看;
可以找到“bf000000 t jz2440_led_drv_open [jz2440_leds]”,可以确定是在jz2440_leds.ko这个模块中;
-
找到对应的jz2440_leds.ko文件,反汇编"arm-linux-objdump -D jz2440_leds.ko > jz2440_leds.dis",查看反汇编文件找到对应的函数,再根据目前的PC指代的地址和找到的相近的地址算出偏移值,最终可定位到哪条语句发生了错误;
-
-
使用Oops信息手工进行栈回溯;
一个程序包含代码段、数据段、BBS段、堆、栈;其中数据段用来存储初始值不为0的全局数据,BSS段用来存储初始值为0的全局数据,堆用于动态内存分配,栈用于实现函数调用、存储局部变量;
被调用函数在执行之前,它将一些寄存器的值保存在堆栈中,其中包括返回地址寄存器lr,如果知道了所保存的lr寄存器的值,那么就可以知道它的调用者是谁,在栈信息中,一个函数一个函数地往上找出所有保存lr的值,就可以知道各个调用函数,这就是栈回溯的原理;