Linux驱动开发(外传)---驱动开发调试方法

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》
《Linux驱动开发(十三)—USB驱动HID开发学习(鼠标)》
《Linux驱动开发(十四)—USB驱动开发学习(键盘+鼠标)》
《Linux驱动开发(十五)—如何使用内核现有驱动(显示屏)》
《Linux驱动开发(十六)—块设备驱动》
《Linux驱动开发(十七)—树莓派PWM驱动》
《Linux驱动开发(十八)—网络(网卡)驱动学习》

这篇学习一下内核开发或者是驱动开发的一些调试方法,算是系列内容的番外篇吧。
Linux驱动开发(外传)---驱动开发调试方法_第1张图片

printk

用的最多,最出名的调试手段。
举个例子

printk(KERN_CRIT  “Hello, world!\n”); 

printk可以指定一个LOG等级。

#define KERN_EMERG	KERN_SOH "0"	/* system is unusable */
#define KERN_ALERT	KERN_SOH "1"	/* action must be taken immediately */
#define KERN_CRIT	KERN_SOH "2"	/* critical conditions */
#define KERN_ERR	KERN_SOH "3"	/* error conditions */
#define KERN_WARNING	KERN_SOH "4"	/* warning conditions */
#define KERN_NOTICE	KERN_SOH "5"	/* normal but significant condition */
#define KERN_INFO	KERN_SOH "6"	/* informational */
#define KERN_DEBUG	KERN_SOH "7"	/* debug-level messages */

#define KERN_DEFAULT	""		/* the default kernel loglevel */

内核根据这个等级来判断是否在终端上打印消息。内核把比指定等级高的所有消息显示在终端。这个指定的等级可以在这里看到

root@raspberrypi:/home/pgg/work/driver# cat /proc/sys/kernel/printk             
3       4       1       3

第一个3就是当前的指定等级,当小于(等级高于)3的时候,会直接在终端输出;
第二个4表示,当没有指定log等级时候的默认等级;
第三个1表示最高等级是1;
第四个3表示默认控制台的log等级为3
Linux驱动开发(外传)---驱动开发调试方法_第2张图片

如果我们要关闭某个模块的打印,可以通过修改全局的方式,但是这样就会关闭相同级别的打印,所以还是在模块内部通过宏定义进行打印的控制更便捷。
下面是一个例子。

#include    
#include  
#include  

#define XXX_DEBUG "XXX:Line:%d "
#undef PDEBUG             /* undef it, just in case */
#ifdef XXX_DEBUG
    #ifdef __KERNEL__
		  /* This one if debugging is on, and kernel space */
        #define PDEBUG(fmt, args...) printk( KERN_EMERG XXX_DEBUG fmt,__LINE__, ## args)
    #else
		  /* This one for user space */
        #define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
    #endif
#else
    #define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif
#undef PDEBUGG
#define PDEBUGG(fmt, args...) /* nothing: it’s a placeholder */
	 

  
static int __init hello_init(void)
{
    PDEBUG("hello world\n");
    return 0;
}
 
static void __exit hello_exit(void)
{
    PDEBUG("byebye world\n");
}
module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

在加载模块和卸载模块的时候,会有打印输出

root@raspberrypi:/home/pgg/work/driver# insmod kernel_debug.ko 
root@raspberrypi:/home/pgg/work/driver# 
Message from syslogd@raspberrypi at Aug 15 03:07:54 ...
 kernel:[ 2735.541133] XXX:Line:25 hello world

root@raspberrypi:/home/pgg/work/driver# rmmod kernel_debug

Message from syslogd@raspberrypi at Aug 15 03:08:02 ...
 kernel:[ 2743.211753] XXX:Line:31 byebye world
root@raspberrypi:/home/pgg/work/driver# 
root@raspberrypi:/home/pgg/work/driver# dmesg 
[ 2735.541133] XXX:Line:25 hello world
[ 2743.211753] XXX:Line:31 byebye world
root@raspberrypi:/home/pgg/work/driver# 

Linux驱动开发(外传)---驱动开发调试方法_第3张图片

oops

中文可以译为——WC。

