4.1 Linux 内核调试

准备工作

与用户态程序不同,为了进行内核调试,我们需要两台机器,一台调试,另一台被调试。在调试机上需要安装必要的调试器(如GDB),被调试机上运行着被调试的内核。

这里选择用 Ubuntu16.04 来展示,因为该发行版默认已经开启了内核调试支持:

$ cat /boot/config-4.13.0-38-generic | grep GDB
# CONFIG_CFG80211_INTERNAL_REGDB is not set
CONFIG_SERIAL_KGDB_NMI=y
CONFIG_GDB_SCRIPTS=y
CONFIG_HAVE_ARCH_KGDB=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
# CONFIG_KGDB_TESTS is not set
CONFIG_KGDB_LOW_LEVEL_TRAP=y
CONFIG_KGDB_KDB=y

获取符号文件

下面我们来准备调试需要的符号文件。看一下该版本的 code name:

然后在下面的目录下新建文件 ddebs.list,其内容如下(注意看情况修改Codename):

http://ddebs.ubuntu.com 是 Ubuntu 的符号服务器。执行下面的命令添加密钥:

然后就可以更新并下载符号文件了:

完成后,符号文件将会放在下面的目录下:

可以看到这是一个静态链接的可执行文件,使用 gdb 即可进行调试,例如这样:

获取源文件

/etc/apt/sources.list 里的 deb-src 行都取消掉注释:

然后就可以更新并获取 Linux 内核源文件了:

printk

在用户态程序中,我们常常使用 printf() 来打印信息,方便调试,在内核中也可以这样做。内核(v4.16.3)使用函数 printk() 来输出信息,在 include/linux/kern_levels.h 中定义了 8 个级别:

用法是:

而当前控制台的日志级别如下所示:

这 4 个数值在文件定义及默认值在如下所示:

虽然这些数值控制了当前控制台的日志级别,但使用虚拟文件 /proc/kmsg 或者命令 dmesg 总是可以查看所有的信息。

QEMU + gdb

QEMU 是一款开源的虚拟机软件,可以使用它模拟出一个完整的操作系统(参考章节2.1.1)。这里我们介绍怎样使用 QEMU 和 gdb 进行内核调试,关于 Linux 内核的编译可以参考章节 1.5.9。

接下来我们需要借助 BusyBox 来创建用户空间:

生成默认配置文件并修改 CONFIG_STATIC=y 让它生成的是一个静态链接的 BusyBox,这是因为 qemu 中没有动态链接库:

编译安装后会出现在 _install 目录下:

接下来创建 initramfs 的目录结构:

最后把它们打包:

这样 initramfs 根文件系统就做好了,其中包含了必要的设备驱动和工具,boot loader 会加载 initramfs 到内存,然后内核将其挂载到根目录 /,并运行 init 脚本,挂载真正的磁盘根文件系统。

QEMU 启动!

  • -s-gdb tcp::1234 的缩写,QEMU 监听在 TCP 端口 1234,等待 gdb 的连接。

  • -S:在启动时冻结 CPU,等待 gdb 输入 c 时继续执行。

  • -kernel:指定内核。

  • -initrd:指定 initramfs。

  • nographic:禁用图形输出并将串行 I/O 重定向到控制台。

  • -append "console=ttyS0:所有内核输出到 ttyS0 串行控制台,并打印到终端。

在另一个终端里使用打开 gdb,然后尝试在函数 cmdline_proc_show() 处下断点:

可以看到,当我们在内核里执行 cat /proc/cmdline 时就被断下来了。

现在我们已经可以对内核代码进行单步调试了。对于内核模块,我们同样可以进行调试,但模块是动态加载的,gdb 不会知道这些模块被加载到哪里,所以需要使用 add-symbol-file 命令来告诉它。

来看一个 helloworld 的例子,源码

Makefile 如下:

编译模块并将 .ko 文件复制到 initramfs,然后重新打包:

最后重新启动 QEMU 即可:

三个命令分别用于载入、列出和卸载模块。

再回到 gdb 中,add-symbol-file 添加模块的 .text.data.bss 段的地址,这些地址在类似 /sys/kernel/<module>/sections 位置:

在这个例子中,只有 .text 段:

然后就可以对该模块进行调试了。

kdb

参考资料

Last updated

Was this helpful?