使用 EXDI 设置 QEMU 内核模式调试

本主题介绍如何在 Windows 调试器中使用 EXDI 设置 QEMU 内核模式调试。

有关设置配置 EXDI 连接和故障排除的一般信息,请参阅配置 EXDI 调试器传输

使用 QEMU(虚拟化和计算机仿真软件),可以连接到充当主机的其他操作系统,例如 Linux。 QEMU 本身可以在许多体系结构上运行,例如 x64 和 Arm64。 ExdiGdb 调试服务器还支持其他处理器,例如,可以使用 WinDbg 调试 x64 上运行的 QEMU,从而模拟 Arm64。 使用 EXDI 还允许在启动过程中尽早调试 VM,甚至在 OS 加载之前也是如此。

注意

EXDI 是一种针对特定环境的高级专业调试方式。 使用标准 KDNET 连接更容易配置,因此建议使用。 要自动设置网络调试,请参阅设置 KDNET 网络内核自动调试

EXDI COM 服务器

EXDI 是一个接口,可通过添加对硬件调试器(如基于 JTAG 或基于 GdbServer)的支持来扩展 WinDbg。 下图说明了 EXDI-GdbServer 的作用。

显示了 EXDI-GdbServer 作用的堆栈图,上面有 WinDbg-DbgEng、EXDI 接口和与 GDB 服务器通信的 EXDI COM 服务器。

重要

由于 EXDI 未使用 KDNET 协议,因此连接的调试器所掌握的有关电脑上运行情况的信息要少得多,许多命令的运行方式也会不同,甚至根本无法运行。 访问被调试代码的专用符号有助于调试器更好地理解目标系统的代码执行。 有关详细信息,请参阅公共符号和专用符号

在 QEMU 上设置与 Windows 映像的调试器连接

这些步骤介绍如何附加到 Windows x64 虚拟机,将 GDB 服务器公开给 Windbg 客户端(使用 EXDI COM 服务器)也在 Windows 上运行。 使用 WinDbg ExdiGdbSrv.dll(GDB 服务器客户端)和 QEMU GDB 服务器之间的 GdbServer RSP 会话。

  1. 在 Windows 上下载并安装 QEMU。
  2. 配置目标 QEMU 虚拟 Windows 映像,以使用所需的网络和 BIOS/UEFI 设置进行调试。
  3. 使用启动脚本启动 QEMU 环境。
  4. 在 QEMU 上启动 GdbServer。
  5. 检查网络连接,找到并记录目标图像 IP 地址。 (LocalHost 的主机 IP 默认地址和端口为 1234)。
  6. 在主机系统中下载并安装 Windows 调试工具。
  7. 使用命令行或 UI 启动 WinDbg 以连接到 EXDI 服务器。
  8. 使用 WinDbg 来调试目标 QEMU Windows 映像。

QEMU 开源计算机模拟器

QEMU 是一个通用的开源计算机模拟器和虚拟化程序,用于进行动态转换。 当 QEMU 用作计算机模拟器时,它可以在不同的计算机上(x64 电脑)上运行为一种处理器(如 Arm64)开发的操作系统和程序。 它还可以运行/托管不同操作系统 (Windows/Linux/Mac) 的虚拟机镜像。

QEMU 可以与其他虚拟机监控程序(如 KVM)一起操作,以使用 CPU 扩展(HVM)进行虚拟化。 将 QEMU 用作虚拟化程序时,QEMU 可直接在主机 CPU 上执行来宾代码,从而实现接近本地的性能。 QEMU 可利用 OS 虚拟机监控程序的功能将 CPU 和 MMU 仿真卸载到实际硬件上。

下载并安装 QEMU

在本演练中,适用于 Windows x64 的 QEMU 将安装在一台 x64 电脑上,而 Windows 调试器也将在这台电脑上运行。

从 QEMU 下载页面下载 QEMU:https://www.qemu.org/download/

