c语言调试驱动器方法,内核调试的方法

驱动程序的调试

一. 打印: prink, 自制proc文件

UBOOT传入console=ttySAC0 console=tty1

1. 内核处理UBOOT传入的参数

console_setup

add_preferred_console // 我想用名为"ttySAC0"的控制台,先记录下来

2. 硬件驱动的入口函数里:

drivers/serial/s3c2410.c

register_console(&s3c24xx_serial_console);

3. printk

vprintk

/* Emit the output into the temporary buffer */

// 先把输出信息放入临时BUFFER

vscnprintf

// Copy the output into log_buf.

// 把临时BUFFER里的数据稍作处理,再写入log_buf

// 比如printk("abc")会得到"<4>abc", 再写入log_buf

// 可以用dmesg命令把log_buf里的数据打印出来重现内核的输出信息

// 调用硬件的write函数输出

release_console_sem();

call_console_drivers(_con_start, _log_end);

// 从log_buf得到数据,算出打印级别

_call_console_drivers(start_print, cur_index, msg_level);

// 如果可以级别够格打印

if ((msg_log_level < console_loglevel

__call_console_drivers

con->write(con, &LOG_BUF(start), end - start);

二. 根据内核打印的段错误信息分析

oops信息 : 单词oops的含义是“惊讶”,当内核出错时(比如访问非法地址),打印出来的信息被称为oops信息。

a). 作为模块:

1,根据PC值,找到导致错误的指令。

pc = 0x00000000 它属于什么的地址?是内核的地址,还是通过insmod加载的驱动程序的地址?

先判断是否属于内核的地址 : 看 内核编译makefile目录下的 System.map(编译完内核都会发现在内核根目录下面多出来一个System.map文件)

确定内核的函数的地址范围 : c0004000~c03faa94。

所以可以确定 : 导致错误的指令不在内核的地址范围,则它属于insmod加载的驱动程序的地址范围。

2,假设它的加载的驱动程序引入的错误。那又怎么确定是哪一个驱动程序?

有时候 Modules linked in: 会指明是哪个驱动程序,但是很多时候加载的驱动程序很多,是不会指明具体是哪个。

所以还是需要根据PC值来确定究竟是哪个驱动程序。

先看看加载的驱动程序的地址范围。

在开发板目录下 : cat /proc/kallsyms >> kallsyms.txt (内核函数的地址、加载的函数的地址)

kallsyms.txt文件中的内容介绍 //T : 表示全局函数 t : 表示静态函数

从这些信息里找到一个相近的地址, 这个地址<=0xbf000018

比如找到了:

bf000000 t first_drv_open[first_drv]

3. 找到了first_drv.ko

在PC上反汇编它: arm-linux-objdump -D lcd.ko > lcd.dis

在dis文件里找到first_drv_open

first_drv.dis文件里              insmod后

00000000 :       bf000000 t first_drv_open[first_drv]

00000018                         pc = bf000018

18: e5923000 ldr r3, [r2]    //r2的值在下面可以找到,是56000050

此时,要通过汇编语言来找到对应的c语言的语句。考验汇编能力的时候。

./firstdrvtest on

//1,一段文本描述信息

Unable to handle kernel paging request at virtual address 56000050

内核使用56000050来访问时发生了错误

pgd = c3eb0000

[56000050] *pgd=00000000

//2,oops信息的序号,#1,表示是第1次。

Internal error: Oops: 5 [#1]

//3,内核中加载的模块的名称

Modules linked in: first_drv

//4,发送错误时,CPU的序号,对于单处理器系统,序号为0。

CPU: 0    Not tainted  (2.6.22.6 #1)

//5,PC就是发生错误时,指令的地址。

//大多时候,PC值只会给出一个地址,不会指示说是在哪个函数里面。

PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小) [first_drv]

PC就是发生错误的指令的地址

大多时候,PC值只会给出一个地址,不到指示说是在哪个函数里

//__init_begin = c0008000, PC=__init_begin+0x3fff8000=

//6,LR寄存器的值。

LR is at chrdev_open+0x14c/0x164

LR寄存器的值

//7,发送错误时,CPU各个寄存器的值。

pc = 0xbf000018

pc : []    lr : []    psr: a0000013

sp : c3c7be88  ip : c3c7be98  fp : c3c7be94

r10: 00000000  r9 : c3c7a000  r8 : c049abc0

r7 : 00000000  r6 : 00000000  r5 : c3e740c0  r4 : c06d41e0

r3 : bf000000  r2 : 56000050  r1 : bf000964  r0 : 00000000

执行这条导致错误的指令时各个寄存器的值

Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user

Control: c000717f  Table: 33eb0000  DAC: 00000015

//8,发生错误时,当前进程是它,并不是说发生错误的是这个进程

Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)

//发生错误时当前进程的名称是firstdrvtest

//9,栈信息

Stack: (0xc3c7be88 to 0xc3c7c000)

be80:                   c3c7bebc c3c7be98 c008d888 bf000010 00000000 c049abc0

bea0: c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0 c0089e48 c008d74c

bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000 c3c7befc c3c7bee8

bee0: c0089f64 c0089d58 00000000 00000002 c3c7bf68 c3c7bf00 c0089fb8 c0089f40

bf00: c3c7bf04 c3e766a8 c0474e20 00000000 00000000 c3eb1000 00000101 00000001

bf20: 00000000 c3c7a000 c04a7468 c04a7460 ffffffe8 c3d10000 c3c7bf68 c3c7bf48

bf40: c008a16c c009fc70 00000003 00000000 c049abc0 00000002 bec1fee0 c3c7bf94

bf60: c3c7bf6c c008a2f4 c0089f88 00008520 bec1fed4 0000860c 00008670 00000005

bf80: c002c044 4013365c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000 c3c7bfa8

bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0 00000001

bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 4013365c bec1fea8

bfe0: 00000000 bec1fe84 0000266c 400c98e0 60000010 00008720 00000000 00000000

//10,栈回溯信息,可以从中看出函数调用关系:从最后一个函数 sys_init_module 开始,向上可以找到函数调用的关系。

//可以通过内核配置信息 make menuconfig 来指定是否输出 栈回溯信息。

Backtrace: (回溯)

[] (first_drv_open+0x0/0x3c [first_drv]) from [] (chrdev_open+0x14c/0x164)

[] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)

