首页officeppt正文

Linux下驱动开发调试技术

强国说学习2022-02-21273调试技术开发驱动Linux

驱动程序开发的一个重大难点就是不易调试。本文目的就是介绍驱动开发中常用的几种直接和间接的调试手段,它们是:

使用printk
查看OOP消息
使用strace
使用内核内置的hacking选项
使用ioctl方法
使用/proc 文件系统
使用kgdb

一、使用printk

这是驱动开发中最朴实无华,同时也是最常用和有效的手段。scull驱动的main.c第338行如下,就是使用printk进行调试的例子,这样的例子相信大家在阅读驱动源码时随处可见。

 printk(KERN_ALERT "wakeup by signal in process %d\n", current->;pid);

printk的功能与我们经常在应用程序中使用的printf是一样的,不同之处在于printk可以在打印字符串前面加上内核定义的宏,例如上面例子中的KERN_ALERT(注意:宏与字符串之间没有逗号):

#define KERN_EMERG ""#define KERN_ALERT ""#define KERN_CRIT ""#define KERN_ERR ""#define KERN_WARNING ""#define KERN_NOTICE ""#define KERN_INFO ""#define KERN_DEBUG ""#define DEFAULT_CONSOLE_LOGLEVEL 7

这个宏是用来定义需要打印的字符串的级别,值越小,级别越高。内核中有个参数用来控制是否将printk打印的字符串输出到控制台(屏幕或者/sys/log/syslog日志文件)

# cat /proc/sys/kernel/printk6 4 1 7

第一个6表示级别高于(小于)6的消息才会被输出到控制台,第二个4表示如果调用printk时没有指定消息级别(宏)则消息的级别为4,第三个1表示接受的最高(最小)级别是1,第四个7表示系统启动时第一个6原来的初值是7。

因此,如果你发现在控制台上看不到你程序中某些printk的输出,请使用 echo 8 > /proc/sys/kernel/printk 来解决。

我们在复杂驱动的开发过程中,为了调试会在源码中加入成百上千的printk语句。而当调试完毕形成最终产品的时候必然会将这些printk语句删除(为什么?想想你自己是驱动的使用者而不是开发者吧。记住:己所不欲,勿施于人),这个工作量是不小的。最要命的是,如果我们将调试用的printk语句删除后,用户又报告我们的驱动有bug,所以我们又不得不手工将这些上千条的printk语句再重新加上。OMG,杀了我吧。所以,我们需要一种能方便地打开和关闭调试信息的手段。哪里能找到这种手段呢?哈哈,远在天边,近在眼前。看看scull驱动或者leds驱动的源代码吧!

#define LEDS_DEBUG#undef PDEBUG#ifdef LEDS_DEBUG#ifdef __KERNEL__#define PDEBUG(fmt, args…) printk( KERN_EMERG "leds: " fmt, ## args)#else#define PDEBUG(fmt, args…) fprintf(stderr, fmt, ## args)#endif#else#define PDEBUG(fmt, args…)#endif#undef PDEBUGG#define PDEBUGG(fmt, args…)

这样一来,在开发驱动的过程中,如果想打印调试消息,我们就可以用 PDEBUG(“address of i_cdev is %p\n”, inode->i_cdev); ,如果不想看到该调试消息,就只需要简单的将 PDEBUG 改为 PDEBUGG 即可。而当我们调试完毕形成最终产品时,只需要简单地将第1行注释掉即可。
上边那一段代码中的__KERNEL__是内核中定义的宏,当我们编译内核(包括模块)时,它会被定义。当然如果你不明白代码中的…和##是什么意思的话,就请认真查阅一下gcc关于预处理部分的资料吧!如果你实在太懒不愿意去查阅的话,那就充当VC工程师把上面的代码copy到你的代码中去吧。

二、查看OOP消息

OOP意为惊讶。当你的驱动有问题,内核不惊讶才怪:嘿!小子,你干吗乱来!好吧,让我们来看看内核是如何惊讶的。
根据 faulty.c 编译出faulty.ko,并 insmod faulty.ko。执行 echo yang > /dev/faulty ,结果内核就惊讶了。内核为什么会惊讶呢?因为faulty驱动的write函数执行了*(int *)0 = 0,向内存0地址写入,这是内核绝对不会容许的。

