技术交流

好好学习,天天向上。

0%

第四章——调试技术

介绍

第四章讲调试技术,事实上除了本章介绍的几种调试技术以外,还有利用Qemu、利用vmcore+crash的调试技术,后面会单独出博文来分别介绍。

内核调试配置项

内核提供了一些配置选项来支持内核调试,不知道这些选项在最新的内核还有用没有,用得时候最好再查一下。

选项名称 功能
CONFIG_DEBUG_KERNEL 内核调试总开关,要调试使用内核调试功能,该选项就必须打开
CONFIG_DEBUG_SLAB 用于检测SLAB内存泄漏,在内存被申请前和释放后,内核会分别填充0xa5和0x6b。如果内核踩内存,发生oops,则可以通过这些特殊值来判断踩踏位置和原因。
CONFIG_DEBUG_PAGEALLOC 当内存被释放的时候,将其完全移出内核地址空间。会降低内核性能,但也能快速定位内存错误。
CONFIG_DEBUG_SPINLOCK 开启该选项,内核将捕获对未初始化的自旋锁的使用,也会捕捉重复开锁等错误。
CONFIG_DEBUG_SPINLOCK_SLEEP 该选项让内核检查内核代码是否在持有锁的时候企图睡眠,哪怕是有“可能会睡眠”也报错。
CONFIG_INIT_DEBUG 标记为__init(或者__initdata)的那些代码和数据应该在初始化完成后被内核丢掉,开启该选项可以让内核检查对那些本该丢掉的内容的访问。
CONFIG_DEBUG_INFO 让内核代码包含完整的调试信息(调试符号表),如果内核将用gdb调试,还要打开CONFIG_DEBUG_INFO
CONFIG_MAGIC_SYSRQ Enables the “magic SysRq” key. We look at this key in the section “System Hangs,” later in this chapter. // TODO 讲到再说
CONFIG_DEBUG_STACKOVERFLOW 和下面的配置项一起使用
CONFIG_DEBUG_STACK_USAGE 上下两个选项,前一个选项打开内核栈溢出检查,这个选项打开内核栈用量统计。可以和SysRq配合使用,输出统计信息。
CONFIG_KALLSYMS This option (under “General setup/Standard features”) 默认是开启的,决定oops的时候打印的trace信息是函数名称还是地址数据。
CONFIG_IKCONFIG 和下面的配置项一起使用
CONFIG_PROC These options (found in the “General setup” menu) 将内核配置选项导出到/proc中,方便查看。
CONFIG_ACPI_DEBUG Under “Power management/ACPI.” This option turns on verbose ACPI (Advanced Configuration and Power Interface) debugging information, which can be useful if you suspect a problem related to ACPI.高级电源管理相关。
CONFIG_DEBUG_DRIVER Under “Device drivers.” 该选项使能driver core中的debug信息,该信息可以用于分析底层代码。
CONFIG_SCSI_CONSTANTS This option, found under “Device drivers/SCSI device support,” 详细记录SCSI错误信息,编写SCSI驱动可以启用该选项。
CONFIG_INPUT_EVBUG This option (under “Device drivers/Input device support”) 原封不动的记录输入内容,包括密码,可以用来调试输入设备的驱动代码。
CONFIG_PROFILING This option is found under “Profiling support.” ,用于系统性能调节,对跟踪内核挂死问题也有用。

打印

事实上,内核打印函数printk是很高级的,在之前的调试过程中都没有用到它的log等级的概念,在这里专门梳理一下它的功能。

log等级 对应事件类型
KERN_EMERG 系统要崩了用这个打印
KERN_ALERT 该情况下最好立即采取行动
KERN_CRIT 紧迫状态,通常涉及严重的硬件或者软件失败
KERN_ERR 出现了错误,一般是驱动报告硬件错误时使用
KERN_WARNING 类比于编程的warning,可以跑,但最好不这么搞得时候的打印
KERN_NOTICE 正常的提醒,许多安全相关的状态用这个log等级,告知用户需要注意的东西
KERN_INFO 信息,比如驱动跑到某某阶段了,打印一下某些关键的内容
KERN_DEBUG Used for debugging messages.

