I built a Type-1 hypervisor in Rust that runs on my Ryzen 5 3450U. It loads from a UEFI boot entry before Windows, virtualizes all 8 logical processors via AMD SVM, and intercepts kernel operations through nested page tables. The base implementation came from tandasat/barevisor; however, there was a lot of work still to be done.
AMD's Secure Virtual Machine extensions let you run a guest OS inside a hardware-managed container. The core data structure is the VMCB (Virtual Machine Control Block), a 4KB page split into two regions: a control area that specifies which events should cause the guest to exit back to the hypervisor (VMEXITs), and a state-save area that holds the guest's register state.
Execution starts with a VMRUN instruction that takes the physical address of the VMCB. The CPU loads guest state, starts executing guest code, and keeps running until something triggers a VMEXIT (a CPUID instruction, a page fault in the nested page tables, an interrupt the hypervisor asked to intercept). On exit, the CPU saves guest state back to the VMCB and resumes in the hypervisor's host context. After handling the exit and possibly modifying the VMCB, the hypervisor executes VMRUN again.
Nested page tables (NPT) add a second layer of address translation. Guest page tables map guest virtual to guest physical; the hypervisor's NPT maps guest physical to host physical. Every memory access goes through both, so the hypervisor mediates all translation without the guest knowing.
AMD's Architecture Programmer's Manual (Vol. 2, section 15.22) says you can intercept System Management Interrupts by setting bit 1 of the VMCB's INTERCEPT_MISC2 field. SMIs are Ring -2 interrupts used by firmware for thermal management, hardware error handling, and vendor-specific platform tasks. They preempt everything, including the hypervisor.
On server silicon (EPYC, Threadripper PRO) the SMI intercept works as documented. I found out the hard way that on my consumer Ryzen 5 it does not.
Every time I tried to boot Windows under the hypervisor, the machine hung for exactly 12 seconds and then hard-reset. I was activating the hypervisor during UEFI's DXE (Driver Execution Environment) phase, before the OS loads. Firmware runs a lot of SMM code during DXE: thermal monitoring, hardware init, vendor-specific platform management. When an SMI fires while the hypervisor is active, the CPU needs to save its current state to the SMRAM save-state area before jumping to the SMM handler. The problem is that it saves guest context (the firmware code running as a guest) rather than the context the SMM handler expects.
Dell's OEM SMM handler parses that save-state expecting firmware register values. It gets guest register values instead. It reads garbage where it expects valid pointers, writes to addresses that don't make sense, and crashes. The platform watchdog sees the 12-second hang and resets the machine.
I spent about two weeks isolating this, disabling VMEXIT handlers one at a time and stripping the hypervisor down to a bare VMRUN loop with no intercepts except SMI, yet it still crashed. The VMCB's SMI intercept bit was set. Technically, the hypervisor should have caught the SMI before it reached firmware, but SMIs were going straight through to the SMM handler with corrupted save-state.
Root cause: SmmLock. Reading the HWCR MSR (0xC0010015) on my Dell board returns 0x09000011, which has bit 0 (SmmLock) set. When SmmLock is enabled, the processor locks down SMM configuration and ignores the VMCB's SMI intercept bit entirely. SMIs bypass the hypervisor and go directly to firmware's handler, which receives guest-provenance save-state it can't parse.
AMD's manual does not document this interaction. SmmLock is described in the BIOS and Kernel Developer's Guide as a security feature that prevents software from modifying SMM configuration after boot. What it doesn't say is that SmmLock also neuters the VMCB's ability to intercept SMIs. Consumer boards ship with SmmLock enabled. Server boards often don't, which is why the intercept works in test environments and fails on real laptops.
This killed the DXE-handoff architecture. If consumer AMD boards lock out SMI interception, you can't safely run a hypervisor while firmware is still doing SMM work, so I had to find a different way in.
Instead of activating during DXE, I wait until firmware finishes its SMM-heavy boot phase, activate the hypervisor, then chainload the Windows boot manager. This puts the hypervisor up after firmware is done with SMM but before Windows starts.
Problem: UEFI memory management. When the Windows boot manager calls ExitBootServices(), UEFI reclaims all memory regions typed as BOOT_SERVICES_CODE and BOOT_SERVICES_DATA. A hypervisor loaded as a normal EFI application has its code in BOOT_SERVICES pages, which ExitBootServices frees, leaving the VMRUN loop jumping to deallocated memory.
Fix: a two-binary deployment. boot_shim.efi is an EFI APPLICATION loaded from the UEFI boot menu. hv_driver.efi is an EFI RUNTIME_DRIVER that the shim loads via LoadImage/StartImage. Runtime drivers survive ExitBootServices because their pages are typed RUNTIME_SERVICES_CODE and RUNTIME_SERVICES_DATA, which the firmware is required to preserve.
There is a second problem. After ExitBootServices, the OS calls SetVirtualAddressMap() to remap all UEFI runtime services from physical to virtual addresses. The firmware walks every runtime-services binary's PE relocation table and patches code/data pointers to reflect the new virtual layout. If the hypervisor's host code gets relocated, the addresses baked into the VMRUN loop (host RIP, host RSP, host CR3) become wrong and the machine crashes.
I handle this by zeroing out the PE relocation directory in hv_driver.efi before it returns to the shim. SetVirtualAddressMap sees no relocations and skips it. The hypervisor's heap is allocated as RUNTIME_SERVICES_DATA (survives ExitBootServices) and forced below the 512 GiB boundary of the identity-mapped region so that physical addresses equal virtual addresses for all hypervisor memory.
The full boot sequence:
Getting the hypervisor to run on the bootstrap processor is one thing. Getting it to run on all 8 logical cores while Windows boots its SMP kernel is another. The base barevisor implementation only virtualized the BSP. Extending it to all application processors required fixing seven separate issues, most of which only manifested under specific timing conditions during Windows boot.
HLT and MWAIT are instructions the OS uses to put idle cores to sleep. If the hypervisor doesn't intercept them, a core that halts never wakes up because the VMRUN loop stalls. Windows puts APs into HLT during early boot and wakes them with INIT-SIPI sequences, so if HLT isn't intercepted, the machine hangs as soon as Windows tries to park a core.
INIT and SIPI (Startup IPI) are the mechanism x86 uses to bring secondary processors online. The BSP sends an INIT to reset the target AP, then a SIPI with a vector address where the AP should begin executing real-mode bootstrap code. Under SVM, both of these need to be intercepted and emulated: the hypervisor synthesizes the AP's initial register state (real mode, CS:IP from the SIPI vector, zeroed segments) and resumes it with VMRUN rather than letting the hardware reset actually happen.
NMI re-injection was a race condition. If a non-maskable interrupt arrives during a VMEXIT handler, the hypervisor needs to re-inject it into the guest on the next VMRUN. Missing this caused sporadic blue screens during heavy interrupt load, especially during boot when Windows is probing hardware.
I/O port passthrough was more subtle. Certain port ranges (PCI config space, ACPI registers, legacy PIC) need to be passed through to hardware without interception. The VMCB has an I/O permissions map (IOPM) that controls which ports cause VMEXITs. Getting the bitmap wrong caused ACPI timer reads to fault, which broke Windows' boot-time calibration loop and made the system clock run at the wrong rate.
PAUSE filtering was a performance issue. Windows spins on PAUSE in spinlock loops. Without a threshold, every PAUSE instruction caused a VMEXIT, and the hypervisor spent most of its time handling millions of PAUSE exits per second. Setting the PAUSE filter count to 128 in the VMCB lets short spin-waits complete in the guest and only exits on genuine long spins.
Last was a TR busy-bit fix specific to Zen+ steppings. On VMRUN, the processor checks that the Task Register's busy bit is set in the GDT descriptor. Some Zen+ steppings are strict about this in ways that later microarchitectures are not. Windows sets up its own TSS during boot, and if the hypervisor's VMCB TR state doesn't reflect the guest's GDT changes, VMRUN faults with a consistency check failure. I re-read the guest's GDT entry for the TR selector on every VMEXIT that modifies segment state and mirror the busy bit into the VMCB.