在linux中,oops表示“惊讶”,是一种信息提示,意味着系统上运行的某些东西违反了内核规定的规则;oops会生成一个崩溃签名“crash signature”,可以帮助内核开发人员找出错误并提高代码质量。
其实这不是一个主动调用的方法,而是一个被动触发的系统技能,也许代码尝试采取不允许的代码路径或使用无效指针。不管它是什么,内核 —— 总是在监测进程的错误行为 —— 很可能会阻止特定进程,并将它做了什么的消息写入控制台、 /var/log/dmesg 或 /var/log/kern.log 中。
Linux驱动开发(外传)---驱动开发调试方法_第4张图片

所以可以通过一个错误例子,来触发一下这个

static int __init hello_init(void)
{
	*(int *)0 = 0;
    return 0;
}

通过dmesg查看一下oops消息。

[ 5228.069985] 8<--- cut here ---
[ 5228.070030] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[ 5228.070055] pgd = 9ac88ec0
[ 5228.070078] [00000000] *pgd=1927b835, *pte=00000000, *ppte=00000000
[ 5228.070135] Internal error: Oops: 817 [#1] SMP ARM
[ 5228.070152] Modules linked in: kernel_debug(+) rfcomm cmac algif_hash aes_arm_bs crypto_simd cryptd algif_skcipher af_alg bnep hci_uart btbcm bluetooth ecdh_generic ecc 8021q garp stp llc bmp280_i2c bmp280 industrialio regmap_i2c snd_soc_hdmi_codec brcmfmac vc4 cec brcmutil drm_kms_helper sha256_generic snd_soc_core cfg80211 snd_compress snd_pcm_dmaengine raspberrypi_hwmon rfkill syscopyarea sysfillrect sysimgblt fb_sys_fops i2c_bcm2835 pwm_bcm2835 snd_bcm2835(C) snd_pcm spi_bcm2835 snd_timer bcm2835_isp(C) bcm2835_codec(C) bcm2835_v4l2(C) v4l2_mem2mem snd bcm2835_mmal_vchiq(C) videobuf2_dma_contig videobuf2_vmalloc videobuf2_memops videobuf2_v4l2 vc_sm_cma(C) videobuf2_common videodev mc fixed uio_pdrv_genirq uio i2c_dev drm drm_panel_orientation_quirks fuse backlight ip_tables x_tables ipv6 [last unloaded: kernel_debug]
[ 5228.070615] CPU: 1 PID: 1524 Comm: insmod Tainted: G         C        5.15.55-v7+ #5
[ 5228.070635] Hardware name: BCM2835
[ 5228.070647] PC is at hello_init+0x18/0x1000 [kernel_debug]
[ 5228.070678] LR is at do_one_initcall+0x50/0x250
[ 5228.070704] pc : [<7f08d018>]    lr : [<80102368>]    psr: 60000013
[ 5228.070718] sp : 992f3d90  ip : 992f3da0  fp : 992f3d9c
[ 5228.070732] r10: 8f8bf968  r9 : 801ccb14  r8 : 8f8bf940
[ 5228.070745] r7 : 00000000  r6 : 7f08d000  r5 : 80f05008  r4 : 7f321000
[ 5228.070759] r3 : 9f54d47a  r2 : 9f54d47a  r1 : b775bbb4  r0 : 00000000
[ 5228.070774] Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
[ 5228.070794] Control: 10c5383d  Table: 1907c06a  DAC: 00000055
[ 5228.070806] Register r0 information: NULL pointer
[ 5228.070825] Register r1 information: non-slab/vmalloc memory
[ 5228.070844] Register r2 information: 0-page vmalloc region starting at 0x9ec00000 allocated at iotable_init+0x0/0x100
[ 5228.070876] Register r3 information: 0-page vmalloc region starting at 0x9ec00000 allocated at iotable_init+0x0/0x100
[ 5228.070904] Register r4 information: 4-page vmalloc region starting at 0x7f31f000 allocated at load_module+0x934/0x288c
[ 5228.070937] Register r5 information: non-slab/vmalloc memory
[ 5228.070955] Register r6 information: 2-page vmalloc region starting at 0x7f08d000 allocated at load_module+0x2618/0x288c
[ 5228.070984] Register r7 information: NULL pointer
[ 5228.071001] Register r8 information: slab kmalloc-64 start 8f8bf940 pointer offset 0 size 64
[ 5228.071045] Register r9 information: non-slab/vmalloc memory
[ 5228.071063] Register r10 information: slab kmalloc-64 start 8f8bf940 pointer offset 40 size 64
[ 5228.071103] Register r11 information: non-slab/vmalloc memory
[ 5228.071120] Register r12 information: non-slab/vmalloc memory
[ 5228.071137] Process insmod (pid: 1524, stack limit = 0xe1a75653)
[ 5228.071154] Stack: (0x992f3d90 to 0x992f4000)
[ 5228.071173] 3d80:                                     992f3e14 992f3da0 80102368 7f08d00c
[ 5228.071194] 3da0: 00000001 80a47df0 80f05830 00000000 992f3dd4 992f3dc0 80a47df0 8019fa64
[ 5228.071215] 3dc0: 80f05830 80335be8 992f3e14 992f3dd8 80335be8 802f34a4 00000000 00000000
[ 5228.071235] 3de0: 00000008 80a40f94 992f3f30 9f54d47a 992f3f30 7f321000 992f3f30 8f8bf180
[ 5228.071256] 3e00: 00000001 8f8bf940 992f3e3c 992f3e18 80a40fb4 80102324 992f3e3c 992f3e28
[ 5228.071277] 3e20: 80317a18 7f321000 992f3f30 00000001 992f3f14 992f3e40 801cffc8 80a40f6c
[ 5228.071298] 3e40: 7f32100c 00007fff 7f321000 801cd1bc 80d14718 80d14730 80d146c0 80d146b4
[ 5228.071319] 3e60: 00000000 7f08e444 7f321114 7f321214 80d14780 7f322000 7f321048 80f05008
[ 5228.071339] 3e80: 80d1429c 00000001 00000000 80dc6260 80daf464 00000000 00000000 00000000
[ 5228.071359] 3ea0: 00000000 00000000 6e72656b 00006c65 00000000 00000000 00000000 00000000
[ 5228.071378] 3ec0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 5228.071399] 3ee0: 00000000 9f54d47a 992f3f2c 80f05008 00000000 0002de04 00000003 80100244
[ 5228.071421] 3f00: 992f2000 0000017b 992f3fa4 992f3f18 801d0d70 801ce270 992f3f2c 7fffffff
[ 5228.071441] 3f20: 00000000 00000002 992f3f24 b88e0000 b88e00de b88e0240 b88e0000 00000e2c
[ 5228.071462] 3f40: b88e0a44 b88e095c b88e07f8 00003000 000030c0 0000141c 0000310d 00000000
[ 5228.071482] 3f60: 00000000 00000000 0000140c 00000016 00000017 0000000e 0000000c 00000007
[ 5228.071503] 3f80: 00000000 9f54d47a 00000000 00000002 0b35ed00 0000017b 00000000 992f3fa8
[ 5228.071523] 3fa0: 80100040 801d0cb4 00000000 00000002 00000003 0002de04 00000000 76fcc074
[ 5228.071544] 3fc0: 00000000 00000002 0b35ed00 0000017b 00e32298 00000002 7eda9794 00000000
[ 5228.071565] 3fe0: 7eda95c0 7eda95b0 00023bc0 76cb29e0 60000010 00000003 00000000 00000000
[ 5228.071578] Backtrace: 
[ 5228.071593] [<7f08d000>] (hello_init [kernel_debug]) from [<80102368>] (do_one_initcall+0x50/0x250)
[ 5228.071631] [<80102318>] (do_one_initcall) from [<80a40fb4>] (do_init_module+0x54/0x218)
[ 5228.071670]  r8:8f8bf940 r7:00000001 r6:8f8bf180 r5:992f3f30 r4:7f321000
[ 5228.071682] [<80a40f60>] (do_init_module) from [<801cffc8>] (load_module+0x1d64/0x288c)
[ 5228.071716]  r6:00000001 r5:992f3f30 r4:7f321000
[ 5228.071727] [<801ce264>] (load_module) from [<801d0d70>] (sys_finit_module+0xc8/0xfc)
[ 5228.071765]  r10:0000017b r9:992f2000 r8:80100244 r7:00000003 r6:0002de04 r5:00000000
[ 5228.071779]  r4:80f05008
[ 5228.071791] [<801d0ca8>] (sys_finit_module) from [<80100040>] (ret_fast_syscall+0x0/0x1c)
[ 5228.071820] Exception stack(0x992f3fa8 to 0x992f3ff0)
[ 5228.071839] 3fa0:                   00000000 00000002 00000003 0002de04 00000000 76fcc074
[ 5228.071860] 3fc0: 00000000 00000002 0b35ed00 0000017b 00e32298 00000002 7eda9794 00000000
[ 5228.071877] 3fe0: 7eda95c0 7eda95b0 00023bc0 76cb29e0
[ 5228.071895]  r7:0000017b r6:0b35ed00 r5:00000002 r4:00000000
[ 5228.071914] Code: e24cb004 e52de004 e8bd4000 e3a00000 (e5800000) 
[ 5228.071932] ---[ end trace 1e2305052fc95bd2 ]---