r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0

[] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48)

[] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48)

r4:00000002

[] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)

r5:bec1fee0 r4:00000002

[] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)

[] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)

Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)

Segmentation fault

#

b). 编入内核

Modules linked in:

CPU: 0    Not tainted  (2.6.22.6 #2)

PC is at first_drv_open+0x18/0x3c

LR is at chrdev_open+0x14c/0x164

pc : []    lr : []    psr: a0000013

sp : c3a03e88  ip : c3a03e98  fp : c3a03e94

r10: 00000000  r9 : c3a02000  r8 : c03f3c60

r7 : 00000000  r6 : 00000000  r5 : c38a0c50  r4 : c3c1e780

r3 : c014e6a8  r2 : 56000050  r1 : c031a47c  r0 : 00000000

Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user

Control: c000717f  Table: 339f0000  DAC: 00000015

Process firstdrvtest (pid: 750, stack limit = 0xc3a02258)

1. 根据pc值确定该指令属于内核还是外加的模块

pc = c014e6c0 它属于什么的地址?是内核的地址,还是通过insmod加载的驱动程序的地址?

先判断是否属于内核的地址 : 看 内核编译makefile目录下的 System.map(编译完内核都会发现在内核根目录下面多出来一个System.map文件)

确定内核的函数的地址范围 : c0004000~c03faa94。

所以可以确定 : 导致错误的指令不在内核的地址范围,则它属于insmod加载的驱动程序的地址范围。

2. 反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis

/*

vmlinux是未压缩的内核,vmlinux 是ELF文件,即编译出来的最原始的文件。用于kernel-debug,产生system.map符号表,不能用于直接加载,不可以作为启动内核。只是启动过程中的中间媒体

vmlinuz是可引导的、压缩的内核。“vm”代表“Virtual Memory”。Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制

*/

first_drv.dis文件中:搜c014e6c0

c014e6a8 :

c014e6a8:       e1a0c00d        mov     ip, sp

c014e6ac:       e92dd800        stmdb   sp!, {fp, ip, lr, pc}

c014e6b0:       e24cb004        sub     fp, ip, #4      ; 0x4

c014e6b4:       e59f1024        ldr     r1, [pc, #36]   ; c014e6e0 c014e6b8:       e3a00000        mov     r0, #0  ; 0x0

c014e6bc:       e5912000        ldr     r2, [r1]

c014e6c0:       e5923000        ldr     r3, [r2] // 在此出错 r2=56000050,在下面可以找到

c).根据栈信息分析函数调用过程(作为模块 或者 编入内核都可,下面的实验是作为模块)

# ./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 : []    lr : []    psr: a0000013

1 根据PC确定出错位置,是在内核,还是在模块中,前面有详细的分析System.map

bf000018 属于 insmod的模块

bf000000 t first_drv_open       [first_drv]

2 确定它属于哪个函数

有时候 Modules linked in: 会指明是哪个驱动程序,但是很多时候加载的驱动程序很多,是不会指明具体是哪个。

所以还是需要根据PC值来确定究竟是哪个驱动程序。

先看看加载的驱动程序的地址范围。

在开发板目录下 : cat /proc/kallsyms >> kallsyms.txt (内核函数的地址、加载的函数的地址)

kallsyms.txt文件中的内容介绍 //T : 表示全局函数 t : 表示静态函数

3 找到了first_drv.ko

在PC上反汇编它: arm-linux-objdump -D first_drv.ko > first_drv.dis

在dis文件里找到first_drv_open

first_drv.dis文件里              insmod后

00000000 :       bf000000 t first_drv_open[first_drv]

00000018                         pc = bf000018

first_drv.dis文件中:

c014e6a8 :

c014e6a8:       e1a0c00d        mov     ip, sp

c014e6ac:       e92dd800        stmdb   sp!, {fp, ip, lr, pc}

//从这里可以看见 first_drv_open函数的栈信息,保存有4*4个字节

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)

