:Linux设备驱动的调试
Posted zcj仲从建
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:Linux设备驱动的调试相关的知识,希望对你有一定的参考价值。
21.1 GDB调式的方法
GDB的四个功能:
- 启动程序,可以按照工程师自定义的要求运行程序
- 让被调使得程序可以在指定的地方停住,断点可以是条件表达式
- 当程序停住时,可以检查此程序发生的事,并追踪上文
- 动态的改变程序的执行环境
调式内核和应用程序时调试的命令是相同的
基本命令
- list命令(缩写l):列出代码
- list ,显示程序第linenum行周围的源代码
- list ,显示函数名为function的函数源程序
- list,显示当前行前后的源程序
- list -,显示当前行前面的源程序
- run命令:运行程序
- 程序运行参数
- set args:指定运行时的参数,如set args 10 20 30
- show args:查看设置好的运行参数
- 运行环境
- path
:设定程序的运行路径 - how paths:查看程序的运行路径
- set environment varname[=value]:设置环境变量,如set env USER=baohua;
- show environment[varname]:查看环境变量
- path
- 工作目录
- cd
:相当于shell的cd命令 - pwd:显示当前所在的目录
- cd
- 程序的输入输出
- info terminal:显示程序用到的终端的模式
- run>outfile:重定向控制程序输出
- tty:指定输入的终端设备,如tty/dev/ttyS1
- break命令
- break:在进入指定函数时停住
- break:指定行号停住
- break+offset/break-offset:当前 行号的前面或者后面offset行停住
- break filename:linenum:在源文件filename的linenum行处停住
- break filename:function:在源文件filename的function函数处停住
- break*address:在程序运行的内存地址处停住
- break:break命令没有参数时,表示在下一条指令处停住
- beak…if:…可以是上述的break、break+offset/break-offset中的参数,condition表示条件,在条件成立时停住
- 例如:在循环体中,可以设置break if i=100,表示当i为100时停住程序
- info:查看断点,如info breakpoints[n]、info break[n](n表示断点号)
- 单步命令
- step:单步跟踪,如果有函数调用,则进入该函数(进入该函数的前提是,此函数被编译有debug信息),默认一条条执行,加上count,执行后面count指令然后停止
- next:单步跟踪,有函数则跳过,不加count,一条条执行,加上count则执行后面count条之后停住
- set step-mode:set step-mode on用于打开step-mode模式
- 在进行step时,若跨过某没有调试信息的函数,程序的执行会在该函数的第一条指令处停住,而不会跳过整个函数,这样可以查看该函数的机器指令
- finish:运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址、返回值及参数值等信息
- until(缩写为u):一直在循环体内执行单步而退步出来是一件令人烦恼的事情,用until命令可以运行程序直到退出循环体
- stepi(缩写为si)和nexti(缩写为ni):这两个命里用于单步跟踪一条机器指令,step和next时C语言级别的命令
- 运行display/i $pc命令之后,单步跟踪会在大厨程序代买的同时打出机器指令,即汇编代码
- continue命令:当程序被停住后,可以用continue命令(缩写为c,fg命令同continue命令)恢复程序的执行直到程序结束,或到达下一个断点
- 命令格式为:continue/c/fg [ignore-count],ignore-count表示忽略其后多少次断点
- 例如:假设设置了函数断点add(),并观察i,则在continue过程中,每次遇到add()函数或者i发生变化,程序就会停住
- print命令:再掉是程序时,当程序被停住时,可以使用print命令(缩写为p),或是同义命令inspect来查看当前程序的运行数据
- 命令格式:print print / 其中是表达式,也是被调试的程序总的表达式,时输出的格式,比如,如果表达式按十六进制输出,则时/x
- 表达式中,有几种GDB所支持的操作符,他们可以用在任何一种语言中
- @:是一个和数组有关的操作符
- :: :指定一个在文件或是函数中的变量
- :表示一个指向内存地址的类型为type的对象
- 例1:演示了查看sum[]数组的值的过程
- 表达式中,有几种GDB所支持的操作符,他们可以用在任何一种语言中
- 当需要查看一段连续内存空间的值时,可以使用GDB的@操作符,@的左边是第一个内存地址,@的右边是想查看内存的长度
- 例2:动态申请内存
- 输出格式:
- x:十六进制
- d:十进制
- u:按十六进制,显示无符号整型
- o:八进制
- t:二进制
- a:十六进制
- c:字符格式
- f:浮点数格式
- display命令:设置一些自动显示的变量,当程序停住时,或是单步跟踪时,这些变量会自动显示
- 修改变量:print 变量=值
- 当GDB的print查看程序运行时的数据时,每个print都会被GDB记录下来。GDB会以 1, 2,$3。。。这样的方式为每一个print命令编号,可以用这个编号访问前面的表达式
- 命令格式:print print / 其中是表达式,也是被调试的程序总的表达式,时输出的格式,比如,如果表达式按十六进制输出,则时/x
- watch命令:观察某个表达式(变量也是一种表达式)的值是否有了变化,有则马上停止运行
- watch:为表达式expr设置一个观察点,一旦这个表达式发生了变化则停止运行
- rwatch:当表达式(变量)被读时,停止程序执行
- awatch:当表达式(变量)的值被读或者被写时,停止运行
- info watchpoints:列出当前所设置的所有观察点
- examine命令:查看内存地址中的值
- 语法:x/
- list命令(缩写l):列出代码
**//例1:**
(gdb) print sum
$2 = 133, 155, 0, 0, 0 ,0 ,0 ,0 ,0 ,0
(gdb) next
Breakpoint 1, main () at gdb-example.c:25
25 sum[i] = add(array1[i], array2[i]):
(gdb) next
23 for(i = 0; i< 10; i++)
(gdb) print sum
$3 = (133, 155, 143, 0, 0 ,0 ,0 ,0 ,0 ,0
//例2:
int *array = (int *) malloc (len * sizeof(int));
在GDB调试过程中个,这样实现这个动态数组的值:
p *array@len
//例3:
main()
void *p = malloc(16);
while(1);
//用如下命令来修改p指向的内存
(gdb) set *(unsigned char *)p='h'
(gdb) set *(unsigned char *)p='e'
(gdb) set *(unsigned char *)p='l'
(gdb) set *(unsigned char *)p='l'
(gdb) set *(unsigned char *)p='e'
//查看结果
(gdb) x/s p
0x804b008 "hello"
//查看函数func反汇编代码
(gdb) disassemble func
Dump of assembler code for function func:
0x8048450 <func>: push %ebp
0x8048451 <func+1> mov %esp,%ebp
...
21.2 内核调试
21.2.1 内核打印信息—-printk()
- 内核打印语句printk()会将内核信息输出到内核信息缓冲区中,内核缓冲区时候在kernel/printk.c中通过以下语句静态定义:
- 内核缓冲区是一个环形缓冲区(Ring Buffer),如果消息过多,则会将之前的消息冲刷掉
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
printk()的8个消息级别,分别是0~7,级别越低(数值越大),消息越不重要,0级时紧急事件级,7级时调试级
- #define KERN_EMERG “<0>”:紧急事件,一般是系统崩溃前的提示信息
- #define KERN_ALERT “<1>”:必须立即采取行动
- #define KERN_CRIT “<2>”:临界状态,通常涉及严重的硬件或软件操作失败
- #define KERN_ERR “<3>”:用于报告错误状态,设备驱动程序会经常调用KERN_ERR来报告来自硬件的问题
- #define KERN_WARNING “<4>”:对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重的问题
- #define KERN_NOTICE “<5>”:有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报
- #define KERN_INFO “<6>”:内核提示信息,很多驱动程序在启动的时候,用这个级别打印他们找到的硬件信息
- #define KERN_DEBUG “<7>”:用于调试
通过/proc/sys/kernel/printk文件可以调节printk()的输出等级,该文件有4个等级
- 控制台日志级别:当前的打印级别,优先级高于该值的信息将被打印纸控制台
- 默认的信息日志级别:将用该优先级来打印没有优先级前缀的消息,也就是直接写printk(“xxx”)而不带打印级别的情况下,会用该级别打印
- 最低的控制台日志级别:控制台日志级别可被设置的最小值(一般都是1)
- 默认的控制台日志级别:控制台日志级别的默认值
- 例5:Ubuntu上的输出级别
//例5:Ubuntu上的输出级别
$ cat /proc/sys/kernel/printk
4 4 1 7
显示内核打印信息方法
- 通过dmesg命令,如果使用dmesg -c命令,则不仅会显示__log_buf,还会清除该缓冲区的内容
- 使用cat /proc/kmsg 命令,/proc/kmsg是一个“永无休止的文件”,因此,cat /porc/kmsg的进程只能通过“Ctrl+C”或kill终止
设备驱动中的调试函数
- pr_debug(),pr_info()
- 使用pr_xxx()族API的好处是,可以在文件开头通过pr_fmt()定义一个打印格式
- 例6:在kernel/watchdog.c的最开头通过如下定义可以保证之后watchdog.c调用的所有pr_xxx()打印的消息都自动带有“NMI watchdog: ”的前缀
- dev_debug():如dev_dbg()、dev_err()、dev_info()等
- 使用dev_xxx()族API打印的时候,设备名称会被自动加到打印消息的前头
- 打印的附加信息,例7
- func:输出printk()调用所在的函数名
- LINE:输出其所在的代码行
- FILE:输出源代码命令名
- pr_debug(),pr_info()
//pr_debug()与pr_info()定义
#ifdef DEBUG
#define pr_debug(fmt,arg...) \\
printk(KERN_DEBUG fmt,##arg)
#else
static inline int __attribute__ ((format (printf,1,2))) pr_debug(cost char * fmt, ...)
return 0;
#endif
#define pr_infor(fmt,arg ...) \\
printk(KERN_INFO fmt, ##arg)
//例6:
#define pr_fmt(fmt) "NMI watchdog: " fmt
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/nmi.h>
...
//例7:
printk(KERN_ERR "Assertion failed! %s,%s,%s,line=%d", #expr, __FILE__, __func__, LINE);
21.2.2 DEBUG_LL和EARLY_PRINTK
- DEBUG_LL对应内核的Kernel low-level debugging功能,EARLY_PRINTK对应内核中一个早起的控制台
- 为了在内核的drivers/serial下的控制台驱动初始化之前支持打印,可以选择上述两个配置项,另外需要在bootargs中设置earlyprintk的选项
21.2.3 使用“/proc”
- “/proc”是一个虚拟文件系统,通过它可以在Linux内核空间和用户控件之间进行通信
- 在“/proc”文件系统中,可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,这些虚拟文件的内容都是动态的
- “/proc”下的绝大多数文件是只读的,以显示内核信息为主,也不都是只读,如修改/proc/sys/kernel/printk以改变printk()的打印级别
- Linux系统的许多命令本身都是通过分析”/proc”下的文件来完成的,如ps、top等,例如,free命令通过分析/proc/meminfo文件的到可用内存信息
21.2.4 Oops
- 内核出现类似用户空间的Segmentation Fault时,Oops会被打印到控制台和写入内核log缓冲区
- 反汇编一个目标文件:arm-linux-gnueabihf-objdump -d -s xxx.o
21.2.5 BUG_ON()和WARN_ON()
- 内核中许多地方调用类似BUG()的语句,它非常像一个内核运行时的断言,以为这本来不该执行到BUG()这条语句,一旦执行到即跑出Oops
- BUG()语句定义如下:
- panic()定义在kernel/panic.c中,会导致内核崩溃,并打开Oops
- BUG()语句定义如下:
#define BUG() do \\
printk("BUG: failure at %s:%d/%s()!\\n", __FILE__, __LINE__, __func__"); \\
panic("BUG!");
while(0)
- BUG_ON():时BUG()的变体,只有当括号内的条件成立的时候,才抛出Oops
- WARN_ON():在括号里的条件成立的时候,内核会抛出栈回溯,但是不会panic(),这通常用于内核抛出一个警告,暗示某种不太合理的事情发生了
21.2.8 strace
- strace是个有效的跟踪工具,它的主要特点是可以被用来监视系统调用
- 既可以调试一个新开始的程序,也可以调试一个已经在运行的程序(这意味着把strace绑定到一个已有的PID上)
以上是关于:Linux设备驱动的调试的主要内容,如果未能解决你的问题,请参考以下文章
从 pngs 创建 gif:循环图像似乎太快了,只能在调试模式下工作