有关安装 QEMU 的信息,请参阅 QEMU 文档:https://www.qemu.org/documentation/

配置目标虚拟磁盘

找到或创建一个包含要调试的软件的虚拟磁盘镜像。

在本例中,将使用 Windows x64 VHDX 虚拟机磁盘映像。 要了解有关 Windows 虚拟机映像的详细信息,请参阅在 Windows 10 上使用 Hyper-V 创建虚拟机

在 Windows 映像中注入 VirtIO 驱动程序

要实现网络功能和合理的存储设备性能,可在 Windows 虚拟机磁盘镜像中注入或安装 VirtIO 驱动程序。 此处提供了 VirtIO 驱动程序: https://github.com/virtio-win/kvm-guest-drivers-windows

VirtIO 是一个标准化接口,允许虚拟机访问抽象硬件,如块设备、网络适配器和控制台。 VirtIO 充当虚拟化环境中硬件设备的抽象层,如 QEMU。

若要将 VirtIO 驱动程序注入 Windows 映像,请执行以下步骤:

  1. 例如 C:\VirtIo_Drivers,提取文件夹中的 VirtIo 驱动程序。
  2. 双击文件资源管理器中的 VHDX(也可以使用 diskpart),装载包含 Windows x64 虚拟机的 VHDX。 Windows 将使用特定字母装载 VHDX,例如“L:”
  3. 使用 Dism 在装载的映像中注入驱动程序: dism /image:L: /Add-Driver /driver:C:\VirtIo_Drivers 有关 DISM 的详细信息,请参阅 DISM 概述
  4. 该过程完成后,可以卸载映像,并继续将 VHDX 转换为 QEMU。

将 VHDX 转换为 QEMU

这一步并非必需但建议使用,因为使用本地 QEMU QCOW 映像而不是 VHDX 可以获得更好的性能。

使用以下 qemu-img.exe 命令转换 vhdx。 此实用工具位于安装 QEMU 的位置,例如 C:\Program Files\qemu

C:\Program Files\qemu> qemu-img convert -c -p -O qcow2 MyVHDXFile.vhdx MyQEMUFile.qcow2 

下载 UEFI 固件

为获得最佳效果,请下载或编译 UEFI 固件文件 (OVMF.fd)。 之所以需要固件,是因为 QEMU 默认情况下会模拟旧版 BIOS 系统。

UEFI 固件的一个来源是 Open Clear Linux 项目:https://clearlinux.org/

UEFI OVMF.fd 文件示例可在此处获取:https://github.com/clearlinux/common/tree/master/OVMF.fd

提取 C:\Program Files\qemu\Firmware 中下载文件的内容。

对于 Intel AMD64 以外的平台,应从 EDK2 中编译固件。 有关详细信息,请参阅 https://github.com/tianocore/tianocore.github.io/wiki/How-to-build-OVMF

配置 QEMU 启动脚本

在 QEMU 中创建配置文件。 例如,在 QEMU 根目录下创建一个 StartQEMUx64Windows.bat 文件。 请参阅以下示例文件。

使用 QEMU 启动脚本启动 QEMU

执行 QEMU 启动脚本以启动 QEMU。

c:\Program Files\qemu\StartQEMUx64Windows.bat

如果出现防火墙防御程序提示,请授予该程序对所有类型网络的所有权限,以便通过调试器主机的 Windows 防火墙启用 Windbg。

选中 Windows Defender 防火墙的所有三个选项对话框。

一旦 Windows 虚拟机在 QEMU 环境中启动,QEMU UI 就会出现。

显示了视图菜单选项的 QEMU 屏幕截图。

使用 CTRL+ALT+ 数字组合键进入 QEMU 监控控制台。 使用 View->compatmonitor 也可以使用此监视器。

键入 gdbserver 以便在 QEMU 上启动前端 GDB 服务器。

QEMU 应显示 Waiting for gdb connection on device ‘tcp::1234’

使用 CTRL+ALT+1 组合键返回主窗口。

