Linux驱动开发学习-04.调试技术

04. 调试技术

4.1 printk

  • 打印优先级(<linux/kernel.h>):

    KERN_EMERG>KERN_ALERT>KERN_CRIT>KERN_ERR>KERN_WARNING>KERN_NOTICE>KERN_INFO>KERN_DEBUG

  • console_loglevel:其初始值为DEFAULT_CONSOLE_LOGLEVEL,可通过klogd -c指定,也可通过proc/sys/kernel/printk修改控制台记录级别

  • 调用时机:printk可以从任何地方调用,甚至中断中

  • 频率限制:过量printk消息会压倒控制台,拖慢系统;常常重复的消息,使用printk_ratelimit限制打印频率;可以通过修改/proc/sys/kern/printk_ratelimit(在重新使能消息前等待的秒数)和/proc/sys/kernel/printk_ratelimit_burst(限速前可接收的消息数)来定制

  • 基本原理:printk将消息写入环形缓存中(__LOG_BUF_LEN,长度值由配置内核时选择),若环形缓存填满,printk将绕回并覆盖开头的数据,因此可能丢失部分数据

  • 备注printk只有提供一个结尾的新行才会刷新

4.2 /proc文件系统

4.2.1 基本概念

  • 概念:/proc文件系统是一种内核和内核模块用来向进程(process) 发送信息的机制,是一个虚拟的文件系统,让用户可以和内核内部数据结构进行交互/proc存在于内存之中而不是硬盘上;proc文件系统以文件的形式向用户空间提供了访问接口,这些接口可以用于在运行时获取相关部件的信息或者修改部件的行为
  • 内容读取:cd /proc && ls可看到带数字的文件夹,每个数字对应一个进程的ID,每个数字文件夹基本有以下内容
cmdline - 启动进程时调用的命令
environ - 进程环境变量
status - 进程状态信息
exe - 运行的进程的可执行程序
fd - 指向进程使用的文件描述符的链接

除数字文件夹之外,还有其他重要的文件

/proc/cpuinfo - CPU 的信息(型号, 家族, 缓存大小等)
/proc/meminfo - 物理内存、交换空间等的信息
/proc/mounts - 已加载的文件系统的列表
/proc/devices - 可用设备的列表
/proc/filesystems - 被支持的文件系统
/proc/modules - 已加载的模块
/proc/version - 内核版本
/proc/cmdline - 系统启动时输入的内核命令行参数

4.2.2 相关源码

  • /proc下每个文件都绑定到一个内核函数上pstopuptime等都是从/proc下获取信息
  • read_proc<linux/proc_fs.h>)在文件被读时产生数据,当某个进程读文件时(使用 read 系统调用),这个请求通过该函数到达模块
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);
  • creat_proc_read_entry(将read_proc连接到/proc的入口)
struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode,struct proc_dir_entry *base, read_proc_t *read_proc, void *data);

//创建一个名为scullmem的文件
create_proc_read_entry("scullmem", 0 /* default mode */,
NULL /* parent dir */, scull_read_procmem,
NULL /* client data */);
remove_proc_entry("scullmem", NULL /* parent dir */);

4.3 ioctl

4.3.1 基本概念

  • 使用ioctl系统调用是用户空间向内核交换数据的常用方法之一,本意是针对I/O设备进行的控制操作,但实际并不限制是真正的I/O设备,可以是任何一个内核设备

4.3.2 相关源码

  • 应用层:ioctl
int ioctl(int fd, int cmd, void *data)
  • 驱动层:xxx_ioctl()
static int xxx_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg)
  • 代码操作:以字符设备驱动为例,即实现iotcl函数即可

4.4 oops消息

  • 基本概念:当内核出错时(比如访问非法地址)打印出来的信息被称为 oops 信息(可以看成是内核级的Segmentation Fault);打印信息包括文本描述、信息序号、模块名称、发生错误的CPU序号、发生错误的CPU的各寄存器值、当前进程名字及ID、栈信息等
  • 产生机制:由于处理器使用的地址几乎都是虚拟地址,这些地址通过一个被称为页表的结构被映射为物理地址;当引入一个非法指针的时候,分页机制无法将该地址映射到物理地址,此时处理器就会向操作系统发出一个页面失效(page fault)的信号;如果地址非法换入(page in)缺失页面,这时,如果处理器恰好处于超级用户模式,系统就会产生一个Oops
  • 编译配置:Kernel hacking -> Compiler options -> Compile the kernel with debug info,使用gdbkbd等调试工具时需勾选此项

4.5 调试工具

4.5.1 gdb

  • 基本概念:全称GNU Debugger,是GNU开源组织发布的一个强大的UNIX下的程序调试工具
  • 功能:启动程序,可按照工程师自定义的要求随心所欲地运行程序;让被调试的程序在工程师指定的断点处停住,断点可以是条件表达式;当程序被停住时,可以检查此时程序中所发生的事,并追索上文动态地改变程序的执行环境
  • 使用:gdb vmlinuxvmlinux是个elf文件,它的符号表中包含了所有内核符号,可通过readelf -a vmlinux 命令查看内容),若不勾选Compile the kernel with debug info将无法查看符号表
Reading symbols from vmlinux...(no debugging symbols found)...done.

4.5.2kgdb工具

  • 基本概念:kgdb是仅用于调试内核的一种源码级调试器,kgdb运行在核内,自身不能单独用来调试内核,需要远端gdb通过串口或网络远程连接上来进行调试,连上之后就可以像调试核外应用程序一样调试内核,如设置断点、查看数据结构,打印调用栈等

4.5.3 kdb

  • 基本概念:kdbkgdb在代码上合二为一,共用debug core,功能上又保持独立。kdb提供一个shell形式的调试控制台,可让kdbkgdb互相切换;kdb只支持汇编级的调试,不像kgdb支持c语言级别的调试,只能通过裸内存地址查看变量、设置断点。其内建的命令也可以用来查看cpu调用栈,进程信息等。kdb可单独使用,不需要远程机器的辅助
  • 值得一提的是gdb连上kgdb之后可以使用kdb的全部功能,所以从开发者的角度来说kgdb更具有亲和性

4.7 在scull中的应用

  • 打印设备编号
int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);
  • 使用宏
//scull.h
#undef PDEBUG /* undef it, just in case */

#ifdef SCULL_DEBUG

# ifdef __KERNEL__
/* This one if debugging is on, and kernel space */
# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## 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 */
  • proc
int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data)
{
    int i, j, len = 0;
    int limit = count - 80; /* Don't print more than this */
    for (i = 0; i < scull_nr_devs && len <= limit; i++) {
        struct scull_dev *d = &scull_devices[i];
        struct scull_qset *qs = d->data;
        if (down_interruptible(&d->sem))
            return -ERESTARTSYS;
        len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n", 
                       i, d->qset, d->quantum, d->size);
        for (; qs && len <= limit; qs = qs->next) { /* scan the list */
            len += sprintf(buf + len, " item at %p, qset at %p\n", qs, qs->data);
            if (qs->data && !qs->next) /* dump only the last item */
                for (j = 0; j < d->qset; j++) {A
                            if (qs->data[j])
                            len += sprintf(buf + len, " % 4i: %8p\n", j, qs->data[j]);
                }
        }
        up(&scull_devices[i].sem);
    }
    *eof = 1;
    return len;
}

4.7 本文参考

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值