可以仅根据栈信息,来确定函数的调用关系。栈就是一块内存

分析如下 :

Stack: (0xc3e69e88 to 0xc3e6a000)

9e80:                   c3e69ebc c3e69e98 c008c888 bf000010 00000000 c0490620

fp         ip   返回地址=lr   pc

栈信息开始部分的4个数据 是first_drv_open 的 栈                chrdev_open is sp

first_drv_open执行完之后,返回地址lr=c008c888,再在 vmlinux.dis 中找到 调用者的函数。

4个数据后面的数据,就是它的调用者的函数的栈空间中的数据。

9ea0: c3e320a0 c008c73c c0465e20 c3e36cb4 c3e69ee4 c3e69ec0 c0088e48 c008c74c

lr=c0088e48

9ec0: c0490620 c3e69f04 00000003 ffffff9c c002b044 c06e0000 c3e69efc c3e69ee8

__dentry_open 的栈

9ee0: c0088f64 c0088d58 00000000 00000002 c3e69f68 c3e69f00 c0088fb8 c0088f40

lr=c0088f64       nameidata_to_filp的栈               lr=c0088fb8

9f00: c3e69f04 c3e36cb4 c0465e20 00000000 00000000 c3e79000 00000101 00000001

do_filp_open的栈

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=c00892f4       do_sys_open的栈

9f80: c002b044 4013365c c3e69fa4 c3e69f98 c00893a8 c00892b0 00000000 c3e69fa8

lr=c00893a8       sys_open的栈

