使用QEMU GDB调试Linux内核
调试Linux内核从来都不是一件容易事。如果在开发过程中发现内核无法启动,甚至连日志输出都没有,问题就会变得很棘手。如果是开发板,可能还可以借助JTAG,但如果没有JTAG,要如何进行调试呢?本文给出另外一种可能:使用QEMU来进行内核调试。
准备工作
要使用QEMU GDB调试Linux内核,首先我们需要让内核能够在QEMU中启动。对于X86架构,这通常不困难,但是对于ARM架构,尤其是ARM32架构,可能需要借助uboot才能启动内核,具体可以参考在QEMU中启动U-Boot和内核。
其次我们还需要GDB运行环境,可以使用GDB命令,也可以使用gdbgui这样的图形化工具。
最后,我们还需要让内核保留调试信息,可以在menuconfig
做如下配置
1 | Symbol: DEBUG_KERNEL [=y] |
1 | Symbol: 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 | (gdb) file vmlinux |
然后我们设置断点,以start_kernel
函数为例
1 | (gdb) break start_kernel |
最后,我们通过GDB控制QEMU继续执行
1 | (gdb) continue |
我们将看到QEMU继续执行,并在start_kernel
处停下来,等待GDB的命令。之后的调试方式与常规用户态的调试大同小异。