提示:GDB 控制台窗口支持 system_reset 命令快速重启仿真。 键入 help GDB 控制台命令列表。

QEMU x64 Windows VM 启动脚本示例

下面是可用于 AMD64 虚拟机的 QEMU 配置脚本示例。 将指向 DISK 和 CDROM 文件的链接替换为电脑上的位置。

    REM
    REM  This script is used to run a Windows x64 VM on QEMU that is hosted by a Windows x64 host system
    REM  The Host system is a PC with Intel(R) Xeon(R) CPU.
    REM
    set EXECUTABLE=qemu-system-x86_64
    set MACHINE=-m 6G -smp 4

    REM No acceleration
    REM generic cpu emulation.
    REM to find out which CPU types are supported by the QEMU version on your system, then run:
    REM	 qemu-system-x86_64.exe -cpu help
    REM the see if your host system CPU is listed
    REM

    set CPU=-machine q35 

    REM Enables x64 UEFI-BIOS that will be used by QEMU :
    set BIOS=-bios "C:\Program Files\qemu\Firmware\OVMF.fd"

    REM  Use regular GFX simulation
    set GFX=-device ramfb -device VGA 
    set USB_CTRL=-device usb-ehci,id=usbctrl
    set KEYB_MOUSE=-device usb-kbd -device usb-tablet

    REM # The following line enable the full-speed HD controller (requires separate driver)
    REM # Following line uses the AHCI controller for the Virtual Hard Disk:
    set DRIVE0=-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0

    REM
    REM This will set the Windows VM x64 disk image that will be launched by QEMU
    REM The disk image is in the qcow2 format accepted by QEMU.
    REM You get the .qcow2 image, once you get the VHDX Windows VM x64 image 
    REM and apply the script to inject the virtio x64 drivers and then run the 
    REM the QEMU tool to convert the .VHDX image to .qcow2 format
    REM 	i.e. 
    REM	qemu-img convert -c -p -O qcow2 Windows_VM_VHDX_with_injected_drivers_file.vhdx file.qcow2
    REM file : points to the specified qcow2 image path.
    REM
    set DISK0=-drive id=disk,file="C:\Program Files\qemu\MyQEMUFile.qcow2",if=none

    REM
    REM for kdnet on, then best option:
    REM   NETWORK0="-netdev user,id=net0,hostfwd=tcp::53389-:3389,hostfwd=tcp::50001-:50001 -device virtio-net,netdev=net0,disable-legacy=on"
    REM
    REM Create a mapping for the RDP service from port 3389 to 3589.
    REM
    set NETHOST=-netdev user,id=net0,hostfwd=tcp::3589-:3389
    set NETGUEST=-device e1000,netdev=net0

    REM # The following line should enable the Daemon (instead of interactive)
    set DAEMON=-daemonize"
    %EXECUTABLE% %MACHINE% %CPU% %BIOS% %GFX% %USB_CTRL% %DRIVE0% %DISK0% %NETHOST% %NETGUEST%

网络连接

本地主机

如果 GDB 服务器已正确启动,则会看到 GDB 服务器将侦听的端口号,并且需要使用此端口来设置主机调试器 IP:Port 对。

如果主机调试器位于托管 QEMU 来宾的同一台计算机上,则会在 IP:端口对中使用 Localhost 标识符。 在此示例中,将使用同一台电脑上 LocalHost:1234 的服务器和主机调试器。

远程主机

如果在远程电脑上工作,请找到 Windows IP 地址(如果调试器主机会话不会与 QEMU VM 位于同一 Windows 计算机)。

目标 QEMU IP <address><port number> 将在 EXDI UI 中配置。

可在 QEMU 控制台 (compatmonitor0) 上发出以下命令,以显示有关网络和连接状态的信息。

info network
info usernet

有关 QEMU 网络连接的详细信息,请参阅https://wiki.qemu.org/Documentation/Networking

在主机系统中下载并安装 Windows 调试工具