9fa0: c002aea0 c0089394 be94eed4 0000860c 00008720 00000002 be94eee0 00000001

lc=002aea0        ret_fast_syscall的栈

9fc0: be94eed4 0000860c 00008670 00000002 00008520 00000000 4013365c be94eea8

9fe0: 00000000 be94ee84 0000266c 400c98e0 60000010 00008720 00000000 00000000

ret_fast_syscall()函数是被谁调用的,我们这里还不需要详细了解,只要知道是应用程序是通过swi中断来加入内核的。

注意 : 上面的信息,从下往上,从大地址到小地址方向。是从栈底开始,往栈顶方向打印栈中的值。也是函数调用的方向。

三. 修改内核来定位系统僵死问题

1,\kernel-2.6.13\arch\arm\kernel\irq.c  //修改这个内核文件

修改这个文件的原因 : 只要系统还在运行,即使某个驱动程序卡死了,系统时钟中断是绝对不会停歇的。

所以,可以在系统中断代码中加入一些调试代码,来帮助我们找到bug。

/*

* do_IRQ handles all hardware IRQ's.  Decoded IRQs should not

* come via this function.  Instead, they should provide their

* own 'handler'

*/

asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

struct irqdesc *desc = irq_desc + irq;

static pid_t pre_pid;

static int count = 0;

/*

* Some hardware gives randomly wrong interrupts.  Rather

* than crashing, do something sensible.

*/

if (irq >= NR_IRQS)

desc = &bad_irq_desc;

if(irq == 30) /*系统时钟中断*/

{

/*如果10秒之内,都是同一个进程在运行,就打印卡死信息。*/

/*静态局部变量:

只有在这个函数中能访问,但是生命周期是和全局变量差不多的,函数退出之后变量还在,

而且只在第一次进入的时候做初始化,以后会跳过初始化语句,保留原来的值*/

if(pre_pid == current->pid) /*当前进程 等于 之前记录下来的进程号*/

{

count++;

}

else /*当前进程 不等于 之前记录下来的进程号*/

{

count = 0;

pre_pid = current->pid; /*把新的当前进程记录下来。*/

}

if(count == 10*HZ) /*累计达到10秒的时候*/

{

count = 0;

/*明确是在哪个进程导致卡死的,明确PC值*/

printk("asm_do_IRQ==>s3c2410_timer_interrupt : pid=[%d], task_name=[%s], PC=[0x%08x]\n", current->pid, current->comm, regs->ARM_pc);

}

}

irq_enter();

spin_lock(&irq_controller_lock);

desc->handle(irq, desc, regs);

/*

* Now re-run any pending interrupts.

*/

if (!list_empty(&irq_pending))

do_pending_irqs(regs);

irq_finish(irq);

spin_unlock(&irq_controller_lock);

irq_exit();

}

重新编译内核 make clean;make

重新启动这个新的内核

2, 在这个驱动文件中加入死循环,来模拟系统僵死问题 \nfs_2.6.13\wxc\driver\chardriver\led\leddriver2.c

static int leddriver2_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

{

//printk("DEVICE:mydriver2_ioctl Called!\n");

printk("DEVICE:cmd=[%d], arg=[%ld]\n", cmd, arg);

while(1);  //故意加的

if(cmd == 0) //所有的灯全亮2s

{

....

}

return 0;

}

启动待测试的驱动程序

# insmod leddriver2.ko

[kernel/sys.c][notifier_call_chain][175]

[kernel/sys.c][notifier_call_chain][187]

DEVICE:leddriver2_init

DEVICE:register leddriver2 OK! Major = [253]

# mknod /dev/leddriver2 c 253 0

[root@EmbedSky ko]# ls -lrt /dev/led*

crw-r--r--    1 root     root     253,   0 Jan  1 00:03 /dev/leddriver2

启动应用程序

# ./leddrivertest2 0 3

APP:open fd=[3]

APP:led_no=[0] time=[3]

