使用QEMU调试Linux内核
本文是一个快速记录,旨在记录使用 QEMU 调试 Linux 内核的方法,文章内容比较短,内容也比较浅显,不会对具体原理进行分析,主打一个够用。使用的 QEMU 版本为 6.2,Linux 内核版本为 5.10,指令集架构为 x86_64。
使用 monitor
使用 monitor 的前提是要先启用该功能,一般将其设置在指定的 telnet 端口(如 6789):
-monitor telnet:localhost:6789,server,nowait,nodelay
使用 monitor 调试的基本场景是内核 crash 时,打开 QEMU 的 monitor,打印此时的寄存器值。为了保证 RIP 的值准确,需要在 QEMU 模拟器的内核命令行参数中关闭内核地址空间随机化:
-append "nokaslr"
打开新的终端使用 telnet 连接到指定端口:
$ telnet localhost 6789
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
QEMU 6.2.0 monitor - type 'help' for more information
(qemu) info registers
RAX=0000000000000839 RBX=00000000000a328c RCX=0000000000000000 RDX=0000000000001000
RSI=0000000000000086 RDI=0000000000001001 RBP=ffffffff82403f40 RSP=ffffffff82403ec8
R8 =ffffffff82450f20 R9 =2d2d2d5d2063696e R10=20796c6c61756e61 R11=6572656767697274
R12=0000000000000000 R13=00000000000a32f0 R14=0000000000000047 R15=0000000000000000
RIP=ffffffff8142b090 RFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 00000000 00000000
CS =0010 0000000000000000 ffffffff 00af9b00 DPL=0 CS64 [-RA]
SS =0000 0000000000000000 ffffffff 00c09300 DPL=0 DS [-WA]
DS =0000 0000000000000000 00000000 00000000
FS =0000 0000000000000000 00000000 00000000
GS =0000 ffffffff82b01000 00000000 00000000
LDT=0000 0000000000000000 00000000 00008200 DPL=0 LDT
TR =0020 0000000000000000 00000fff 00808900 DPL=0 TSS64-avl
GDT= ffffffff82b0c000 0000007f
IDT= ffffffff82c38000 00000fff
CR0=80050033 CR2=ffff888000013750 CR3=0000000002b82000 CR4=000000a0
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
EFER=0000000000000d01
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000
XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000
XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000
XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000
XMM08=0000000000000000 0000000000000000 XMM09=0000000000000000 0000000000000000
XMM10=0000000000000000 0000000000000000 XMM11=0000000000000000 0000000000000000
XMM12=0000000000000000 0000000000000000 XMM13=0000000000000000 0000000000000000
XMM14=0000000000000000 0000000000000000 XMM15=0000000000000000 0000000000000000
可以看到,此时的 pc(x86_64 架构下为 RIP 寄存器) 停在 0xffffffff8142b090 的位置,此时我们可以对内核进行反汇编并查看此时的代码上下文,注意,需要对 vmlinux 进行 dump 而不是对 bzImage:
objdump -d vmlinux > kernel.asm
然后搜索 RIP 值所在的指令位置:
ffffffff8142b090 <delay_loop>:
ffffffff8142b090: 48 89 f8 mov %rdi,%rax
ffffffff8142b093: 48 85 c0 test %rax,%rax
ffffffff8142b096: 74 1d je ffffffff8142b0b5 <delay_loop+0x25>
ffffffff8142b098: eb 06 jmp ffffffff8142b0a0 <delay_loop+0x10>
ffffffff8142b09a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
ffffffff8142b0a0: eb 0e jmp ffffffff8142b0b0 <delay_loop+0x20>
ffffffff8142b0a2: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1)
ffffffff8142b0a9: 00 00 00 00
ffffffff8142b0ad: 0f 1f 00 nopl (%rax)
ffffffff8142b0b0: 48 ff c8 dec %rax
ffffffff8142b0b3: 75 fb jne ffffffff8142b0b0 <delay_loop+0x20>
ffffffff8142b0b5: 48 ff c8 dec %rax
ffffffff8142b0b8: c3 ret
ffffffff8142b0b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
使用 GDB
QEMU 实现了 GDB-stub,因此能够支持使用 GDB 通过远程连接的方式调试模拟器内运行的内核。
为了方便调试的进行,需要在编译内核时开启调试信息:
$ make menuconfig
# 确保设置:Kernel hacking
# -> Compile-time checks and compiler options
# -> # [*] Compile the kernel with debug info
在启动 QEMU 时,参数加上 -s -S,这样模拟器将会在执行时停止在第一条指令处,并开放默认端口 1234 等待 GDB client 连接。
同样,打开一个新的终端,使用 gdb vmlinux 启动 GDB 并加载内核的符号,并使用 target remote 连接到指定的 GDB 服务端,此后便可以像正常 GDB 一样进行调试了。
$ gdb vmlinux
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from vmlinux...
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x000000000000fff0 in exception_stacks ()
(gdb) b start_kernel
Breakpoint 1 at 0xffffffff82b2c921: file init/main.c, line 853.
(gdb) c
Continuing.
Breakpoint 1, start_kernel () at init/main.c:853
853 set_task_stack_end_magic(&init_task);
(gdb)
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Lordaeron_ESZ's blog!
评论













