libvirt的Hyper-V虚拟化或导致Windows KDNET初始化失败

近日因为遇到了某个Bug,希望在不重启的情况下,通过从宿主机Patch虚拟机内存的方式,修复此问题。因此发现了一个很有意思的项目memflow,这个项目提供了一套探查操作系统的物理内存的技术,可以在操作系统之外重建一些影响系统抽象层的能力。这个Rust项目的抽象也比较有意思,准备抽空深入学习下。

扯远了。在开发环境初始化好后,应用补丁时遇到了一些问题,BugCheck给出了一个有点出乎意料的错误。于是按照MSDN的指引,使用kdnet设置好参数后,重启虚拟机,但调试器并没和虚拟机建立链接。排除网络等原因后,为了查看具体的故障原因,执行kdnet,提示:

Network debugging is supported on the following NICs:
busparams=1.0.0, Intel(R)......
busparams=VM, Network debugging is supported by this Microsoft Hypervisor Virtual Machine, Cable status unknown.


KDNET transport initialization failed during a previous boot.  Status = 0xC0000182.
NIC hardware initialization failed.
KDNET did not successfully receive any packets during init.
KDNET did not successfully send any packets during init.

查询资料,KDNET是用一个指定的UDP端口和调试器互相通信。抓包发现在引导时,虚拟机在物理机对应的网卡上并没有数据包对外发出,说明问题应该出在虚拟机内部,或虚拟化模拟层面。

进一步查询后,发现早在2021年即有人在Bugzilla反馈过类似问题,发件人自称为Windows平台的驱动开发者,他们通过临时关闭Hyper-V模拟特性可以缓解这一问题。查看Qemu文档发现,Hyper-V的虚拟化开关功能极多。在开发环境通过ps检查,确认qemu进程携带了相关参数。由于开发环境是使用libvirt配置的,查看配置文件,发现:

<!-- ... -->
<hyperv mode='custom'>
    <relaxed state='on'/>
    <vapic state='on' />
    <spinlocks state='on' retries='8191' />
</hyperv>
<!-- ... -->
<clock>
    <!-- ... -->
    <timer name='hypervclock' present='yes' />
</clock>
<!-- ... -->

临时屏蔽相关段落后,重启,调试器连接成功。

回看相关错误消息,从kdnet输出也应该意识到,机器里有一个奇怪的网络调试设备。在QEMU文档中,和kdnet对应的网络调试能力是hv-syndbg:

hv-syndbg

Enables Hyper-V synthetic debugger interface, this is a special interface used by Windows Kernel debugger to send the packets through, rather than sending them via serial/network . When enabled, this enlightenment provides additional communication facilities to the guest: SynDbg messages. This new communication is used by Windows Kernel debugger rather than sending packets via serial/network, adding significant performance boost over the other comm channels. This enlightenment requires a VMBus device (-device vmbus-bridge,irq=15).

Requires: hv-relaxed, hv_time, hv-vapic, hv-vpindex, hv-synic, hv-runtime, hv-stimer

且无论这一选项是否开启,打开Hyper-V虚拟化功能后,都会使CPUID变更:

When any set of the Hyper-V enlightenments is enabled, QEMU changes hypervisor identification (CPUID 0x40000000..0x4000000A) to Hyper-V. KVM identification and features are kept in leaves 0x40000100..0x40000101.

kdnet.exe一侧,暂没有发现控制busparams=VM的有效手段,姑且认为无法关闭此特性。

总结下来是,错误搭配的CPUID和Hyper-V参数,导致内核初始化时使用错误的设备开启调试进程,进而触发异常。正确配置libvirt,或暂时关闭Hyper-V虚拟化功能均可缓解此问题。