出错原因,空指针
在这里插入图片描述
堆栈信息,查看调用关系
Linux驱动开发(外传)---驱动开发调试方法_第5张图片
其他的部分涉及到了寄存器,类似汇编的地方,还需要深入学习一下。
Linux驱动开发(外传)---驱动开发调试方法_第6张图片

dmesg

前面已经用到了这个命令,这个就是查看内核的输出缓存记录,里面包含了从开机启动到当前,内核中输出的信息。
简单列举几个常用方法

参考《dmesg七种用法》

分页显示

我们可以使用如‘more’,‘tail, ‘less ’或者‘grep’文字处理工具来处理‘dmesg’命令的输出。
由于dmesg日志的输出不适合在一页中完全显示,因此我们使用管道(pipe)将其输出送到more或者less命令单页显示。

[[email protected] ~]# dmesg | more
[[email protected] ~]# dmesg | less

详细用法可以参考《Shell命令-文件及内容处理之more、less》

前后几行

只选择显示前面或者后面几行

[[email protected] ~]# dmesg | head  -20
[[email protected] ~]# dmesg | tail -20

或者清空了数据,重新测试

[[email protected] ~]# dmesg -c

Linux驱动开发(外传)---驱动开发调试方法_第7张图片

选择等级显示

dmesg --level=err,warn

