在真实机器上调试内核风险高、门槛高。使用 QEMU 模拟一台 x86_64 虚拟机,配合 GDB 远程调试,可以在普通开发机上完成从内核启动到函数级别的调试。

本文基于 Ubuntu 22.04(WSL 也可),内核版本 5.15.78,QEMU 5.1.0。

一、安装 QEMU

1.1 源码编译安装(可选)

wget https://download.qemu.org/qemu-5.1.0.tar.xz
tar -xf qemu-5.1.0.tar.xz && cd qemu-5.1.0

sudo apt-get install libglib2.0-dev   # glib
sudo apt-get install libpixman-1-dev  # pixman

# 只编译 x86_64 系统模拟器,减少编译时间
./configure --target-list=x86_64-softmmu
make -j && sudo make install

1.2 apt 直接安装(推荐)

sudo apt install qemu-system-x86

二、编译带调试信息的内核

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.15.78.tar.gz
tar -xvf linux-5.15.78.tar.gz
cd linux-5.15.78

export ARCH=x86
make x86_64_defconfig

make menuconfig

menuconfig 中需要修改两处:

  1. 关闭 KASLR:Processor type and features → 取消 Randomize the address of the kernel image (KASLR)。地址随机化会干扰断点。
  2. 开启调试信息:Kernel hacking → Compile-time checks and compiler options → 勾选:
    • Compile the Kernel with debug info
    • Provide GDB scripts for kernel debugging

如果找不到这些选项,通常是目标内核版本与宿主机差距过大,或没有匹配的调试配置。

编译内核:

make -j 20

最终产物为 arch/x86_64/boot/bzImage。

踩坑:canonical-certs.pem 编译报错

Ubuntu 22.04 编译内核可能报错:

No rule to make target 'debian/canonical-certs.pem', needed by 'certs/x509_certificate_list'.

执行以下两条命令后重新编译:

scripts/config --disable SYSTEM_TRUSTED_KEYS
scripts/config --disable SYSTEM_REVOCATION_KEYS

三、busybox 制作最小 rootfs

内核启动后需要根文件系统承载 init 进程和 shell 命令。

3.1 编译静态 busybox

wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -jxvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1

make menuconfig

在 Settings 中勾选:

[*] Build static binary (no shared libs)

保存后编译安装:

make
make install

3.2 制作 rootfs 镜像

dd if=/dev/zero of=rootfs.img bs=1M count=10
mkfs.ext4 rootfs.img

mkdir fs
sudo mount -t ext4 -o loop rootfs.img ./fs
sudo make install CONFIG_PREFIX=./fs

# 创建必要目录并拷贝 busybox 示例配置
cd fs/
sudo mkdir proc dev etc home mnt
sudo cp -r ../examples/bootfloppy/etc/* etc/
cd ..
sudo chmod -R 777 fs/
sudo umount fs

四、QEMU 启动与 GDB 调试

4.1 先测试能否正常启动

qemu-system-x86_64 \
  -kernel ./linux-5.15.78/arch/x86/boot/bzImage \
  -hda ./busybox-1.36.1/rootfs.img \
  -append "root=/dev/sda console=ttyS0" \
  -nographic

4.2 以调试模式启动

qemu-system-x86_64 \
  -smp 4 \
  -m 2048M \
  -kernel ../linux-5.15.78/arch/x86/boot/bzImage \
  -drive format=raw,file=../busybox-1.36.1/rootfs.img \
  -net nic -net tap,ifname=tap0,script=no,downscript=no \
  -append "root=/dev/sda nokaslr console=ttyS0" \
  -nographic \
  -S \
  -s

参数说明:

  • -S:启动后暂停 CPU,等待调试器接入。
  • -s:打开 GDB 远程调试端口 1234(等价于 -gdb tcp::1234)。
  • nokaslr:与编译阶段关闭 KASLR 保持一致,保证断点地址稳定。

4.3 GDB 远程连接

另开一个终端:

cd linux-5.15.78
gdb ./vmlinux
target remote :1234
b start_kernel
c

程序会在 start_kernel 处停下,之后可以单步、查看变量和调用栈。

五、虚拟机和宿主机网络互通

在宿主机创建 tap 网卡:

sudo ip tuntap add dev tap0 mode tap
sudo ip link set dev tap0 up
sudo ip address add dev tap0 192.168.2.128/24

QEMU 启动参数中已带 -net tap,ifname=tap0,虚拟机内配置 IP:

ip addr add 192.168.2.129/24 dev eth0
ip link set eth0 up

互相 ping 验证:

# 虚拟机内 ping 宿主机
ping 192.168.2.128

# 宿主机 ping 虚拟机
ping 192.168.2.129

六、VS Code 界面化调试

安装 C/C++ 插件后,使用 launch.json 配置远程 gdb:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "kernel-debug",
      "type": "cppdbg",
      "request": "launch",
      "miDebuggerServerAddress": "127.0.0.1:1234",
      "program": "${workspaceFolder}/vmlinux",
      "stopAtEntry": false,
      "cwd": "${workspaceFolder}",
      "MIMode": "gdb"
    }
  ]
}

先用 QEMU 启动内核,再在 VS Code 中按 F5,即可在源码界面打断点调试。

七、扩展:用 ubuntu-base 制作完整 rootfs

busybox 适合调试内核本身;如果需要完整用户环境,可以用 ubuntu-base 通过 chroot 制作:

dd if=/dev/zero of=rootfs.img bs=2048 count=1M
mkfs.ext4 -F -L linuxroot rootfs.img

mkdir fs
mount -t ext4 -o loop rootfs.img ./fs

tar -xzvf ubuntu-base-23.10-base-amd64.tar.gz -C ./fs
cp /etc/resolv.conf ./fs/etc

# 挂载系统目录,chroot 内才能使用 apt
mount -t proc /proc ./fs/proc
mount -t sysfs /sys ./fs/sys
mount -o bind /dev ./fs/dev
mount -o bind /dev/pts ./fs/dev/pts

chroot ./fs

chroot 后可以安装软件、设置 hostname 和 root 密码,完成后卸载:

umount ./fs/proc
umount ./fs/sys
umount ./fs/dev/pts
umount ./fs/dev

八、总结

  • QEMU -S -s 是内核调试的核心组合:暂停等待调试器 + 开放 1234 端口。
  • 内核必须关闭 KASLR 并开启 debug info,断点才稳定可靠。
  • busybox 静态编译 + ext4 镜像是最快的内核调试 rootfs 方案。
  • canonical-certs.pem 报错通过关闭 SYSTEM_TRUSTED/REVOCATION_KEYS 解决。
  • tap 网卡让虚拟机与宿主机互通,便于后续网络相关调试。
  • VS Code cppdbg 可以把命令行调试升级为界面化调试。

相关脚本

整理自 cyy-tag/bash-example 仓库: