x4412开发板&ibox卡片电脑项目实战11-linux驱动的调试方法

驱动程序开发的重点就在于驱动、系统的调试了,因此,掌握linux驱动的调试方法,是linux系统工程师必须掌握的技能。这里介绍驱动开发中常用的几种调试手段:
利用printk
查看OOP 消息
利用strace
利用内核内置的hacking 选项
利用ioctl 方法
利用/proc  文件系统
使用kgdb
1.1.1    利用printk
这是驱动开发中最朴实无华,同时也是最常用和有效的手段。printk函数会将内核信息输出到内核信息缓冲区中。为了避免缓冲区数据无限扩大,它使用了环形缓冲区的机制。当然,如果塞入的信息过多,之前的信息将会被冲刷掉。
在系统启动的过程中,我们可以使用串口终端监控打印信息,这些打印信息有助于我们分析系统的运行情况。进入系统之后,我们仍然可以使用dmesg命令查看。有时候我们需要打印更多的信息,有时候我们需要部分信息就可以了,在某种情况下,我们甚至不需要任何打印信息,但是设备要能正常工作。打印信息能够帮助分析系统运行情况的同时,它也能帮助“外人”查看设备信息,造成数据外泄。比如POS机,真正通过过银行标准认证的POS机是不允许有内核打印信息的。这时候,printk函数就大显身手了。
       printk 的功能与我们经常在应用程序中使用的printf 是一样的,不同之处在于printk 可以在打印字符串前面加上内核定义的宏,这个宏定义了printk 的消息级别。printk 函数定义了8 个级别,依次对应级别0~7 ,其数值越大,表示级别越低,表明对应的消息越不重要。第0 级为紧急事件级,第7 级为调试级,如下清单为printk 的级别定义。
  1. #define KERN _ EMERG "<0>"    /* 紧急事件,一般是系统崩溃之前提示的消息 */
  2. #define KERN _ ALERT "<1>"     /* 必须立即采取行动 */
  3. #define KERN _ CRIT "<2>"         /* 临界状态,通常涉及严重的硬件或软件操作失败 */
  4. #define KERN _ ERR "<3>"         /* 用于报告错误状态,设备驱动程序会经常使用KERN _ ERR来报告来自                                                硬件的问题 */
  5. #define KERN _ WARNING "<4>" /* 对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重                                                 问题 */
  6. #define KERN _ NOTICE "<5>"   /* 有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行                                                  汇报 */
  7. #define KERN _ INFO "<6>"        /* 内核提示性信息,很多驱动程序在启动的时候,以这个级别打印出它                                                            们找到的硬件信息 */
  8. #define KERN _ DEBUG "<7>"   /* 用于调试信息 */
复制代码

       /proc/sys/kernel/printk文件表述了printk的输出等级,我们可以查看当前输出等级,如下图所示:


可见,该文件有四个数值,它们对应的属性如下:

控制台日志级别:优先级高于该值的消息将被打印至控制台。
默认的消息日志级别:将用该优先级来打印没有优先级的消息。
最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)。
默认的控制台日志级别:控制台日志级别的默认值。
        也就是说,第一个7 表示级别高于(小于)7 的消息才会被输出到控制台,第二个4 表示如果调用printk 时没有指定消息级别(宏)则消息的级别为4 ,第三个1 表示接受的最高(最小)级别是1 ,第四个7 表示系统启动时第一个7 原来的初值是7
通过如下命令可以使Linux内核的任何printk都被输出: 
  1. echo  8  >  /proc/sys/kernel/printk
复制代码
我们在复杂驱动的开发过程中,为了调试会在源码中加入成百上千的printk语句。而当调试完毕形成最终产品的时候必然会将这些printk语句删除,这势必会增加我们的工作量。可以通过封装更高级的宏来减轻我们的工作量。
  1. #define LEDS_DEBUG
  2. #undef PDEBUG             /* undef it, just in case */
  3. #ifdef LEDS_DEBUG
  4.     #ifdef __KERNEL__
  5.      /* This one if debugging is on, and kernel space */
  6.         #define PDEBUG(fmt, args…) printk( KERN_EMERG "leds: " fmt, ## args)
  7.     #else
  8.      /* This one for user space */
  9.         #define PDEBUG(fmt, args…) fprintf(stderr, fmt, ## args)
  10.     #endif
  11. #else
  12.     #define PDEBUG(fmt, args…) /* not debugging: nothing */
  13. #endif
  14. #undef PDEBUGG
  15. #define PDEBUGG(fmt, args…) /* nothing: it’s a placeholder */