其实上表中的宏,从上往下对应0-7的数字,数字越小优先级越高。当printk不写log等级的时候,它用的其实是DEFAULT_MESSAGE_LOGLEVEL这个等级,这个等级其实就对应于上表中的某个log等级,具体是啥可以读代码看。注意,这些标志后不加逗号。用法是printk(KERN_DEBUG “hello,world”),printk不支持浮点打印,内核态不随便用浮点单元。

log

复杂的内核log机制

在内核用有两个内核服务守护程序,一个是klogd,负责监听来自内核的各种log数据,另一个是syslogd,负责监听来自各种进程的log数据,包括klogd的。syslog自1980就开始开发, 其定义了log协议。在syslog的基础上又衍生出了syslog-ng和rsyslog。三个项目现在是并行状态,CentOS8默认装的是rsyslog。事实上,很多发行版的klogd和syslogd都合并了,对于其中的细节不需要太深究。下图是内核各种日志打印的关系图示,dmesg打印的是/proc/kmsg的内容,cat该文件可以看到内核的实时日志输出。

控制台log

控制台也可以作为内核日志的输出口,但是它有个console_loglevel变量来控制哪些内容能输出。只有内核log等级高于(数值小于)console_loglevel的printk才会显示。该变量通过/proc/sys/kernel/printk来修改。

1
2
3
4
5
6
7
8
9
10
cat /proc/sys/kernel/printk # 输出4个数字对应下面的代码

int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */
DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */
MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */
DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */
};

echo 8 > /proc/sys/kernel/printk # 可以只写一个参数,默认会修改 DEFAULT_CONSOLE_LOGLEVEL,也就是console_loglevel

对STDIN_FILENO文件描述符执行TIOCLINUX 参数的ioctl以修改默认接受printk打印的终端。TIOCLINUX 仍然是复合命令,还需要指定参数选定子功能

1
2
3
4
5
6
char bytes[2] = {11,0}; /* 11 is the TIOCLINUX cmd number */
if (ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0) { /* use stdin */
fprintf(stderr,"%s: ioctl(stdin, TIOCLINUX): %s\n",
argv[0], strerror(errno));
exit(1);
}

printk辅助函数

printk不停的刷可能导致缓冲区溢出,内核提供了一个函数来控制打印速率

1
2
if (printk_ratelimit() != 0)
printk(KERN_NOTICE "返回非0值表示可以打印\n")

打印设备号常用,但写起来麻烦。内核提供下面两个函数将设备号转码到字符串buffer中

1
2
3
4
#include <linux/kdev_t.h> // 原型在这里

int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);

其他调试方法

/proc

可以将内核中的一些运行信息导出到/proc文件系统中,注意不要滥用这个导出功能,必要时考虑导出到/sysfs。该接口以单次一个Page的粒度,将内核中的数据传输到用户态。

seq_file

同/proc类似的一组接口,也是将内核数据导出到/proc中,不过对大块输出输出更友好。

ioctl

就是驱动的自定义命令,也可以用来作为响应式调试。相对于/proc导出的好处是,可以将调试接口对用户隐藏。

strace

strace可以记录用户态程序的每一个系统调用,可以打印出系统调用的入参和返回值。如果用来调试驱动程序,可以借助用户态程序。让用户态程序对驱动进行操作,观察驱动的返回值等。

SysRq魔法键

对于某些系统假死的情况(看起来卡死了,其实调度器还能调度),可以通过SysRq魔法键来调试,例如打印当前寄存器信息、保存磁盘数据等。开启该功能可能需要重编内核,然后代开sysfs中的开关。SysRq魔法键也提供其他功能,具体可看内核文档

利用性能调优工具

如果SysRq处理假死问题行不通,还可以用性能调优工具。本书描述的调优工具已经废弃了,但是思路是不变的。可以通过调优工具来分析内核假死的时候将时间消耗在了哪个位置。

GDB调试

除了利用Qemu的GDB调试以外,还有一种内核自己用GDB调试自己的技术。自己调试自己的时候更像是只读,因为不能加断点、不能改数据、不能单步调试,但是比用Qemu调试要方便快捷。

1
2
3
CONFIG_PROC_KCORE             // 使能/proc/kcore必须开启的编译选项
gdb vmlnux /proc/kcore // 启动调试,GDB会告知程序没有运行,但是没关系,已经可以查看变量了。
core-file /proc/kcore // 为了提升效率,gdb会缓存kcore内容,有时候重复打印一个变量的值不会变化。在gdb中用过该命令刷新缓存