DEVICE:cmd=[0], arg=[3]  //会发现驱动程序卡死在这里了,无论怎么也退出不了,下面打印出来了很多关键信息。

asm_do_IRQ==>s3c2410_timer_interrupt : pid=[806], task_name=[leddrivertest2], PC=[0xbf000044]

asm_do_IRQ==>s3c2410_timer_interrupt : pid=[806], task_name=[leddrivertest2], PC=[0xbf000044]

PC=0xbf000044

3,定位僵死发生的代码的具体位置

3.1), PC=0xbf000044

从这些信息里找到一个相近的地址, 这个地址<=0xbf000044

前面有分析 可知 地址 0xbf000044 不属于内核地址空间,属于外加的内核。

在开发板目录下 :

# cat /proc/kallsyms >> kallsyms.txt

在这个文件下面,查找 地址 0xbf000044 大概在什么函数

bf000000 t $a[leddriver2]

bf000048 t $d[leddriver2]   // 对于中断, pc-4=0xbf000044 才是发生中断瞬间的地址,说明发生中断的驱动程序是 : leddriver2.ko。

bf002000 t leddriver2_init[leddriver2]

bf00004c t leddriver2_exit[leddriver2]

c481e178 ? __mod_license202[leddriver2]

c481e184 ? __mod_description201[leddriver2]

c481e1a4 ? __mod_author200[leddriver2]

bf0008a0 b $d[leddriver2]

bf0008a4 b MYDRIVER_Major[leddriver2]

bf000698 d leddriver2_fops[leddriver2]

bf000698 d $d[leddriver2]

bf000028 t leddriver2_ioctl[leddriver2] //bf000028 是与 0xbf000044 最近的地址。说明 : 发生中断的驱动程序是 : leddriver2.ko。

bf000000 t leddriver2_open[leddriver2] //中断很有可能发生在 leddriver2_ioctl 函数中。

bf000014 t leddriver2_release[leddriver2]

bf000704 d GPBDAT_HIGH[leddriver2]

bf000714 d GPBDAT_LOW[leddriver2]

bf000724 d GPBUP_UPEN[leddriver2]

bf000734 d GPBCON_OUTP[leddriver2]

bf000744 d GPBCON_CLEAN[leddriver2]

bf000000 t $a[leddriver2]

bf000048 t $d[leddriver2]

bf002000 t $a[leddriver2]

bf002094 t $d[leddriver2]

bf00004c t $a[leddriver2]

bf000080 t $d[leddriver2]

3.2), 找到 leddriver2.ko

在PC上反汇编它:

在ubuntu目录下 :

# arm-linux-objdump -D leddriver2.ko > leddriver2.dis //PATH没有,就用下面一行

# /home/wangxc/linux/toolchain/crosstools_3.4.1_softfloat/arm-linux/gcc-3.4.1-glibc-2.3.3/bin/arm-linux-objdump -D leddriver2.ko > leddriver2.dis

在dis文件里找到 leddriver2_ioctl

00000028 :  //insmod后 上面的地址 bf000028 对应在这里

28:e1a0c00d movip, sp

2c:e92dd800 stmdbsp!, {fp, ip, lr, pc}

30:e24cb004 subfp, ip, #4; 0x4

34:e59f000c ldrr0, [pc, #12]; 48 38:e1a01002 movr1, r2

3c:e1a02003 movr2, r3

40:ebfffffe bl40 // 对于中断, pc-4才是发生中断瞬间的地址,所以僵死的代码在这里

44:ea00000f b88 //所以 0xbf000044 在这里

48:00000000 andeqr0, r0, r0

Disassembly of section .init.text:

3.3), 分析汇编代码,找到对应的c语言代码

40:ebfffffe bl40  //bl自己跳转到自己,就是一个死循环,

所以,c语言代码中的就是死循环的语句。

在驱动源码leddriver2_ioctl函数中寻找,可以找到 while(1);语句,说明僵死就发生在这里。

阅读(1665) | 评论(0) | 转发(0) |

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值