复制代码
这时如果想打印调试消息,可以用PDEBUG,如果不想看到该调试消息,只需要简单的将PDEBUG改为PDEBUGG即可。而当我们调试完毕形成最终产品时,只需要简单地将第1行注释掉即可。
上边那一段代码中的__KERNEL__是内核中定义的宏,当我们编译内核(包括模块)时,它会被定义。如果不明白代码中的…和##,请查阅gcc关于预处理部分的相关资料。
1.1.1    查看Oops消息
Oops是指当一段正常运行的内核程序执行一段不可预知的程序时,所产生的程序偏差。它会以错误的打印提示告知用户。比如当内核访问一个并不存在的虚拟地址,程序跑偏等。Oops信息会被打印到控制台,并写入系统打印的环形缓冲区。在移植驱动的过程中,经常会遇到内核打印出一长串莫名其妙的信息,我们可以分析这些信息来还原程序执行现场。
我们编写一个简单的字符设备驱动,让他产生Oops,让读写函数均访问0地址,程序如下:
  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/sched.h>
  5. #include <linux/delay.h>
  6. #include <linux/platform_device.h>
  7. #include <mach/hardware.h>
  8. #include <asm/mach-types.h>

  9. static ssize_t x4412_Oops_read(struct device *dev, struct device_attribute *attr, char *buf)
  10. {
  11.          int *p = 0;
  12.          *p = 1;

  13.          return 0;

  14. static ssize_t x4412_Oops_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
  15. {
  16.          int *p = 0;
  17.          *p = 1;

  18.          return 0;
  19. }

  20. static DEVICE_ATTR(Oops, 0666, x4412_Oops_read, x4412_Oops_write);

  21. static struct attribute * x4412_Oops_sysfs_entries[] = {
  22.          &dev_attr_Oops.attr,
  23.          NULL,
  24. };

  25. static struct attribute_group x4412_Oops_attr_group = {
  26.          .name         = NULL,
  27.          .attrs  = x4412_Oops_sysfs_entries,
  28. };

  29. static int x4412_Oops_probe(struct platform_device *pdev)
  30. {
  31.          return sysfs_create_group(&pdev->dev.kobj, &x4412_Oops_attr_group);
  32. }

  33. static int x4412_Oops_remove(struct platform_device *pdev)
  34. {
  35.          sysfs_remove_group(&pdev->dev.kobj, &x4412_Oops_attr_group);
  36.          return 0;
  37. }

  38. #define x4412_Oops_suspend        NULL
  39. #define x4412_Oops_resume         NULL

  40. static struct platform_driver x4412_Oops_driver = {
  41.          .probe                  = x4412_Oops_probe,
  42.          .remove               = x4412_Oops_remove,
  43.          .suspend     = x4412_Oops_suspend,
  44.          .resume                = x4412_Oops_resume,
  45.          .driver                  = {
  46.                    .name         = "x4412-Oops",
  47.          },
  48. };

  49. static struct platform_device x4412_Oops_device = {
  50.          .name      = "x4412-Oops",
  51.          .id        = -1,
  52. };

  53. static int __devinit x4412_Oops_init(void)
  54. {
  55.          int ret;

  56.          printk("x4412 Oops driver\r\n");

  57.          ret = platform_device_register(&x4412_Oops_device);
  58.          if(ret)
  59.                    printk("failed to register x4412 Oops device\n");

  60.          ret = platform_driver_register(&x4412_Oops_driver);
  61.          if(ret)
  62.                    printk("failed to register x4412 Oops driver\n");

  63.          return ret;
  64. }

  65. static void x4412_Oops_exit(void)
  66. {
  67.          platform_driver_unregister(&x4412_Oops_driver);
  68. }

  69. module_init(x4412_Oops_init);
  70. module_exit(x4412_Oops_exit);

  71. MODULE_LICENSE("GPL");
  72. MODULE_AUTHOR("lqm");
  73. MODULE_DESCRIPTION("x4412 Oops driver");
复制代码
        本段程序使用了 sys 文件系统,在加载后,将会在 /sys/devices/platform 下产生 x4412-Oops 目录,对应 x4412_Oops 目录下会创立 Oops 文件,如下图所示:


当我们使用cat指令查看Oops内容时,将会触发x4412_Oops_read函数,使用echo指令给Oops填写内容时,将会触发x4412_Oops_write函数。在读写函数中,前面的程序清单中加粗标红的内容,将会访问0地址。
    执行如下指令:
  1. cat  Oops
复制代码
    可以看到有如下打印信息出来:
  1. [root@x4412 x4412-Oops]# cat Oops
  2. [ 1569.887046] Unable to handle kernel NULL pointer dereference at virtual address 00000000
  3. [ 1569.894523] pgd = d6a90000
  4. [ 1569.896813] [00000000] *pgd=56f67831, *pte=00000000, *ppte=00000000
  5. [ 1569.902652] Internal error: Oops: 817 [#2] PREEMPT SMP
  6. [ 1569.907759] Modules linked in:
  7. [ 1569.910809] CPU: 0    Tainted: G      D      (3.0.15-9tripod #26)
  8. [ 1569.916909] PC is at x4412_Oops_read+0x18/0x20
  9. [ 1569.921317] LR is at dev_attr_show+0x28/0x50
  10. [ 1569.925570] pc : [<c022c3a4>]    lr : [<c022e9b4>]    psr: a0000013
  11. [ 1569.925594] sp : d6c73ed8  ip : d6c73ee8  fp : d6c73ee4
  12. [ 1569.937016] r10: bebbb988  r9 : 00001000  r8 : c05c5ef8
  13. [ 1569.942226] r7 : d6c73f70  r6 : d6c3ca80  r5 : d6937ea0  r4 : c0753afc
  14. [ 1569.948735] r3 : 00000000  r2 : 00000001  r1 : c0753afc  r0 : 00000000
  15. ……
  16. [ 1570.824538] Backtrace:
  17. [ 1570.827017] [<c022c38c>] (x4412_Oops_read+0x0/0x20) from [<c022e9b4>] (dev_attr_show+0x28/0x50)
  18. [ 1570.835706] [<c022e98c>] (dev_attr_show+0x0/0x50) from [<c014d808>] (sysfs_read_file+0xa4/0x154)
  19. [ 1570.844420]  r5:d6937ea0 r4:d6c3ca98
  20. [ 1570.848011] [<c014d764>] (sysfs_read_file+0x0/0x154) from [<c00fbbec>] (vfs_read+0xb4/0x148)
  21. [ 1570.856421] [<c00fbb38>] (vfs_read+0x0/0x148) from [<c00fbd5c>] (sys_read+0x44/0x74)
  22. [ 1570.864122]  r8:00000000 r7:00000003 r6:00001000 r5:bebbb988 r4:d6c5b6e0
  23. [ 1570.870852] [<c00fbd18>] (sys_read+0x0/0x74) from [<c0051b40>] (ret_fast_syscall+0x0/0x30)
  24. [ 1570.879053]  r9:d6c72000 r8:c0051ce8 r6:bebbb988 r5:00000003 r4:000aea20
  25. [ 1570.885754] Code: e24cb004 e3a03000 e3a02001 e1a00003 (e5832000)
  26. [ 1570.901538] ---[ end trace 7ced72ad36929528 ]---
  27. Segmentation fault
  28. [root@x4412 x4412-Oops]#
复制代码
    以上一堆莫名其妙的信息,就是Oops信息。从第一行可以看到,程序出错的原因是因为访问了“Null pointer”。第七行给出了事发现场,即x4412_Oops_read函数偏离0x18个字节处。
    我们可以通过反汇编来查询x4412_Oops_read函数偏离0x18个字节处对应的C代码。在ubuntu下进入对应的驱动生成目录:
  1. lqm@ubuntu:~/samba/x4412_ics_rtm_v10/kernel/drivers/char/oops$ ls
  2. built-in.o  Kconfig  Makefile  modules.builtin  modules.order  x4412-oops.c  x4412-oops.o
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值