使用 GDB 调试 Linux Kernel 需要以下几个步骤:

  1. 编译一个可以调试的内核

  2. QEMU 启动内核

  3. GDB 连接 QEMU

编译一个可以调试的内核

在前文 Linux Kernel 从编译到运行 中我们编译了一个内核,这个内核是没有调试信息的,我们需要重新编译一个带调试信息的内核。

首先使用 make mrproper 清理内核源码,然后使用 make menuconfig 配置内核,

Kernel hacking > Compile-time checks and compiler options > Debug infomation > 选择 Rely on the toolchain's implicit default DWARF version

和 debug 有关的内核配置项还包括:

  • CONFIG_GDB_SCRIPTS
  • CONFIG_DEBUG_INFO
  • CONFIG_KGDB
  • CONFIG_KGDB_SERIAL_CONSOLE
  • CONFIG_KGDB_TESTS

它们大部分是默认开启的,我们只需要确认一下。

然后使用 make -j8 编译内核,这个过程比较慢,需要等待一段时间。

QEMU 启动内核

在前文 Linux Kernel 从编译到运行 中我们使用 QEMU 启动内核,这里我们需要修改一下启动命令,增加 -s -S 参数。

最好使用内核启动参数 nokaslr 来禁用内核地址空间随机化。

修改后的 Makefile 如下:

.PHONY: initramfs run clean

initramfs:
	cd initramfs && find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.img

run:
	qemu-system-x86_64 \
	-kernel bzImage \
	-initrd initramfs.img \
	-nographic \
	-append "console=ttyS0 nokaslr"\
	-m 1024 \
	-smp 1 \
	-S \
	-s	

clean:
	rm -f initramfs.cpio initramfs.img

其中,-S 选项表示在启动时暂停 CPU,-s 选项表示在 1234 端口监听 GDB 连接。

-s 也可以使用 -gdb tcp::1234 代替。

把编译好的 bzImage 和 vmlinux 拷贝放到 QEMU 启动目录下

cp arch/x86/boot/bzImage ../workspace/
cp vmlinux ../workspace/

我的 QEMU 启动目录是 ../workspace/,你可以根据实际情况修改。

然后执行 make run 启动 QEMU。

GDB 连接 QEMU

使用 -S 选项启动 QEMU 后,QEMU 会暂停 CPU,等待 GDB 连接。

然后在另一个终端中使用 GDB 连接 QEMU

gdb vmlinux

然后在 GDB 中输入 target remote :1234 连接 QEMU

这时候就可以使用 GDB 调试内核了。

这里我们首先在 start_kernel 函数上打一个断点。

(gdb) b start_kernel
Breakpoint 1 at 0xffffffff838d9e90: file init/main.c, line 904.

然后输入 c 继续执行,QEMU 会继续执行内核启动过程,直到遇到断点。

(gdb) c
Continuing.

Breakpoint 1, start_kernel () at init/main.c:904
904	{

这样就可以在内核启动的过程中使用 GDB 进行调试了。

Tips

当我们在真实的调试过程中,会反复执行某些命令,例如 target remote :1234,设置断点,查看变量等等。

对于这些命令,我们可以将它们写入 .gdbinit 文件中,这样每次启动 GDB 时会自动执行这些命令。

如果想使用当前目录下的 .gdbinit 文件,需要在 home 目录下的 gdbinit 文件中添加 set auto-load safe-path /,否则 GDB 会忽略当前目录下的 .gdbinit 文件。

例如:

mkdir -p ~/.config/gdb
echo "set auto-load safe-path /" > ~/.config/gdb/gdbinit

echo "target remote :1234" > .gdbinit
echo "b start_kernel" >> .gdbinit
echo "c" >> .gdbinit

# 启动内核
make run

# 启动 GDB
gdb vmlinux

此时,GDB 会自动连接 QEMU,设置断点,并继续执行至 start_kernel 函数。

The End…