跳到主要内容

第 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-armqemu-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 小白了——比如知道 breakrunnextcontinue 是干什么的。如果你现在脑子里只有 printf 调试法,建议先去搜一个「GDB cheat sheet」快速扫一眼。

但别担心,内核调试和用户态调试还是有点区别的,那些真正酷炫(且危险)的操作,我们会一步一步带着你做。

场地清理完毕。接下来,我们要把调试器这个外挂,正式植入到内核里去了。