然后系统的error以及程序员都不看的warning就展示了出来:
level选项还可以填入别的等级,例如emerg、alert、crit、err、warn、notice、info、debug。

实时监控dmesg日志输出

在某些发行版中可以使用命令来实时监控dmesg的日志输出。

tail -f /var/log/dmesg

也有的用

tail -f /var/log/kern.log

还可以用

[[email protected] log]# watch "dmesg | tail -20"

Linux驱动开发(外传)---驱动开发调试方法_第8张图片

内核调试

通过配置menuconfig,在hacking中,有很多调试选项,感兴趣的可以试试

Linux驱动开发(外传)---驱动开发调试方法_第9张图片
Linux驱动开发(外传)---驱动开发调试方法_第10张图片

魔键

如果一台服务器,连屏幕没反应,ssh远程连接不上,还有什么办法?这里还有一个魔键。参考资料
《【调试工具】【sysrq】Sysrq魔术键介绍》
Linux驱动开发(外传)---驱动开发调试方法_第11张图片

驱动相关

前面的其实都是内核调试通用的做法,这里将一下驱动相关的调试。首先就是利用printk在各个入口函数,例如加载函数,probe函数,open 函数这类关键函数,增加调试打印。

dts问题

Status

在编写或者修改dts的时候,一定要注意status这个值,是不是没有添加,或者没有设置为okey。

查看设备树

通过下面两个命令,可以查看当前设备树注册的部分设备,例如我们自己定义的gpio设备

ls /sys/firmware/devicetree/base

或者

ls /proc/device-tree/
设备种类 查看位置
输入子系统设备 /dev/input
I2C从设备 /sys/bus/i2c/devices/
SPI从站 /sys/bus/spi/devices/
framebuffer /dev/fbx
块设备 /dev/
PWM /sys/class/pwm/
网络设备 /sys/class/net/

与内核进行信息交互

参考前面的一篇博客《Linux小知识—内核与用户态通讯方式之procfs》,也可以实时输出内核的一些信息。
不过这个是单向查看,如果需要双向数据传输,可以通过ioctl函数进行开发。

Linux驱动开发(外传)---驱动开发调试方法_第12张图片

部分参考资料

《linux驱动程序调试方法》

结束语

一个很温馨的小故事。
Linux驱动开发(外传)---驱动开发调试方法_第13张图片

你可能感兴趣的:(驱动开发,操作系统,linux知识,驱动开发,linux,调试)