KVM-Unit-Tests,如该项目主页所介绍的,这是一套伴随着 KVM 一起出现的单元测试集,意在为 KVM 提供测试用例。每个单元测试就是一个几十行代码写的功能单一的小型操作系统,它作为 guest 被 QEMU 唤起执行,以验证特定虚拟化功能是否在 KVM 上工作正常。
- 项目主页:www.linux-kvm.org/page/KVM-un… ,
- 代码仓库:gitlab.com/kvm-unit-te…
本人在针对 x86 CPU 的虚拟化 feature 编写单元测试时,遇到了测例报错的情况,而调试 kvm-unit-test 的资料较少,所以写下本文记录一下。如有不正确的地方,欢迎大家指正。
编译和运行单元测试
本文用到的是 UEFI 方式运行单元测试。
# 编译
./configure --enable-efi
make
# 执行单个测试用例,如: msr
EFI_UEFI=/usr/share/ovmf/OVMF.fd ./x86/efi/run ./x86/msr.efi
# 执行所有测试用例
./run_tests.sh
当执行单个测试出错时,错误日志会直接打印在命令行下面。
当执行的是所有测试用例,可以在 ./logs/
找到不同单元测试的日志。
如果不能直接通过日志找到错误原因,常用的方法是用 GDB 调试程序。
GDB 调试过程
本文所写的调试方法参考了 Rob 针对 ARM 架构的单元测试调试的文章[1],不过年代比较久远了(March 12, 2020),有些步骤并不适用当前最新的 kvm-unit-tests。
开启 QEMU 调试端口
在启动测试的命令行中添加 -s -S -monitor pty
, 即 x86/efi/run x86/msr.efi -s -S -monitor pty
-s
,使用 QEMU 的 TCP 端口:1234
用于调试-S
,将测试停止在启动阶段,等待调试器连接和控制-monitor pty
重定向 QEMU monitor
如下所示,测试停在了启动阶段,并输出了详细的启动命令。
[root@pc kvm-unit-tests]# x86/efi/run x86/msr.efi -s -S -monitor pty
/home/temp/bin/qemu-system-x86_64 --no-reboot -nodefaults -device pc-testdev -device isa-debug-exit,iobase=0xf4,iosize=0x4 -vnc none -serial stdio -device pci-testdev -machine accel=kvm -drive file=/usr/share/qemu/OVMF.fd,format=raw,if=pflash,readonly=on -drive file.dir=efi-tests/msr/,file.driver=vvfat,file.rw=on,format=raw,if=virtio -net none -nographic -m 256 -s -S -monitor pty -smp 1
char device redirected to /dev/pts/4 (label compat_monitor0)
根据输出信息, QEMU monitor 已被重定向到字符设备 /dev/pts/4
,打开 monitor 的方法:minicom -D /dev/pts/4
。
使用 GDB 连接测试程序
gdb
启动调试器target remote :1234
连接 QEMUx86/msr.efi
中加载符号[root@pc kvm-unit-tests]# gdb
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-20.el8
Copyright (C) 2018 Free Software Foundation, Inc.
...
(gdb) target remote :1234
Remote debugging using :1234
...
(gdb) file x86/msr.efi
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from x86/msr.efi...(no debugging symbols found)...done.
此处 gdb 提示添加的 efi 文件中并不包含调试符号,这将导致后续的 debug 没法打断点和查看执行的代码。
我们可以把 x86/msr.so
添加进 gdb 中用于调试,不过要找到单元测试执行时,代码段起始位置的虚拟地址,将其设为库文件的加载点: add-symbol-file x86/msr.so
。寻找库文件加载点的方法可参考下一节的内容。
(gdb) add-symbol-file x86/msr.so 0x000000000e66a000
add symbol table from file "x86/msr.so" at
.text_addr = 0xe66a000
(y or n) y
...
Reading symbols from /home/temp/kvm-unit-tests/x86/msr.efi.debug...done.
done.
检查是否可以找到源码中的函数,如 _start
。
(gdb) l _start
39 .text
40 .align 4
41
42 .globl _start
43 _start:
44 subq $8, %rsp
45 pushq %rcx
46 pushq %rdx
47
48 0:
接下来就可以正常使用 gdb 调试 kvm-unit-tests 源码了。如:
# 打断点
hb _start
# 查看堆栈
bt
# 查看寄存器 EAX
i registers eax
p $eax
寻找库文件加载点
monitor 查看 RIP
主要思路是,在测试程序开始的地方加入死循环,用 QEMU monitor 查看 RIP
,获得代码运行起始地址。
修改代码如下,并重新编译执行。
diff --git a/x86/efi/crt0-efi-x86_64.S b/x86/efi/crt0-efi-x86_64.S
index 1708ed5..849850c 100644
--- a/x86/efi/crt0-efi-x86_64.S
+++ b/x86/efi/crt0-efi-x86_64.S
@@ -41,6 +41,7 @@
.globl _start
_start:
+ jmp .
subq $8, %rsp
pushq %rcx
pushq %rdx
在 QEMU monitor 中输入 c
继续测试运行,以陷入死循环。查看寄存器,RIP 为 000000000e66ab68
。
(qemu) QEMU 8.0.90 monitor - type 'help' for more information
(qemu) c
(qemu) info registers
CPU#0
RAX=000000000e99e898 RBX=000000000e99e070 RCX=000000000e99eb18 RDX=000000000f9ec018
RSI=000000000e99e898 RDI=000000000e99e898 RBP=000000000fea5890 RSP=000000000fea5818
R8 =00000000000000af R9 =000000000fec0578 R10=000000000febda50 R11=0000000000000002
R12=0000000000000000 R13=000000000efada80 R14=000000000efac798 R15=0000000000000000
RIP=000000000e66ab68 RFL=00000246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
...
计算代码段起始地址
GDB 中使用 i files
查看可执行文件的内存分布, 对于当前 msr 单元测试,代码段(.text)的起始偏移是 0x0000000000003000
, 入口点偏移为 0x3b68
。
(gdb) file x86/msr.efi
(gdb) i files
Symbols from "/home/temp/kvm-unit-tests/x86/msr.efi".
Remote serial target in gdb-specific protocol:
Debugging a target over a serial line.
While running this, GDB does not access memory from...
Local exec file:
`/home/temp/kvm-unit-tests/x86/msr.efi', file type pei-x86-64.
Entry point: 0x3b68
0x0000000000003000 - 0x000000000000ba30 is .text
0x000000000000c000 - 0x000000000000c00c is .reloc
0x000000000000d000 - 0x0000000000127aa8 is .data
0x0000000000128000 - 0x0000000000128100 is .dynamic
0x0000000000129000 - 0x0000000000129750 is .rela
0x000000000012a000 - 0x000000000012b4e8 is .dynsym
- 入口点 (
Entry point: 0x3b68
) 对应的虚拟地址为000000000e66ab68
。则, - 代码段起始偏移
0x0000000000003000
对应的虚拟地址为000000000e66a000
。
最后,将修改后的代码恢复。
Reference
- Testing QEMU emulation: how to debug kvm-unit-tests - ARM-Datacenter (futurewei-cloud.github.io)