在主机系统上安装 Windows 调试工具。 有关下载和安装调试器工具的信息,请参阅适用于 Windows 的调试工具

在主机系统上启动 WinDbg

在此处所述的方案中,在 EXDI UI 中设置以下选项进行连接。

目标类型 - QEMU

目标体系结构 - x64

目标 OS - Windows

图像扫描启发式大小 - 0xFFE - NT

Gdb 服务器和端口 - LocalHost:1234

连接中断 -

Windbg EXDI 内核连接 UI,其中显示了连接选项,包括 IP 和端口地址。

尽管建议使用 EXDI UI,但也可以使用类似于此处所示的命令行选项启动 WinDbg。

c:\Debuggers> windbg.exe -v -kx exdi:CLSID={29f9906e-9dbe-4d4b-b0fb-6acf7fb6d014},Kd=Guess,Inproc=ExdiGdbSrv.dll,DataBreaks=Exdi

使用命令行时,将使用 exdiConfigData.xml 文件配置 IP 地址和端口。 有关详细信息,请参阅 EXDI XML 配置文件

要显示更多输出,可使用 -v: 详细会话。 有关 WinDbg 选项的一般信息,请参阅 WinDbg 命令行选项

调试器应启动并连接至 QEMU GdbServer。

在窗口标题中显示 EXDI CLSID 的 WinDbg 主会话。

调试器将显示 EXDI 传输初始化成功。

EXDI: DbgCoInitialize returned 0x00000001
EXDI: CoCreateInstance() returned 0x00000000
EXDI: QueryInterface(IExdiServer3) returned 0x00000000
Target command response: QEMU
exdiCmd: The function: 'ExdiDbgType' was completed.
EXDI: Server::GetTargetInfo() returned 0x00000000
EXDI: Server::SetKeepaliveInterface() returned 0x00000000
EXDI: Server::GetNbCodeBpAvail() returned 0x00000000
EXDI: ExdiNotifyRunChange::Initialize() returned 0x00000000
EXDI: LiveKernelTargetInfo::Initialize() returned 0x00000000
EXDI: Target initialization succeeded

EXDIGdbServer 控制台数据包窗口还可以显示有关 EXDI 连接状态的信息,如果*“在”高级选项下显示通信数据包日志 “设置为”打开”。 有关详细信息,请参阅配置 EXDI 调试器传输中的故障排除信息。

使用 WinDbg 来调试目标 QEMU Windows 映像

dbgeng.dll 使用启发式算法来查找中断命令发生时 NT 基本加载地址的位置。 如果没有专用符号,此过程将失败。

这意味着在许多连接序列下,中断将无法发挥预期的功能。 如果手动侵入代码,它将是 Windows 当时正在执行的一个随机位置。 由于可能无法获得目标代码的符号,因此很难使用符号设置断点。

可用的调试器内存访问命令

以下直接访问内存的命令可以正常使用。

k、kb、kc、kd、kp、kP、kv(显示堆栈回溯)

r(寄存器)

d、da、db、dc、dd、dD、df、dp、dq、du、dw(显示内存)

u(取消汇编)

还可以通过代码进行操作。

p(步进)

还可以使用一些命令来尝试查找要调试的代码。

s(搜索内存)

.imgscan(查找映像标头)

Imgscan 对 EDXI 调试很有帮助,因为与传统的基于 KDNET 的内核调试不同,它可能无法根据符号设置断点。 定位所需的目标图像,可以方便地使用其位置来设置内存访问断点。

.exdicmd(EXDI 命令)

.exdicmd 使用活动 EXDI 调试连接向目标系统发送 EXDI 命令。 有关详细信息,请参阅 .exdicmd(EXDI 命令)

故障排除

请参阅配置 EXDI 调试器传输中的故障排除信息。

另请参阅

配置 EXDI 调试器传输

EXDI XML 配置文件

.exdicmd(EXDI 命令)

自动设置 KDNET 网络内核调试

手动设置 KDNET 网络内核调试