使用QEMU GDB调试Linux内核

调试Linux内核从来都不是一件容易事。如果在开发过程中发现内核无法启动,甚至连日志输出都没有,问题就会变得很棘手。如果是开发板,可能还可以借助JTAG,但如果没有JTAG,要如何进行调试呢?本文给出另外一种可能:使用QEMU来进行内核调试。

准备工作

要使用QEMU GDB调试Linux内核,首先我们需要让内核能够在QEMU中启动。对于X86架构,这通常不困难,但是对于ARM架构,尤其是ARM32架构,可能需要借助uboot才能启动内核,具体可以参考在QEMU中启动U-Boot和内核

其次我们还需要GDB运行环境,可以使用GDB命令,也可以使用gdbgui这样的图形化工具。

最后,我们还需要让内核保留调试信息,可以在menuconfig做如下配置

1
2
3
4
5
6
7
Symbol: DEBUG_KERNEL [=y]
Type : bool
Defined at lib/Kconfig.debug:211
Prompt: Kernel debugging
Location:
-> Kernel hacking
-> Kernel debugging (DEBUG_KERNEL [=y])
1
2
3
4
5
6
7
8
9
10
11
Symbol: DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT [=y]
Type : bool
Defined at lib/Kconfig.debug:258
Prompt: Rely on the toolchain's implicit default DWARF version
Depends on: <choice> && (!CC_IS_CLANG [=n] || AS_IS_LLVM [=n] || CLANG_VERSION [=0]<140000 || AS_IS_GNU [=y] && \
AS_VERSION [=23502]>=23502 && AS_HAS_NON_CONST_LEB128 [=y])
Location:
-> Kernel hacking
-> Compile-time checks and compiler options
-> Debug information (<choice> [=y])
-> Rely on the toolchain's implicit default DWARF version (DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT [=y])

启动GDB Server

假设我们基于X86架构调试,那么编译出的内核镜像通常是bzImage,我们可以尝试使用如下命令启动内核,因为并没有指定根文件系统,所以内核会在挂载root目录时panic。

1
qemu-system-x86_64 -nographic -kernel arch/x86_64/boot/bzImage -append 'console=ttyS0'

要启动QEMU内置的GDB Server,我们需要使用2个额外的参数

  • -s: Do not start CPU at startup
  • -S: Open a gdbserver on TCP port 1234

第一个参数的目的是让我们能够在CPU启动之前连接GDB Server,而第二个参数则是启动GDB Server。

此外,我们还需要添加一个内核cmdline参数nokaslr来关闭内核地址随机化,否则我们打断点的地址与实际执行的地址将不一致,从而导致断点失效。

于是,最终的命令为

1
qemu-system-x86_64 -nographic -s -S -kernel arch/x86_64/boot/bzImage -append 'console=ttyS0 nokaslr'

执行命令后,我们会发现QEMU没有任何输出,因为此时虚拟机的CPU还未启动,接下来,我们将在GDB中完成后续工作。

GDB调试

在内核源码根目录执行gdb命令进入GDB shell。

首先连接到QEMU的GDB Server,默认端口是1234

1
2
3
(gdb) file vmlinux
(gdb) add-auto-load-safe-path $(pwd)
(gdb) target remote localhost:1234

然后我们设置断点,以start_kernel函数为例

1
(gdb) break start_kernel

最后,我们通过GDB控制QEMU继续执行

1
(gdb) continue

我们将看到QEMU继续执行,并在start_kernel处停下来,等待GDB的命令。之后的调试方式与常规用户态的调试大同小异。

参考

How to debug the Linux kernel with GDB and QEMU?