第 11 章 深入内核:当调试器成为内核的一部分
有一类问题,普通调试器是碰不到的。
当你的代码在 Ring 0 运行,当 printk 的输出缓冲区因为系统崩溃而无法刷新,当你要调试的是一个中断处理程序——这时候,普通的用户态调试工具不仅软弱无力,甚至根本不存在。
本章的任务,就是构建一种更底层的调试能力。我们将不再从外部观察进程,而是让调试器本身成为内核的一部分。
这不是「学会使用一个工具」那么简单。理解 KGDB,意味着你开始理解操作系统的上帝视角——能够暂停整个世界,查看它的状态,然后让时间继续流动。
11.1 技术准备
在深入内核之前,我们需要先把场地清理干净。
本章的基础工作空间和工具链依然沿袭我们在第 1 章建立的规范——如果你还没搭建那个环境,现在是个回头补课的好时机。所有的代码示例都已经躺在书里的 GitHub 仓库上了,随用随取。
但这一次,我们要玩的东西有点大。除了常规的武器,我们需要安装更重型的模拟器组件,以及一个准备好的根文件系统镜像。
这一步虽然繁琐,但别偷懒。地基如果不打牢,后面内核一崩,你连问题出在哪个环上都找不到。
1. 安装 QEMU 和依赖库
我们需要 QEMU 的 ARM 和 x86 版本,这不仅仅是跑两个虚拟机那么简单——本章我们将大量依赖 QEMU 的调试特性。
打开终端,执行以下命令。这一连串包下来大概要吃掉你 400 MB 的磁盘空间:
sudo apt install qemu-system-arm qemu-system-x86 lzop \
libncursesw5 libncursesw5-dev p7zip-full
这里解释一下几个关键角色:
qemu-system-arm和qemu-system-x86:这是我们的「沙盒」。KGDB 调试通常需要两台机器,或者用虚拟机充当目标机。QEMU 完美扮演了这个倒霉的目标机角色,而且它对 GDB 的支持做得极好。lzop:一个压缩工具,内核世界里有时候你会遇到.lzo格式的东西。libncursesw5:如果你要配置内核(make menuconfig),没有这个库你的菜单界面弹不出来。p7zip-full:一会儿解压根文件系统要用,别问为什么不用unzip,有些事情就是这么任性。
2. 获取根文件系统镜像
接下来,我们需要一个现成的 ARM Linux 根文件系统。
这个文件后面我们调试内核模块时会用到。请导航到本书源码的 ch11/ 目录,然后下载这个大块头(大约 178 MB,喝杯咖啡慢慢等):
cd <book_src>/ch11
wget https://github.com/PacktPublishing/Linux-Kernel-Debugging/raw/main/ch11/rootfs_deb.img.7z
⚠️ 注意——这里有个小坑
如果你直接运行上面的 wget,GitHub 可能会耍点小脾气。由于仓库里已经存在一个占位用的 rootfs_deb.img.7z(meta 文件),wget 为了不覆盖它,会把你下载下来的真实文件重命名为 rootfs_deb.img.7z.1。
这名字不对,后续脚本会找不到它。你需要手动修正一下这个「笔误」:
rm rootfs_deb.img.7z
mv rootfs_deb.img.7z.1 rootfs_deb.img.7z
现在的目录结构应该干净利落了(如果你好奇,可以翻到后面的图 11.8 看看最终长什么样,但现在我们先把文件放在这,后面用到时再解压)。
3. 关于 GDB 的前置知识
最后一件事:我们在这一章会大量使用 GDB。
我会假设你已经不是一个 GDB 小白了——比如知道 break、run、next、continue 是干什么的。如果你现在脑子里只有 printf 调试法,建议先去搜一个「GDB cheat sheet」快速扫一眼。
但别担心,内核调试和用户态调试还是有点区别的,那些真正酷炫(且危险)的操作,我们会一步一步带着你做。
场地清理完毕。接下来,我们要把调试器这个外挂,正式植入到内核里去了。