ssize_t faulty_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos){    *(int *)0 = 0;    return 0;}
Unable to handle kernel NULL pointer dereference at virtual address 00000000pgd = c3894000[00000000] *pgd=33830031, *pte=00000000, *ppte=00000000Internal error: Oops: 817 [#1] PREEMPTModules linked in: faulty scullCPU: 0 Not tainted (2.6.22.6 #4)PC is at faulty_write+0×10/0×18 [faulty]LR is at vfs_write+0xc4/0×148pc : [] lr : [] psr: a0000013sp : c3871f44 ip : c3871f54 fp : c3871f50r10: 4021765c r9 : c3870000 r8 : 00000000r7 : 00000004 r6 : c3871f78 r5 : 40016000 r4 : c38e5160r3 : c3871f78 r2 : 00000004 r1 : 40016000 r0 : 00000000Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment userControl: c000717f Table: 33894000 DAC: 00000015Process sh (pid: 745, stack limit = 0xc3870258)Stack: (0xc3871f44 to 0xc3872000)1f40: c3871f74 c3871f54 c0088eb8 bf00608c 00000004 c38e5180 c38e51601f60: c3871f78 00000000 c3871fa4 c3871f78 c0088ffc c0088e04 00000000 000000001f80: 00000000 00000004 40016000 40215730 00000004 c002c0e4 00000000 c3871fa81fa0: c002bf40 c0088fc0 00000004 40016000 00000001 40016000 00000004 000000001fc0: 00000004 40016000 40215730 00000004 00000001 00000000 4021765c 000000001fe0: 00000000 bea60964 0000266c 401adb40 60000010 00000001 00000000 00000000Backtrace:[] (faulty_write+0×0/0×18 [faulty]) from [] (vfs_write+0xc4/0×148)[] (vfs_write+0×0/0×148) from [] (sys_write+0x4c/0×74)r7:00000000 r6:c3871f78 r5:c38e5160 r4:c38e5180[] (sys_write+0×0/0×74) from [] (ret_fast_syscall+0×0/0x2c)r8:c002c0e4 r7:00000004 r6:40215730 r5:40016000 r4:00000004Code: e1a0c00d e92dd800 e24cb004 e3a00000 (e5800000)

1行惊讶的原因,也就是报告出错的原因;
2-4行是OOP信息序号;
5行是出错时内核已加载模块;
6行是发生错误的CPU序号;
7-15行是发生错误的位置,以及当时CPU各个寄存器的值,这最有利于我们找出问题所在地;

由强国说学习-WPS之家(wps.qiangguoshuo.com)收集的该篇文章内容来源于网络,希望能为广大朋友提供帮助。


16行是当前进程的名字及进程ID
17-23行是出错时,栈内的内容
24-29行是栈回溯信息,可看出直到出错时的函数递进调用关系(确保CONFIG_FRAME_POINTER被定义)
30行是出错指令及其附近指令的机器码,出错指令本身在小括号中

反汇编 faulty.ko:

arm-linux-objdump -D faulty.ko > faulty.dis ;cat faulty.dis

可以看到如下的语句如下:

0000007c :7c: e1a0c00d mov ip, sp80: e92dd800 stmdb sp!, {fp, ip, lr, pc}84: e24cb004 sub fp, ip, #4 ; 0×488: e3a00000 mov r0, #0 ; 0×08c: e5800000 str r0, [r0]90: e89da800 ldmia sp, {fp, sp, pc}

定位出错位置以及获取相关信息的过程:

9 pc : [] lr : [] psr: a000001325 [] (faulty_write+0×0/0×18 [faulty]) from [] (vfs_write+0xc4/0×148)26 [] (vfs_write+0×0/0×148) from [] (sys_write+0x4c/0×74)

出错代码是 faulty_write 函数中的第5条指令((0xbf00608c-0xbf00607c)/4+1=5),该函数的首地址是0xbf00607c,该函数总共6条指令(0×18),该函数是被0xc0088eb8的前一条指令调用的(即:函数返回地址是0xc0088eb8。这一点可以从出错时lr的值正好等于0xc0088eb8得到印证)。调用该函数的指令是vfs_write的第49条(0xc4/4=49)指令。

如想转载该文章请注明出处:强国说学习-qiangguoshuo.com
强国说学习

转载声明:本站发布文章及版权归原作者所有,转载本站文章请注明文章来源!

本文链接:https://www.qiangguoshuo.com/ppt/7781.html