调试 KVMUnitTests

2023年 10月 15日 110.3k 0

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 连接 QEMU
  • 从文件 x86/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)

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论