linux设备驱动:从如何定位oops的代码行谈驱动调试方法

在普通的c应用程序中,我们经常使用printf来输出信息,或者使用gdb来调试程序,那么驱动程序如何调试呢?我们知道在调试程序时经常遇到的问题就是野指针或者数组越界带来的问题,在应用程序中运行这种程序就会报segmentation fault的错误,而由于驱动程序的特殊性,出现此类情况后往往会直接造成系统宕机,并会抛出oops信息。那么我们如何来分析oops信息呢,甚至根据oops信息来定位具体的出错的代码行呢?下面就根据一个简单的实例来说明如何调试驱动程序。

如何根据oops定位代码行

我们借用linux设备驱动第二篇:构造和运行模块里面的hello world程序来演示出错的情况,含有错误代码的hello world如下:

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
    char *p = NULL; 
    memcpy(p,"test", 4);
    printk(KERN_ALERT "hello , world\n");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "goodBye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

Makefile文件如下:

ifneq ($(KERNELRELEASE),)
    obj-m := hello_world.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

很明显,以上代码的第8行是一个空指针错误。insmod后会出现下面的oops信息:

这里写图片描述

下面简单分析下oops信息的内容。
由BUG: unable to handle kernel NULL pointer dereference at (null)知道出错的原因是使用了空指针。标红的部分确定了具体出错的函数。Modules linked in: helloworld表明了引起oops问题的具体模块。call trace列出了函数的调用信息。这些信息中其中标红的部分是最有用的,我们可以根据其信息找到具体出错的代码行。下面就来说下,如何定位到具体出错的代码行。
第一步我们需要使用objdump把编译生成的bin文件反汇编,我们这里就是helloworld.o,如下命令把反汇编信息保存到err.txt文件中:

objdump helloworld.o -D > err.txt  

err.txt内容如下:

hello_world.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <init_module>:
   0:   e8 00 00 00 00          callq  5 <init_module+0x5>
   5:   55                      push   %rbp
   6:   48 c7 c7 00 00 00 00    mov    $0x0,%rdi
   d:   c7 04 25 00 00 00 00    movl   $0x74736574,0x0
  14:   74 65 73 74 
  18:   48 89 e5                mov    %rsp,%rbp
  1b:   e8 00 00 00 00          callq  20 <init_module+0x20>
  20:   31 c0                   xor    %eax,%eax
  22:   5d                      pop    %rbp
  23:   c3                      retq   
  24:   66 90                   xchg   %ax,%ax
  26:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  2d:   00 00 00 

0000000000000030 <cleanup_module>:
  30:   e8 00 00 00 00          callq  35 <cleanup_module+0x5>
  35:   55                      push   %rbp
  36:   48 c7 c7 00 00 00 00    mov    $0x0,%rdi
  3d:   48 89 e5                mov    %rsp,%rbp
  40:   e8 00 00 00 00          callq  45 <cleanup_module+0x15>
  45:   5d                      pop    %rbp
  46:   c3                      retq   

Disassembly of section .rodata.str1.1:

0000000000000000 <.rodata.str1.1>:
   0:   01 31                   add    %esi,(%rcx)
   2:   68 65 6c 6c 6f          pushq  $0x6f6c6c65
   7:   20 2c 20                and    %ch,(%rax,%riz,1)
   a:   77 6f                   ja     7b <cleanup_module+0x4b>
   c:   72 6c                   jb     7a <cleanup_module+0x4a>
   e:   64 0a 00                or     %fs:(%rax),%al
  11:   01 31                   add    %esi,(%rcx)
  13:   67 6f                   outsl  %ds:(%esi),(%dx)
  15:   6f                      outsl  %ds:(%rsi),(%dx)
  16:   64 42 79 65             fs rex.X jns 7f <cleanup_module+0x4f>
  1a:   2c 20                   sub    $0x20,%al
  1c:   63 72 75                movslq 0x75(%rdx),%esi
  1f:   65 6c                   gs insb (%dx),%es:(%rdi)
  21:   20 77 6f                and    %dh,0x6f(%rdi)
  24:   72 6c                   jb     92 <cleanup_module+0x62>
  26:   64 0a 00                or     %fs:(%rax),%al

Disassembly of section .modinfo:

0000000000000000 <__UNIQUE_ID_license0>:
   0:   6c                      insb   (%dx),%es:(%rdi)
   1:   69 63 65 6e 73 65 3d    imul   $0x3d65736e,0x65(%rbx),%esp
   8:   44 75 61                rex.R jne 6c <cleanup_module+0x3c>
   b:   6c                      insb   (%dx),%es:(%rdi)
   c:   20 42 53                and    %al,0x53(%rdx)
   f:   44 2f                   rex.R (bad) 
  11:   47 50                   rex.RXB push %r8
  13:   4c                      rex.WR
    ...

Disassembly of section .comment:

0000000000000000 <.comment>:
   0:   00 47 43                add    %al,0x43(%rdi)
   3:   43 3a 20                rex.XB cmp (%r8),%spl
   6:   28 55 62                sub    %dl,0x62(%rbp)
   9:   75 6e                   jne    79 <cleanup_module+0x49>
   b:   74 75                   je     82 <cleanup_module+0x52>
   d:   20 35 2e 34 2e 30       and    %dh,0x302e342e(%rip)        # 302e3441 <cleanup_module+0x302e3411>
  13:   2d 36 75 62 75          sub    $0x75627536,%eax
  18:   6e                      outsb  %ds:(%rsi),(%dx)
  19:   74 75                   je     90 <cleanup_module+0x60>
  1b:   31 7e 31                xor    %edi,0x31(%rsi)
  1e:   36 2e 30 34 2e          ss xor %dh,%cs:(%rsi,%rbp,1)
  23:   39 29                   cmp    %ebp,(%rcx)
  25:   20 35 2e 34 2e 30       and    %dh,0x302e342e(%rip)        # 302e3459 <cleanup_module+0x302e3429>
  2b:   20 32                   and    %dh,(%rdx)
  2d:   30 31                   xor    %dh,(%rcx)
  2f:   36 30 36                xor    %dh,%ss:(%rsi)
  32:   30 39                   xor    %bh,(%rcx)
    ...

Disassembly of section __mcount_loc:

0000000000000000 <__mcount_loc>:

由oops信息我们知道出错的地方是hello_init的地址偏移0xd。而有dump信息知道,hello_init的地址即init_module的地址,因为hello_init即本模块的初始化入口,如果在其他函数中出错,dump信息中就会有相应符号的地址。由此我们得到出错的地址是0xd,下一步我们就可以使用addr2line来定位具体的代码行:

addr2line -C -f -e helloworld.o d  

此命令就可以得到行号了。以上就是通过oops信息来定位驱动崩溃的行号。

其他调试手段

以上就是通过oops信息来获取具体的导致崩溃的代码行,这种情况都是用在遇到比较严重的错误导致内核挂掉的情况下使用的,另外比较常用的调试手段就是使用printk来输出打印信息。printk的使用方法类似printf,只是要注意一下打印级别,详细介绍在linux设备驱动第二篇:构造和运行模块中已有描述,另外需要注意的是大量使用printk会严重拖慢系统,所以使用过程中也要注意。
以上两种调试手段是我工作中最常用的,还有一些其他的调试手段,例如使用/proc文件系统,使用trace等用户空间程序,使用gdb,kgdb等,这些调试手段一般不太容易使用或者不太方便使用,所以这里就不在介绍了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值