文章目录
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
下每个文件都绑定到一个内核函数上;ps
、top
、uptime
等都是从/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
,使用gdb
、kbd
等调试工具时需勾选此项
4.5 调试工具
4.5.1 gdb
- 基本概念:全称GNU Debugger,是GNU开源组织发布的一个强大的UNIX下的程序调试工具
- 功能:启动程序,可按照工程师自定义的要求随心所欲地运行程序;让被调试的程序在工程师指定的断点处停住,断点可以是条件表达式;当程序被停住时,可以检查此时程序中所发生的事,并追索上文;动态地改变程序的执行环境
- 使用:
gdb vmlinux
(vmlinux
是个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
- 基本概念:
kdb
与kgdb
在代码上合二为一,共用debug core,功能上又保持独立。kdb
提供一个shell形式的调试控制台,可让kdb
和kgdb
互相切换;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;
}