驱动调试方法

驱动调试方法

内核打印函数printk

调试内核、驱动最简单的方法就是使用printk函数打印信息;

printk函数的打印级别

printk函数与用户空间的printf函数格式完全相同,它所打印的字符串头部可以加入“”样式的字符,其中n为0~7,表示这条信息的打印级别;

在内核代码: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])

分别说明以上各宏的含义:

  1. 对于printk(""....),只有n小于console_loglevel时,这个信息才会被打印;
  2. 假设default_message_loglevel的值等于4,如果printk的参数开头没有“”样式的字符,则在printk函数中进一步处理前会自动加上“<4>”;
  3. minimum_console_loglevel是一个预设值,平时不起作用,当通过其他方式来设置console_loglevel的值时,这个值不能小于minimum_console_loglevel;
  4. 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信息主要有:

  1. 一段文本描述信息;

    比如类似“Unable to handle kernel paging request at virtual address 56000050”的信息,说明了发生的是哪类错误;

  2. Oops信息的序号;

    比如第1次、第2次等,类似“Internal error: Oops: 5 [#1]”,括号内的数字表示序号;

  3. 内核加载模块的名称,也可能没有,以“Modules linked in”开头的,类似“Modules linked in: jz2440_leds”;

  4. 发生错误的CPU的序号,对于单处理器的系统,序号为0,比如:

    “CPU: 0 Not tainted (2.6.22.6 #4)”

  5. 发生错误时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
    
  6. 当前进程的名字及进程ID,比如“Process ledtest (pid: 801, stack limit = 0xc073a258)”;

    这并不是说发生错误的就是这个进程,而是表示发生错误时,当前进程是它,错误可能发生在内核代码、驱动程序,也可能就是这个进程的错误;

  7. 栈信息;

    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
    
  8. 栈回溯信息,可以看到函数的调用关系,如下:

    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)
    
  9. 出错指令附近的指令的机器码,比如(出错指令在小括号内):

    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的值,就可以知道各个调用函数,这就是栈回溯的原理;

posted @ 2019-09-23 11:04  SKILL-RABBIT  阅读(1176)  评论(0编辑  收藏  举报