From f4e081ab722d225ef7f6e28adabfcd31ee0c3b6d Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:56:02 -0800 Subject: [PATCH 1/6] Move kvm,hyperv_linux/hyperv_windows into vm module Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Clean up import paths after file move Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Rename hyperv_linux to mshv, hyperv_windows to whp Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/hyperlight_vm.rs | 10 +++++----- src/hyperlight_host/src/hypervisor/mod.rs | 10 +--------- .../src/hypervisor/{ => vm}/kvm.rs | 13 +++++++------ src/hyperlight_host/src/hypervisor/vm/mod.rs | 8 ++++++++ .../hypervisor/{hyperv_linux.rs => vm/mshv.rs} | 13 +++++++------ .../hypervisor/{hyperv_windows.rs => vm/whp.rs} | 15 +++++++-------- src/hyperlight_host/src/sandbox/hypervisor.rs | 14 +++++++------- 7 files changed, 42 insertions(+), 41 deletions(-) rename src/hyperlight_host/src/hypervisor/{ => vm}/kvm.rs (95%) create mode 100644 src/hyperlight_host/src/hypervisor/vm/mod.rs rename src/hyperlight_host/src/hypervisor/{hyperv_linux.rs => vm/mshv.rs} (96%) rename src/hyperlight_host/src/hypervisor/{hyperv_windows.rs => vm/whp.rs} (98%) diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs index 7fdd83b8f..9b60377c3 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs @@ -42,13 +42,13 @@ use crate::hypervisor::Hypervisor; use crate::hypervisor::LinuxInterruptHandle; #[cfg(crashdump)] use crate::hypervisor::crashdump; +use crate::hypervisor::regs::CommonSpecialRegisters; +#[cfg(kvm)] +use crate::hypervisor::vm::kvm::KvmVm; #[cfg(mshv3)] -use crate::hypervisor::hyperv_linux::MshvVm; +use crate::hypervisor::vm::mshv::MshvVm; #[cfg(target_os = "windows")] -use crate::hypervisor::hyperv_windows::WhpVm; -#[cfg(kvm)] -use crate::hypervisor::kvm::KvmVm; -use crate::hypervisor::regs::CommonSpecialRegisters; +use crate::hypervisor::vm::whp::WhpVm; #[cfg(target_os = "windows")] use crate::hypervisor::wrappers::HandleWrapper; use crate::hypervisor::{HyperlightExit, InterruptHandle, InterruptHandleImpl, get_max_log_level}; diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index 5cdd74b48..0c01f4e53 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -20,12 +20,6 @@ use crate::Result; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; use crate::mem::memory_region::MemoryRegion; -/// HyperV-on-linux functionality -#[cfg(mshv3)] -pub(crate) mod hyperv_linux; -#[cfg(target_os = "windows")] -pub(crate) mod hyperv_windows; - /// GDB debugging support #[cfg(gdb)] pub(crate) mod gdb; @@ -33,9 +27,7 @@ pub(crate) mod gdb; /// Abstracts over different hypervisor register representations pub(crate) mod regs; -#[cfg(kvm)] -/// Functionality to manipulate KVM-based virtual machines -pub(crate) mod kvm; +pub(crate) mod vm; #[cfg(target_os = "windows")] /// Hyperlight Surrogate Process diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/vm/kvm.rs similarity index 95% rename from src/hyperlight_host/src/hypervisor/kvm.rs rename to src/hyperlight_host/src/hypervisor/vm/kvm.rs index 037b60fc1..b37e78c43 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/vm/kvm.rs @@ -25,6 +25,7 @@ use tracing::{Span, instrument}; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; use crate::hypervisor::{HyperlightExit, Hypervisor}; use crate::mem::memory_region::MemoryRegion; use crate::{Result, new_error}; @@ -130,34 +131,34 @@ impl Hypervisor for KvmVm { } } - fn regs(&self) -> Result { + fn regs(&self) -> Result { let kvm_regs = self.vcpu_fd.get_regs()?; Ok((&kvm_regs).into()) } - fn set_regs(&self, regs: &super::regs::CommonRegisters) -> Result<()> { + fn set_regs(&self, regs: &CommonRegisters) -> Result<()> { let kvm_regs: kvm_regs = regs.into(); self.vcpu_fd.set_regs(&kvm_regs)?; Ok(()) } - fn fpu(&self) -> Result { + fn fpu(&self) -> Result { let kvm_fpu = self.vcpu_fd.get_fpu()?; Ok((&kvm_fpu).into()) } - fn set_fpu(&self, fpu: &super::regs::CommonFpu) -> Result<()> { + fn set_fpu(&self, fpu: &CommonFpu) -> Result<()> { let kvm_fpu: kvm_fpu = fpu.into(); self.vcpu_fd.set_fpu(&kvm_fpu)?; Ok(()) } - fn sregs(&self) -> Result { + fn sregs(&self) -> Result { let kvm_sregs = self.vcpu_fd.get_sregs()?; Ok((&kvm_sregs).into()) } - fn set_sregs(&self, sregs: &super::regs::CommonSpecialRegisters) -> Result<()> { + fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()> { let kvm_sregs: kvm_sregs = sregs.into(); self.vcpu_fd.set_sregs(&kvm_sregs)?; Ok(()) diff --git a/src/hyperlight_host/src/hypervisor/vm/mod.rs b/src/hyperlight_host/src/hypervisor/vm/mod.rs new file mode 100644 index 000000000..bf9ac1575 --- /dev/null +++ b/src/hyperlight_host/src/hypervisor/vm/mod.rs @@ -0,0 +1,8 @@ +#[cfg(kvm)] +/// Functionality to manipulate KVM-based virtual machines +pub(crate) mod kvm; +/// HyperV-on-linux functionality +#[cfg(mshv3)] +pub(crate) mod mshv; +#[cfg(target_os = "windows")] +pub(crate) mod whp; diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/vm/mshv.rs similarity index 96% rename from src/hyperlight_host/src/hypervisor/hyperv_linux.rs rename to src/hyperlight_host/src/hypervisor/vm/mshv.rs index 8c6feb351..79531fad8 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mshv.rs @@ -33,6 +33,7 @@ use tracing::{Span, instrument}; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; use crate::hypervisor::{HyperlightExit, Hypervisor}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, new_error}; @@ -175,34 +176,34 @@ impl Hypervisor for MshvVm { Ok(result) } - fn regs(&self) -> Result { + fn regs(&self) -> Result { let mshv_regs = self.vcpu_fd.get_regs()?; Ok((&mshv_regs).into()) } - fn set_regs(&self, regs: &super::regs::CommonRegisters) -> Result<()> { + fn set_regs(&self, regs: &CommonRegisters) -> Result<()> { let mshv_regs: StandardRegisters = regs.into(); self.vcpu_fd.set_regs(&mshv_regs)?; Ok(()) } - fn fpu(&self) -> Result { + fn fpu(&self) -> Result { let mshv_fpu = self.vcpu_fd.get_fpu()?; Ok((&mshv_fpu).into()) } - fn set_fpu(&self, fpu: &super::regs::CommonFpu) -> Result<()> { + fn set_fpu(&self, fpu: &CommonFpu) -> Result<()> { let mshv_fpu: FloatingPointUnit = fpu.into(); self.vcpu_fd.set_fpu(&mshv_fpu)?; Ok(()) } - fn sregs(&self) -> Result { + fn sregs(&self) -> Result { let mshv_sregs = self.vcpu_fd.get_sregs()?; Ok((&mshv_sregs).into()) } - fn set_sregs(&self, sregs: &super::regs::CommonSpecialRegisters) -> Result<()> { + fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()> { let mshv_sregs: SpecialRegisters = sregs.into(); self.vcpu_fd.set_sregs(&mshv_sregs)?; Ok(()) diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/vm/whp.rs similarity index 98% rename from src/hyperlight_host/src/hypervisor/hyperv_windows.rs rename to src/hyperlight_host/src/hypervisor/vm/whp.rs index cc6876a5b..ca6d1d8e2 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/vm/whp.rs @@ -23,16 +23,15 @@ use windows::Win32::System::LibraryLoader::*; use windows::core::s; use windows_result::HRESULT; -use super::regs::{ - Align16, WHP_FPU_NAMES, WHP_FPU_NAMES_LEN, WHP_REGS_NAMES, WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES, - WHP_SREGS_NAMES_LEN, -}; -use super::surrogate_process::SurrogateProcess; -use super::surrogate_process_manager::get_surrogate_process_manager; -use super::wrappers::HandleWrapper; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; -use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; +use crate::hypervisor::regs::{ + Align16, CommonFpu, CommonRegisters, CommonSpecialRegisters, WHP_FPU_NAMES, WHP_FPU_NAMES_LEN, + WHP_REGS_NAMES, WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES, WHP_SREGS_NAMES_LEN, +}; +use crate::hypervisor::surrogate_process::SurrogateProcess; +use crate::hypervisor::surrogate_process_manager::get_surrogate_process_manager; +use crate::hypervisor::wrappers::HandleWrapper; use crate::hypervisor::{HyperlightExit, Hypervisor}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, log_then_return, new_error}; diff --git a/src/hyperlight_host/src/sandbox/hypervisor.rs b/src/hyperlight_host/src/sandbox/hypervisor.rs index c72b7efbf..7ef773b79 100644 --- a/src/hyperlight_host/src/sandbox/hypervisor.rs +++ b/src/hyperlight_host/src/sandbox/hypervisor.rs @@ -17,10 +17,10 @@ limitations under the License. use std::fmt::Debug; use std::sync::OnceLock; -#[cfg(mshv3)] -use crate::hypervisor::hyperv_linux; #[cfg(kvm)] -use crate::hypervisor::kvm; +use crate::hypervisor::vm::kvm; +#[cfg(mshv3)] +use crate::hypervisor::vm::mshv; static AVAILABLE_HYPERVISOR: OnceLock> = OnceLock::new(); @@ -31,7 +31,7 @@ pub fn get_available_hypervisor() -> &'static Option { // If both features are enabled, we need to determine hypervisor at runtime. // Currently /dev/kvm and /dev/mshv cannot exist on the same machine, so the first one // that works is guaranteed to be correct. - if hyperv_linux::is_hypervisor_present() { + if mshv::is_hypervisor_present() { Some(HypervisorType::Mshv) } else if kvm::is_hypervisor_present() { Some(HypervisorType::Kvm) @@ -45,15 +45,15 @@ pub fn get_available_hypervisor() -> &'static Option { None } } else if #[cfg(mshv3)] { - if hyperv_linux::is_hypervisor_present() { + if mshv::is_hypervisor_present() { Some(HypervisorType::Mshv) } else { None } } else if #[cfg(target_os = "windows")] { - use crate::hypervisor::hyperv_windows; + use crate::hypervisor::vm::whp; - if hyperv_windows::is_hypervisor_present() { + if whp::is_hypervisor_present() { Some(HypervisorType::Whp) } else { None From 9cf78471873e87c25f95109ac575b7f3f22b1c2d Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:56:23 -0800 Subject: [PATCH 2/6] Rename Hypervisor trait to Vm trait. Rename HyperlightExit to VmExit Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- src/hyperlight_host/src/hypervisor/gdb/mod.rs | 5 +- .../src/hypervisor/hyperlight_vm.rs | 34 ++++---- src/hyperlight_host/src/hypervisor/mod.rs | 81 ------------------ src/hyperlight_host/src/hypervisor/vm/kvm.rs | 24 +++--- src/hyperlight_host/src/hypervisor/vm/mod.rs | 82 +++++++++++++++++++ src/hyperlight_host/src/hypervisor/vm/mshv.rs | 32 ++++---- src/hyperlight_host/src/hypervisor/vm/whp.rs | 22 ++--- 7 files changed, 140 insertions(+), 140 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/gdb/mod.rs b/src/hyperlight_host/src/hypervisor/gdb/mod.rs index cb866d511..213989039 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mod.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mod.rs @@ -31,9 +31,10 @@ use gdbstub::target::TargetError; use thiserror::Error; use x86_64_target::HyperlightSandboxTarget; +use super::InterruptHandle; use super::regs::CommonRegisters; -use super::{Hypervisor, InterruptHandle}; use crate::hypervisor::regs::CommonFpu; +use crate::hypervisor::vm::Vm; use crate::mem::layout::SandboxMemoryLayout; use crate::mem::memory_region::MemoryRegion; use crate::mem::mgr::SandboxMemoryManager; @@ -276,7 +277,7 @@ pub(crate) enum DebugResponse { /// Trait for VMs that support debugging capabilities. /// This extends the base Hypervisor trait with GDB-specific functionality. -pub(crate) trait DebuggableVm: Hypervisor { +pub(crate) trait DebuggableVm: Vm { /// Translates a guest virtual address to a guest physical address fn translate_gva(&self, gva: u64) -> crate::Result; diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs index 9b60377c3..001351899 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs @@ -36,13 +36,13 @@ use super::regs::{CommonFpu, CommonRegisters}; #[cfg(target_os = "windows")] use super::{PartitionState, WindowsInterruptHandle}; use crate::HyperlightError::{ExecutionCanceledByHost, NoHypervisorFound}; -#[cfg(not(gdb))] -use crate::hypervisor::Hypervisor; #[cfg(any(kvm, mshv3))] use crate::hypervisor::LinuxInterruptHandle; #[cfg(crashdump)] use crate::hypervisor::crashdump; use crate::hypervisor::regs::CommonSpecialRegisters; +#[cfg(not(gdb))] +use crate::hypervisor::vm::Vm; #[cfg(kvm)] use crate::hypervisor::vm::kvm::KvmVm; #[cfg(mshv3)] @@ -51,7 +51,7 @@ use crate::hypervisor::vm::mshv::MshvVm; use crate::hypervisor::vm::whp::WhpVm; #[cfg(target_os = "windows")] use crate::hypervisor::wrappers::HandleWrapper; -use crate::hypervisor::{HyperlightExit, InterruptHandle, InterruptHandleImpl, get_max_log_level}; +use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level, vm::VmExit}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; @@ -77,7 +77,7 @@ pub(crate) struct HyperlightVm { #[cfg(gdb)] vm: Box, #[cfg(not(gdb))] - vm: Box, + vm: Box, page_size: usize, entrypoint: u64, orig_rsp: GuestPtr, @@ -117,7 +117,7 @@ impl HyperlightVm { #[cfg(gdb)] type VmType = Box; #[cfg(not(gdb))] - type VmType = Box; + type VmType = Box; #[cfg_attr(not(gdb), allow(unused_mut))] let mut vm: VmType = match get_available_hypervisor() { @@ -368,7 +368,7 @@ impl HyperlightVm { let result = loop { // ===== KILL() TIMING POINT 2: Before set_tid() ===== // If kill() is called and ran to completion BEFORE this line executes: - // - CANCEL_BIT will be set and we will return an early HyperlightExit::Cancelled() + // - CANCEL_BIT will be set and we will return an early VmExit::Cancelled() // without sending any signals/WHV api calls #[cfg(any(kvm, mshv3))] self.interrupt_handle.set_tid(); @@ -379,7 +379,7 @@ impl HyperlightVm { let exit_reason = if self.interrupt_handle.is_cancelled() || self.interrupt_handle.is_debug_interrupted() { - Ok(HyperlightExit::Cancelled()) + Ok(VmExit::Cancelled()) } else { #[cfg(feature = "trace_guest")] tc.setup_guest_trace(Span::current().context()); @@ -424,7 +424,7 @@ impl HyperlightVm { // - Signals will not be sent match exit_reason { #[cfg(gdb)] - Ok(HyperlightExit::Debug { dr6, exception }) => { + Ok(VmExit::Debug { dr6, exception }) => { // Handle debug event (breakpoints) let stop_reason = arch::vcpu_stop_reason(self.vm.as_mut(), dr6, self.entrypoint, exception)?; @@ -433,13 +433,11 @@ impl HyperlightVm { } } - Ok(HyperlightExit::Halt()) => { + Ok(VmExit::Halt()) => { break Ok(()); } - Ok(HyperlightExit::IoOut(port, data)) => { - self.handle_io(mem_mgr, host_funcs, port, data)? - } - Ok(HyperlightExit::MmioRead(addr)) => { + Ok(VmExit::IoOut(port, data)) => self.handle_io(mem_mgr, host_funcs, port, data)?, + Ok(VmExit::MmioRead(addr)) => { let all_regions = self.sandbox_regions.iter().chain(self.get_mapped_regions()); match get_memory_access_violation( addr as usize, @@ -465,7 +463,7 @@ impl HyperlightVm { } } } - Ok(HyperlightExit::MmioWrite(addr)) => { + Ok(VmExit::MmioWrite(addr)) => { let all_regions = self.sandbox_regions.iter().chain(self.get_mapped_regions()); match get_memory_access_violation( addr as usize, @@ -491,7 +489,7 @@ impl HyperlightVm { } } } - Ok(HyperlightExit::Cancelled()) => { + Ok(VmExit::Cancelled()) => { // If cancellation was not requested for this specific guest function call, // the vcpu was interrupted by a stale cancellation. This can occur when: // - Linux: A signal from a previous call arrives late @@ -499,7 +497,7 @@ impl HyperlightVm { if !cancel_requested && !debug_interrupted { // Track that an erroneous vCPU kick occurred metrics::counter!(METRIC_ERRONEOUS_VCPU_KICKS).increment(1); - // treat this the same as a HyperlightExit::Retry, the cancel was not meant for this call + // treat this the same as a VmExit::Retry, the cancel was not meant for this call continue; } @@ -517,10 +515,10 @@ impl HyperlightVm { metrics::counter!(METRIC_GUEST_CANCELLATION).increment(1); break Err(ExecutionCanceledByHost()); } - Ok(HyperlightExit::Unknown(reason)) => { + Ok(VmExit::Unknown(reason)) => { break Err(new_error!("Unexpected VM Exit: {:?}", reason)); } - Ok(HyperlightExit::Retry()) => continue, + Ok(VmExit::Retry()) => continue, Err(e) => { break Err(e); } diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index 0c01f4e53..767c9c4f5 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -16,10 +16,6 @@ limitations under the License. use log::LevelFilter; -use crate::Result; -use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; -use crate::mem::memory_region::MemoryRegion; - /// GDB debugging support #[cfg(gdb)] pub(crate) mod gdb; @@ -53,83 +49,6 @@ use std::sync::atomic::{AtomicU8, Ordering}; #[cfg(any(kvm, mshv3))] use std::time::Duration; -pub(crate) enum HyperlightExit { - /// The vCPU has exited due to a debug event (usually breakpoint) - #[cfg(gdb)] - Debug { dr6: u64, exception: u32 }, - /// The vCPU has halted - Halt(), - /// The vCPU has issued a write to the given port with the given value - IoOut(u16, Vec), - /// The vCPU tried to read from the given (unmapped) addr - MmioRead(u64), - /// The vCPU tried to write to the given (unmapped) addr - MmioWrite(u64), - /// The vCPU execution has been cancelled - Cancelled(), - /// The vCPU has exited for a reason that is not handled by Hyperlight - Unknown(String), - /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN - #[cfg_attr( - target_os = "windows", - expect( - dead_code, - reason = "Retry() is never constructed on Windows, but it is still matched on (which dead_code lint ignores)" - ) - )] - Retry(), -} - -/// Trait for single-vCPU VMs. Provides a common interface for basic VM operations. -/// Abstracts over differences between KVM, MSHV and WHP implementations. -pub(crate) trait Hypervisor: Debug + Send { - /// Map memory region into this VM - /// - /// # Safety - /// The caller must ensure that the memory region is valid and points to valid memory, - /// and lives long enough for the VM to use it. - /// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped - /// memory regions may be overwritten. - /// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries. - unsafe fn map_memory(&mut self, region: (u32, &MemoryRegion)) -> Result<()>; - - /// Unmap memory region from this VM that has previously been mapped using `map_memory`. - fn unmap_memory(&mut self, region: (u32, &MemoryRegion)) -> Result<()>; - - /// Runs the vCPU until it exits. - /// Note: this function should not emit any traces or spans as it is called after guest span is setup - fn run_vcpu(&mut self) -> Result; - - /// Get regs - #[allow(dead_code)] - fn regs(&self) -> Result; - /// Set regs - fn set_regs(&self, regs: &CommonRegisters) -> Result<()>; - /// Get fpu regs - #[allow(dead_code)] - fn fpu(&self) -> Result; - /// Set fpu regs - fn set_fpu(&self, fpu: &CommonFpu) -> Result<()>; - /// Get special regs - #[allow(dead_code)] - fn sregs(&self) -> Result; - /// Set special regs - fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()>; - - /// xsave - #[cfg(crashdump)] - fn xsave(&self) -> Result>; - - /// Get partition handle - #[cfg(target_os = "windows")] - fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE; - - /// Mark that initial memory setup is complete. After this, map_memory will fail. - /// This is only needed on Windows where dynamic memory mapping is not yet supported. - #[cfg(target_os = "windows")] - fn complete_initial_memory_setup(&mut self); -} - /// Get the logging level to pass to the guest entrypoint fn get_max_log_level() -> u32 { // Check to see if the RUST_LOG environment variable is set diff --git a/src/hyperlight_host/src/hypervisor/vm/kvm.rs b/src/hyperlight_host/src/hypervisor/vm/kvm.rs index b37e78c43..9253fa72d 100644 --- a/src/hyperlight_host/src/hypervisor/vm/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/vm/kvm.rs @@ -26,7 +26,7 @@ use tracing::{Span, instrument}; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; -use crate::hypervisor::{HyperlightExit, Hypervisor}; +use crate::hypervisor::vm::{VmExit, Vm}; use crate::mem::memory_region::MemoryRegion; use crate::{Result, new_error}; @@ -85,7 +85,7 @@ impl KvmVm { } } -impl Hypervisor for KvmVm { +impl Vm for KvmVm { unsafe fn map_memory(&mut self, (slot, region): (u32, &MemoryRegion)) -> Result<()> { let mut kvm_region: kvm_userspace_memory_region = region.into(); kvm_region.slot = slot; @@ -104,27 +104,27 @@ impl Hypervisor for KvmVm { Ok(()) } - fn run_vcpu(&mut self) -> Result { + fn run_vcpu(&mut self) -> Result { match self.vcpu_fd.run() { - Ok(VcpuExit::Hlt) => Ok(HyperlightExit::Halt()), - Ok(VcpuExit::IoOut(port, data)) => Ok(HyperlightExit::IoOut(port, data.to_vec())), - Ok(VcpuExit::MmioRead(addr, _)) => Ok(HyperlightExit::MmioRead(addr)), - Ok(VcpuExit::MmioWrite(addr, _)) => Ok(HyperlightExit::MmioWrite(addr)), + Ok(VcpuExit::Hlt) => Ok(VmExit::Halt()), + Ok(VcpuExit::IoOut(port, data)) => Ok(VmExit::IoOut(port, data.to_vec())), + Ok(VcpuExit::MmioRead(addr, _)) => Ok(VmExit::MmioRead(addr)), + Ok(VcpuExit::MmioWrite(addr, _)) => Ok(VmExit::MmioWrite(addr)), #[cfg(gdb)] - Ok(VcpuExit::Debug(debug_exit)) => Ok(HyperlightExit::Debug { + Ok(VcpuExit::Debug(debug_exit)) => Ok(VmExit::Debug { dr6: debug_exit.dr6, exception: debug_exit.exception, }), Err(e) => match e.errno() { // InterruptHandle::kill() sends a signal (SIGRTMIN+offset) to interrupt the vcpu, which causes EINTR - libc::EINTR => Ok(HyperlightExit::Cancelled()), - libc::EAGAIN => Ok(HyperlightExit::Retry()), - _ => Ok(HyperlightExit::Unknown(format!( + libc::EINTR => Ok(VmExit::Cancelled()), + libc::EAGAIN => Ok(VmExit::Retry()), + _ => Ok(VmExit::Unknown(format!( "Unknown KVM VCPU error: {}", e ))), }, - Ok(other) => Ok(HyperlightExit::Unknown(format!( + Ok(other) => Ok(VmExit::Unknown(format!( "Unknown KVM VCPU exit: {:?}", other ))), diff --git a/src/hyperlight_host/src/hypervisor/vm/mod.rs b/src/hyperlight_host/src/hypervisor/vm/mod.rs index bf9ac1575..50a278e7b 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mod.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mod.rs @@ -1,3 +1,8 @@ +use crate::Result; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; +use crate::mem::memory_region::MemoryRegion; +use std::fmt::Debug; + #[cfg(kvm)] /// Functionality to manipulate KVM-based virtual machines pub(crate) mod kvm; @@ -6,3 +11,80 @@ pub(crate) mod kvm; pub(crate) mod mshv; #[cfg(target_os = "windows")] pub(crate) mod whp; + +pub(crate) enum VmExit { + /// The vCPU has exited due to a debug event (usually breakpoint) + #[cfg(gdb)] + Debug { dr6: u64, exception: u32 }, + /// The vCPU has halted + Halt(), + /// The vCPU has issued a write to the given port with the given value + IoOut(u16, Vec), + /// The vCPU tried to read from the given (unmapped) addr + MmioRead(u64), + /// The vCPU tried to write to the given (unmapped) addr + MmioWrite(u64), + /// The vCPU execution has been cancelled + Cancelled(), + /// The vCPU has exited for a reason that is not handled by Hyperlight + Unknown(String), + /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN + #[cfg_attr( + target_os = "windows", + expect( + dead_code, + reason = "Retry() is never constructed on Windows, but it is still matched on (which dead_code lint ignores)" + ) + )] + Retry(), +} + +/// Trait for single-vCPU VMs. Provides a common interface for basic VM operations. +/// Abstracts over differences between KVM, MSHV and WHP implementations. +pub(crate) trait Vm: Debug + Send { + /// Map memory region into this VM + /// + /// # Safety + /// The caller must ensure that the memory region is valid and points to valid memory, + /// and lives long enough for the VM to use it. + /// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped + /// memory regions may be overwritten. + /// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries. + unsafe fn map_memory(&mut self, region: (u32, &MemoryRegion)) -> Result<()>; + + /// Unmap memory region from this VM that has previously been mapped using `map_memory`. + fn unmap_memory(&mut self, region: (u32, &MemoryRegion)) -> Result<()>; + + /// Runs the vCPU until it exits. + /// Note: this function should not emit any traces or spans as it is called after guest span is setup + fn run_vcpu(&mut self) -> Result; + + /// Get regs + #[allow(dead_code)] + fn regs(&self) -> Result; + /// Set regs + fn set_regs(&self, regs: &CommonRegisters) -> Result<()>; + /// Get fpu regs + #[allow(dead_code)] + fn fpu(&self) -> Result; + /// Set fpu regs + fn set_fpu(&self, fpu: &CommonFpu) -> Result<()>; + /// Get special regs + #[allow(dead_code)] + fn sregs(&self) -> Result; + /// Set special regs + fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()>; + + /// xsave + #[cfg(crashdump)] + fn xsave(&self) -> Result>; + + /// Get partition handle + #[cfg(target_os = "windows")] + fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE; + + /// Mark that initial memory setup is complete. After this, map_memory will fail. + /// This is only needed on Windows where dynamic memory mapping is not yet supported. + #[cfg(target_os = "windows")] + fn complete_initial_memory_setup(&mut self); +} diff --git a/src/hyperlight_host/src/hypervisor/vm/mshv.rs b/src/hyperlight_host/src/hypervisor/vm/mshv.rs index 79531fad8..614eb6baf 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mshv.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mshv.rs @@ -34,7 +34,7 @@ use tracing::{Span, instrument}; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; -use crate::hypervisor::{HyperlightExit, Hypervisor}; +use crate::hypervisor::vm::{VmExit, Vm}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, new_error}; @@ -90,7 +90,7 @@ impl MshvVm { } } -impl Hypervisor for MshvVm { +impl Vm for MshvVm { unsafe fn map_memory(&mut self, (_slot, region): (u32, &MemoryRegion)) -> Result<()> { let mshv_region: mshv_user_mem_region = region.into(); self.vm_fd.map_user_memory(mshv_region)?; @@ -103,7 +103,7 @@ impl Hypervisor for MshvVm { Ok(()) } - fn run_vcpu(&mut self) -> Result { + fn run_vcpu(&mut self) -> Result { const HALT_MESSAGE: hv_message_type = hv_message_type_HVMSG_X64_HALT; const IO_PORT_INTERCEPT_MESSAGE: hv_message_type = hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT; @@ -116,7 +116,7 @@ impl Hypervisor for MshvVm { let result = match exit_reason { Ok(m) => match m.header.message_type { - HALT_MESSAGE => HyperlightExit::Halt(), + HALT_MESSAGE => VmExit::Halt(), IO_PORT_INTERCEPT_MESSAGE => { let io_message = m.to_ioport_info().map_err(mshv_ioctls::MshvError::from)?; let port_number = io_message.port_number; @@ -132,15 +132,15 @@ impl Hypervisor for MshvVm { }, ..Default::default() }])?; - HyperlightExit::IoOut(port_number, rax.to_le_bytes().to_vec()) + VmExit::IoOut(port_number, rax.to_le_bytes().to_vec()) } UNMAPPED_GPA_MESSAGE => { let mimo_message = m.to_memory_info().map_err(mshv_ioctls::MshvError::from)?; let addr = mimo_message.guest_physical_address; match MemoryRegionFlags::try_from(mimo_message)? { - MemoryRegionFlags::READ => HyperlightExit::MmioRead(addr), - MemoryRegionFlags::WRITE => HyperlightExit::MmioWrite(addr), - _ => HyperlightExit::Unknown("Unknown MMIO access".to_string()), + MemoryRegionFlags::READ => VmExit::MmioRead(addr), + MemoryRegionFlags::WRITE => VmExit::MmioWrite(addr), + _ => VmExit::Unknown("Unknown MMIO access".to_string()), } } INVALID_GPA_ACCESS_MESSAGE => { @@ -148,9 +148,9 @@ impl Hypervisor for MshvVm { let gpa = mimo_message.guest_physical_address; let access_info = MemoryRegionFlags::try_from(mimo_message)?; match access_info { - MemoryRegionFlags::READ => HyperlightExit::MmioRead(gpa), - MemoryRegionFlags::WRITE => HyperlightExit::MmioWrite(gpa), - _ => HyperlightExit::Unknown("Unknown MMIO access".to_string()), + MemoryRegionFlags::READ => VmExit::MmioRead(gpa), + MemoryRegionFlags::WRITE => VmExit::MmioWrite(gpa), + _ => VmExit::Unknown("Unknown MMIO access".to_string()), } } #[cfg(gdb)] @@ -159,18 +159,18 @@ impl Hypervisor for MshvVm { .to_exception_info() .map_err(mshv_ioctls::MshvError::from)?; let DebugRegisters { dr6, .. } = self.vcpu_fd.get_debug_regs()?; - HyperlightExit::Debug { + VmExit::Debug { dr6, exception: ex_info.exception_vector as u32, } } - other => HyperlightExit::Unknown(format!("Unknown MSHV VCPU exit: {:?}", other)), + other => VmExit::Unknown(format!("Unknown MSHV VCPU exit: {:?}", other)), }, Err(e) => match e.errno() { // InterruptHandle::kill() sends a signal (SIGRTMIN+offset) to interrupt the vcpu, which causes EINTR - libc::EINTR => HyperlightExit::Cancelled(), - libc::EAGAIN => HyperlightExit::Retry(), - _ => HyperlightExit::Unknown(format!("Unknown MSHV VCPU error: {}", e)), + libc::EINTR => VmExit::Cancelled(), + libc::EAGAIN => VmExit::Retry(), + _ => VmExit::Unknown(format!("Unknown MSHV VCPU error: {}", e)), }, }; Ok(result) diff --git a/src/hyperlight_host/src/hypervisor/vm/whp.rs b/src/hyperlight_host/src/hypervisor/vm/whp.rs index ca6d1d8e2..cd765fc77 100644 --- a/src/hyperlight_host/src/hypervisor/vm/whp.rs +++ b/src/hyperlight_host/src/hypervisor/vm/whp.rs @@ -31,8 +31,8 @@ use crate::hypervisor::regs::{ }; use crate::hypervisor::surrogate_process::SurrogateProcess; use crate::hypervisor::surrogate_process_manager::get_surrogate_process_manager; +use crate::hypervisor::vm::{VmExit, Vm}; use crate::hypervisor::wrappers::HandleWrapper; -use crate::hypervisor::{HyperlightExit, Hypervisor}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, log_then_return, new_error}; @@ -129,7 +129,7 @@ impl WhpVm { } } -impl Hypervisor for WhpVm { +impl Vm for WhpVm { unsafe fn map_memory(&mut self, (_slot, region): (u32, &MemoryRegion)) -> Result<()> { // Only allow memory mapping during initial setup (the first batch of regions). // After the initial setup is complete, subsequent calls should fail, @@ -205,7 +205,7 @@ impl Hypervisor for WhpVm { } #[expect(non_upper_case_globals, reason = "Windows API constant are lower case")] - fn run_vcpu(&mut self) -> Result { + fn run_vcpu(&mut self) -> Result { let mut exit_context: WHV_RUN_VP_EXIT_CONTEXT = Default::default(); unsafe { @@ -225,7 +225,7 @@ impl Hypervisor for WhpVm { WHvX64RegisterRip, Align16(WHV_REGISTER_VALUE { Reg64: rip }), )])?; - HyperlightExit::IoOut( + VmExit::IoOut( exit_context.Anonymous.IoPortAccess.PortNumber, exit_context .Anonymous @@ -235,7 +235,7 @@ impl Hypervisor for WhpVm { .to_vec(), ) }, - WHvRunVpExitReasonX64Halt => HyperlightExit::Halt(), + WHvRunVpExitReasonX64Halt => VmExit::Halt(), WHvRunVpExitReasonMemoryAccess => { let gpa = unsafe { exit_context.Anonymous.MemoryAccess.Gpa }; let access_info = unsafe { @@ -246,13 +246,13 @@ impl Hypervisor for WhpVm { }; let access_info = MemoryRegionFlags::try_from(access_info)?; match access_info { - MemoryRegionFlags::READ => HyperlightExit::MmioRead(gpa), - MemoryRegionFlags::WRITE => HyperlightExit::MmioWrite(gpa), - _ => HyperlightExit::Unknown("Unknown memory access type".to_string()), + MemoryRegionFlags::READ => VmExit::MmioRead(gpa), + MemoryRegionFlags::WRITE => VmExit::MmioWrite(gpa), + _ => VmExit::Unknown("Unknown memory access type".to_string()), } } // Execution was cancelled by the host. - WHvRunVpExitReasonCanceled => HyperlightExit::Cancelled(), + WHvRunVpExitReasonCanceled => VmExit::Cancelled(), #[cfg(gdb)] WHvRunVpExitReasonException => { let exception = unsafe { exit_context.Anonymous.VpException }; @@ -273,12 +273,12 @@ impl Hypervisor for WhpVm { unsafe { out[0].0.Reg64 } }; - HyperlightExit::Debug { + VmExit::Debug { dr6, exception: exception.ExceptionType as u32, } } - WHV_RUN_VP_EXIT_REASON(_) => HyperlightExit::Unknown(format!( + WHV_RUN_VP_EXIT_REASON(_) => VmExit::Unknown(format!( "Unknown exit reason '{}'", exit_context.ExitReason.0 )), From 1146fb702c1b9b56cc5878d3491b2d0ee94faace Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:38:19 -0800 Subject: [PATCH 3/6] Move hypervisor-related functions out of sandbox module Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/hyperlight_vm.rs | 3 +- src/hyperlight_host/src/hypervisor/vm/mod.rs | 96 +++++++++++++++++++ src/hyperlight_host/src/lib.rs | 4 +- src/hyperlight_host/src/sandbox/hypervisor.rs | 85 ---------------- src/hyperlight_host/src/sandbox/mod.rs | 31 ------ 5 files changed, 100 insertions(+), 119 deletions(-) delete mode 100644 src/hyperlight_host/src/sandbox/hypervisor.rs diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs index 001351899..0a4ae742f 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs @@ -41,8 +41,10 @@ use crate::hypervisor::LinuxInterruptHandle; #[cfg(crashdump)] use crate::hypervisor::crashdump; use crate::hypervisor::regs::CommonSpecialRegisters; +use crate::hypervisor::vm::HypervisorType; #[cfg(not(gdb))] use crate::hypervisor::vm::Vm; +use crate::hypervisor::vm::get_available_hypervisor; #[cfg(kvm)] use crate::hypervisor::vm::kvm::KvmVm; #[cfg(mshv3)] @@ -59,7 +61,6 @@ use crate::mem::shared_mem::HostSharedMemory; use crate::metrics::{METRIC_ERRONEOUS_VCPU_KICKS, METRIC_GUEST_CANCELLATION}; use crate::sandbox::SandboxConfiguration; use crate::sandbox::host_funcs::FunctionRegistry; -use crate::sandbox::hypervisor::{HypervisorType, get_available_hypervisor}; use crate::sandbox::outb::handle_outb; #[cfg(feature = "mem_profile")] use crate::sandbox::trace::MemTraceInfo; diff --git a/src/hyperlight_host/src/hypervisor/vm/mod.rs b/src/hyperlight_host/src/hypervisor/vm/mod.rs index 50a278e7b..f3986ca48 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mod.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mod.rs @@ -1,3 +1,5 @@ +use tracing::{Span, instrument}; + use crate::Result; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; use crate::mem::memory_region::MemoryRegion; @@ -12,6 +14,77 @@ pub(crate) mod mshv; #[cfg(target_os = "windows")] pub(crate) mod whp; +use std::sync::OnceLock; + +static AVAILABLE_HYPERVISOR: OnceLock> = OnceLock::new(); + +/// Returns which type of hypervisor is available, if any +pub fn get_available_hypervisor() -> &'static Option { + AVAILABLE_HYPERVISOR.get_or_init(|| { + cfg_if::cfg_if! { + if #[cfg(all(kvm, mshv3))] { + // If both features are enabled, we need to determine hypervisor at runtime. + // Currently /dev/kvm and /dev/mshv cannot exist on the same machine, so the first one + // that works is guaranteed to be correct. + if mshv::is_hypervisor_present() { + Some(HypervisorType::Mshv) + } else if kvm::is_hypervisor_present() { + Some(HypervisorType::Kvm) + } else { + None + } + } else if #[cfg(kvm)] { + if kvm::is_hypervisor_present() { + Some(HypervisorType::Kvm) + } else { + None + } + } else if #[cfg(mshv3)] { + if mshv::is_hypervisor_present() { + Some(HypervisorType::Mshv) + } else { + None + } + } else if #[cfg(target_os = "windows")] { + if whp::is_hypervisor_present() { + Some(HypervisorType::Whp) + } else { + None + } + } else { + None + } + } + }) +} + +/// Returns `true` if a suitable hypervisor is available. +/// If this returns `false`, no hypervisor-backed sandboxes can be created. +#[instrument(skip_all, parent = Span::current())] +pub fn is_hypervisor_present() -> bool { + get_available_hypervisor().is_some() +} + +/// The hypervisor types available for the current platform +#[derive(PartialEq, Eq, Debug)] +pub(crate) enum HypervisorType { + #[cfg(kvm)] + Kvm, + + #[cfg(mshv3)] + Mshv, + + #[cfg(target_os = "windows")] + Whp, +} + +// Compiler error if no hypervisor type is available +#[cfg(not(any(kvm, mshv3, target_os = "windows")))] +compile_error!( + "No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature." +); + +/// The various reasons a VM's vCPU can exit pub(crate) enum VmExit { /// The vCPU has exited due to a debug event (usually breakpoint) #[cfg(gdb)] @@ -88,3 +161,26 @@ pub(crate) trait Vm: Debug + Send { #[cfg(target_os = "windows")] fn complete_initial_memory_setup(&mut self); } + +#[cfg(test)] +mod tests { + + #[test] + // TODO: add support for testing on WHP + #[cfg(target_os = "linux")] + fn is_hypervisor_present() { + use std::path::Path; + + cfg_if::cfg_if! { + if #[cfg(all(kvm, mshv3))] { + assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present()); + } else if #[cfg(kvm)] { + assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present()); + } else if #[cfg(mshv3)] { + assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present()); + } else { + assert!(!super::is_hypervisor_present()); + } + } + } +} diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 5f034dc79..06771cecc 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -85,13 +85,13 @@ pub(crate) mod testing; /// The re-export for the `HyperlightError` type pub use error::HyperlightError; +/// The re-export for the `is_hypervisor_present` type +pub use hypervisor::vm::is_hypervisor_present; /// A sandbox that can call be used to make multiple calls to guest functions, /// and otherwise reused multiple times pub use sandbox::MultiUseSandbox; /// The re-export for the `UninitializedSandbox` type pub use sandbox::UninitializedSandbox; -/// The re-export for the `is_hypervisor_present` type -pub use sandbox::is_hypervisor_present; /// The re-export for the `GuestBinary` type pub use sandbox::uninitialized::GuestBinary; diff --git a/src/hyperlight_host/src/sandbox/hypervisor.rs b/src/hyperlight_host/src/sandbox/hypervisor.rs deleted file mode 100644 index 7ef773b79..000000000 --- a/src/hyperlight_host/src/sandbox/hypervisor.rs +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2025 The Hyperlight Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -use std::fmt::Debug; -use std::sync::OnceLock; - -#[cfg(kvm)] -use crate::hypervisor::vm::kvm; -#[cfg(mshv3)] -use crate::hypervisor::vm::mshv; - -static AVAILABLE_HYPERVISOR: OnceLock> = OnceLock::new(); - -pub fn get_available_hypervisor() -> &'static Option { - AVAILABLE_HYPERVISOR.get_or_init(|| { - cfg_if::cfg_if! { - if #[cfg(all(kvm, mshv3))] { - // If both features are enabled, we need to determine hypervisor at runtime. - // Currently /dev/kvm and /dev/mshv cannot exist on the same machine, so the first one - // that works is guaranteed to be correct. - if mshv::is_hypervisor_present() { - Some(HypervisorType::Mshv) - } else if kvm::is_hypervisor_present() { - Some(HypervisorType::Kvm) - } else { - None - } - } else if #[cfg(kvm)] { - if kvm::is_hypervisor_present() { - Some(HypervisorType::Kvm) - } else { - None - } - } else if #[cfg(mshv3)] { - if mshv::is_hypervisor_present() { - Some(HypervisorType::Mshv) - } else { - None - } - } else if #[cfg(target_os = "windows")] { - use crate::hypervisor::vm::whp; - - if whp::is_hypervisor_present() { - Some(HypervisorType::Whp) - } else { - None - } - } else { - None - } - } - }) -} - -/// The hypervisor types available for the current platform -#[derive(PartialEq, Eq, Debug)] -pub(crate) enum HypervisorType { - #[cfg(kvm)] - Kvm, - - #[cfg(mshv3)] - Mshv, - - #[cfg(target_os = "windows")] - Whp, -} - -// Compiler error if no hypervisor type is available -#[cfg(not(any(kvm, mshv3, target_os = "windows")))] -compile_error!( - "No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature." -); diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index e637dd578..ccb15e3fb 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -18,8 +18,6 @@ limitations under the License. pub mod config; /// Functionality for reading, but not modifying host functions pub(crate) mod host_funcs; -/// Functionality for dealing with `Sandbox`es that contain Hypervisors -pub(crate) mod hypervisor; /// Functionality for dealing with initialized sandboxes that can /// call 0 or more guest functions pub mod initialized_multi_use; @@ -47,21 +45,11 @@ pub use callable::Callable; pub use config::SandboxConfiguration; /// Re-export for the `MultiUseSandbox` type pub use initialized_multi_use::MultiUseSandbox; -use tracing::{Span, instrument}; /// Re-export for `GuestBinary` type pub use uninitialized::GuestBinary; /// Re-export for `UninitializedSandbox` type pub use uninitialized::UninitializedSandbox; -/// Determine whether a suitable hypervisor is available to run -/// this sandbox. -/// -/// Returns a boolean indicating whether a suitable hypervisor is present. -#[instrument(skip_all, parent = Span::current())] -pub fn is_hypervisor_present() -> bool { - hypervisor::get_available_hypervisor().is_some() -} - #[cfg(test)] mod tests { use std::sync::Arc; @@ -73,25 +61,6 @@ mod tests { use crate::sandbox::uninitialized::GuestBinary; use crate::{MultiUseSandbox, UninitializedSandbox, new_error}; - #[test] - // TODO: add support for testing on WHP - #[cfg(target_os = "linux")] - fn is_hypervisor_present() { - use std::path::Path; - - cfg_if::cfg_if! { - if #[cfg(all(kvm, mshv3))] { - assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present()); - } else if #[cfg(kvm)] { - assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present()); - } else if #[cfg(mshv3)] { - assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present()); - } else { - assert!(!super::is_hypervisor_present()); - } - } - } - #[test] fn check_create_and_use_sandbox_on_different_threads() { let unintializedsandbox_queue = Arc::new(ArrayQueue::::new(10)); From bd7b26d2262c9e155d2b2776312ec848be91dd26 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:41:03 -0800 Subject: [PATCH 4/6] License header + cargo fmt Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/hyperlight_vm.rs | 5 ++--- src/hyperlight_host/src/hypervisor/vm/kvm.rs | 7 ++---- src/hyperlight_host/src/hypervisor/vm/mod.rs | 22 ++++++++++++++++--- src/hyperlight_host/src/hypervisor/vm/mshv.rs | 2 +- src/hyperlight_host/src/hypervisor/vm/whp.rs | 2 +- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs index 0a4ae742f..cf11e0f20 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs @@ -41,19 +41,18 @@ use crate::hypervisor::LinuxInterruptHandle; #[cfg(crashdump)] use crate::hypervisor::crashdump; use crate::hypervisor::regs::CommonSpecialRegisters; -use crate::hypervisor::vm::HypervisorType; #[cfg(not(gdb))] use crate::hypervisor::vm::Vm; -use crate::hypervisor::vm::get_available_hypervisor; #[cfg(kvm)] use crate::hypervisor::vm::kvm::KvmVm; #[cfg(mshv3)] use crate::hypervisor::vm::mshv::MshvVm; #[cfg(target_os = "windows")] use crate::hypervisor::vm::whp::WhpVm; +use crate::hypervisor::vm::{HypervisorType, VmExit, get_available_hypervisor}; #[cfg(target_os = "windows")] use crate::hypervisor::wrappers::HandleWrapper; -use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level, vm::VmExit}; +use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; diff --git a/src/hyperlight_host/src/hypervisor/vm/kvm.rs b/src/hyperlight_host/src/hypervisor/vm/kvm.rs index 9253fa72d..c74bfd977 100644 --- a/src/hyperlight_host/src/hypervisor/vm/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/vm/kvm.rs @@ -26,7 +26,7 @@ use tracing::{Span, instrument}; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; -use crate::hypervisor::vm::{VmExit, Vm}; +use crate::hypervisor::vm::{Vm, VmExit}; use crate::mem::memory_region::MemoryRegion; use crate::{Result, new_error}; @@ -119,10 +119,7 @@ impl Vm for KvmVm { // InterruptHandle::kill() sends a signal (SIGRTMIN+offset) to interrupt the vcpu, which causes EINTR libc::EINTR => Ok(VmExit::Cancelled()), libc::EAGAIN => Ok(VmExit::Retry()), - _ => Ok(VmExit::Unknown(format!( - "Unknown KVM VCPU error: {}", - e - ))), + _ => Ok(VmExit::Unknown(format!("Unknown KVM VCPU error: {}", e))), }, Ok(other) => Ok(VmExit::Unknown(format!( "Unknown KVM VCPU exit: {:?}", diff --git a/src/hyperlight_host/src/hypervisor/vm/mod.rs b/src/hyperlight_host/src/hypervisor/vm/mod.rs index f3986ca48..7e22aadc3 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mod.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mod.rs @@ -1,9 +1,27 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +use std::fmt::Debug; +use std::sync::OnceLock; + use tracing::{Span, instrument}; use crate::Result; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; use crate::mem::memory_region::MemoryRegion; -use std::fmt::Debug; #[cfg(kvm)] /// Functionality to manipulate KVM-based virtual machines @@ -14,8 +32,6 @@ pub(crate) mod mshv; #[cfg(target_os = "windows")] pub(crate) mod whp; -use std::sync::OnceLock; - static AVAILABLE_HYPERVISOR: OnceLock> = OnceLock::new(); /// Returns which type of hypervisor is available, if any diff --git a/src/hyperlight_host/src/hypervisor/vm/mshv.rs b/src/hyperlight_host/src/hypervisor/vm/mshv.rs index 614eb6baf..10e254369 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mshv.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mshv.rs @@ -34,7 +34,7 @@ use tracing::{Span, instrument}; #[cfg(gdb)] use crate::hypervisor::gdb::DebuggableVm; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; -use crate::hypervisor::vm::{VmExit, Vm}; +use crate::hypervisor::vm::{Vm, VmExit}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, new_error}; diff --git a/src/hyperlight_host/src/hypervisor/vm/whp.rs b/src/hyperlight_host/src/hypervisor/vm/whp.rs index cd765fc77..6b012df61 100644 --- a/src/hyperlight_host/src/hypervisor/vm/whp.rs +++ b/src/hyperlight_host/src/hypervisor/vm/whp.rs @@ -31,7 +31,7 @@ use crate::hypervisor::regs::{ }; use crate::hypervisor::surrogate_process::SurrogateProcess; use crate::hypervisor::surrogate_process_manager::get_surrogate_process_manager; -use crate::hypervisor::vm::{VmExit, Vm}; +use crate::hypervisor::vm::{Vm, VmExit}; use crate::hypervisor::wrappers::HandleWrapper; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, log_then_return, new_error}; From 6d2f287e56450bd35bfea45a0545a4a6ad4b28ed Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:18:20 -0800 Subject: [PATCH 5/6] Update comments based on PR feedback Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- src/hyperlight_host/src/hypervisor/gdb/mod.rs | 2 +- src/hyperlight_host/src/hypervisor/vm/mod.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/gdb/mod.rs b/src/hyperlight_host/src/hypervisor/gdb/mod.rs index 213989039..d773bf75a 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mod.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mod.rs @@ -276,7 +276,7 @@ pub(crate) enum DebugResponse { } /// Trait for VMs that support debugging capabilities. -/// This extends the base Hypervisor trait with GDB-specific functionality. +/// This extends the base Vm trait with GDB-specific functionality. pub(crate) trait DebuggableVm: Vm { /// Translates a guest virtual address to a guest physical address fn translate_gva(&self, gva: u64) -> crate::Result; diff --git a/src/hyperlight_host/src/hypervisor/vm/mod.rs b/src/hyperlight_host/src/hypervisor/vm/mod.rs index 7e22aadc3..fcf0d2912 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mod.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mod.rs @@ -23,12 +23,13 @@ use crate::Result; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; use crate::mem::memory_region::MemoryRegion; +/// KVM (Kernel-based Virtual Machine) functionality (linux) #[cfg(kvm)] -/// Functionality to manipulate KVM-based virtual machines pub(crate) mod kvm; -/// HyperV-on-linux functionality +/// MSHV (Microsoft Hypervisor) functionality (linux) #[cfg(mshv3)] pub(crate) mod mshv; +/// WHP (Windows Hypervisor Platform) functionality (windows) #[cfg(target_os = "windows")] pub(crate) mod whp; From 0995c4a7df4c400a5111784d78b7159e52d97e2e Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:00:15 -0800 Subject: [PATCH 6/6] Add unit tests for Vm trait Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- src/hyperlight_host/src/hypervisor/vm/mod.rs | 239 +++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/src/hyperlight_host/src/hypervisor/vm/mod.rs b/src/hyperlight_host/src/hypervisor/vm/mod.rs index fcf0d2912..34fbc12f0 100644 --- a/src/hyperlight_host/src/hypervisor/vm/mod.rs +++ b/src/hyperlight_host/src/hypervisor/vm/mod.rs @@ -181,6 +181,37 @@ pub(crate) trait Vm: Debug + Send { #[cfg(test)] mod tests { + use super::*; + use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister}; + + fn boxed_vm() -> Box { + let available_vm = get_available_hypervisor().as_ref().unwrap(); + match available_vm { + #[cfg(kvm)] + HypervisorType::Kvm => { + use crate::hypervisor::vm::kvm::KvmVm; + Box::new(KvmVm::new().unwrap()) + } + #[cfg(mshv3)] + HypervisorType::Mshv => { + use crate::hypervisor::vm::mshv::MshvVm; + Box::new(MshvVm::new().unwrap()) + } + #[cfg(target_os = "windows")] + HypervisorType::Whp => { + use hyperlight_common::mem::PAGE_SIZE_USIZE; + + use crate::hypervisor::vm::whp::WhpVm; + use crate::hypervisor::wrappers::HandleWrapper; + use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; + + let mem_size = PAGE_SIZE_USIZE; + let shared_mem = ExclusiveSharedMemory::new(mem_size).unwrap(); + let handle = HandleWrapper::from(shared_mem.get_mmap_file_handle()); + Box::new(WhpVm::new(handle, shared_mem.raw_mem_size()).unwrap()) + } + } + } #[test] // TODO: add support for testing on WHP @@ -200,4 +231,212 @@ mod tests { } } } + + #[test] + fn regs() { + let vm = boxed_vm(); + + let regs = CommonRegisters { + rax: 1, + rbx: 2, + rcx: 3, + rdx: 4, + rsi: 5, + rdi: 6, + rsp: 7, + rbp: 8, + r8: 9, + r9: 10, + r10: 11, + r11: 12, + r12: 13, + r13: 14, + r14: 15, + r15: 16, + rip: 17, + rflags: 0x2, + }; + + vm.set_regs(®s).unwrap(); + let read_regs = vm.regs().unwrap(); + assert_eq!(regs, read_regs); + } + + #[test] + fn fpu() { + let vm = boxed_vm(); + + // x87 FPU registers are 80-bit (10 bytes), stored in 16-byte slots for alignment. + // Only the first 10 bytes are preserved; the remaining 6 bytes are reserved/zeroed. + // See IntelĀ® 64 and IA-32 Architectures SDM, Vol. 1, Sec. 10.5.1.1 (x87 State) + let fpr_entry: [u8; 16] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]; + let fpu = CommonFpu { + fpr: [fpr_entry; 8], + fcw: 2, + fsw: 3, + ftwx: 4, + last_opcode: 5, + last_ip: 6, + last_dp: 7, + xmm: [[8; 16]; 16], + mxcsr: 9, + pad1: 0, + pad2: 0, + }; + vm.set_fpu(&fpu).unwrap(); + #[cfg_attr(not(kvm), allow(unused_mut))] + let mut read_fpu = vm.fpu().unwrap(); + #[cfg(kvm)] + { + read_fpu.mxcsr = fpu.mxcsr; // KVM get/set fpu does not preserve mxcsr + } + assert_eq!(fpu, read_fpu); + } + + #[test] + fn sregs() { + let vm = boxed_vm(); + + let segment = CommonSegmentRegister { + base: 1, + limit: 2, + selector: 3, + type_: 3, + present: 1, + dpl: 1, + db: 0, + s: 1, + l: 1, + g: 1, + avl: 1, + unusable: 0, + padding: 0, + }; + + let cs_segment = CommonSegmentRegister { + base: 1, + limit: 0xFFFF, + selector: 0x08, + type_: 0b1011, // code segment, execute/read, accessed + present: 1, + dpl: 1, + db: 0, // must be 0 in 64-bit mode + s: 1, + l: 1, // 64-bit mode + g: 1, + avl: 1, + unusable: 0, + padding: 0, + }; + let table = CommonTableRegister { + base: 12, + limit: 13, + }; + let sregs = CommonSpecialRegisters { + cs: cs_segment, + ds: segment, + es: segment, + fs: segment, + gs: segment, + ss: segment, + tr: segment, + ldt: segment, + gdt: table, + idt: table, + cr0: 0x80000011, // bit 0 (PE) + bit 4 (ET) + bit 31 (PG) + cr2: 2, + cr3: 3, + cr4: 0x20, + cr8: 5, + efer: 0x500, + apic_base: 0xFEE00900, + interrupt_bitmap: [0; 4], + }; + vm.set_sregs(&sregs).unwrap(); + let read_sregs = vm.sregs().unwrap(); + assert_eq!(sregs, read_sregs); + } + + /// Helper to create a page-aligned memory region for testing + #[cfg(any(kvm, mshv3))] + fn create_test_memory(size: usize) -> crate::mem::shared_mem::ExclusiveSharedMemory { + use hyperlight_common::mem::PAGE_SIZE_USIZE; + let aligned_size = size.div_ceil(PAGE_SIZE_USIZE) * PAGE_SIZE_USIZE; + crate::mem::shared_mem::ExclusiveSharedMemory::new(aligned_size).unwrap() + } + + /// Helper to create a MemoryRegion from ExclusiveSharedMemory + #[cfg(any(kvm, mshv3))] + fn region_for_test_memory( + mem: &crate::mem::shared_mem::ExclusiveSharedMemory, + guest_base: usize, + flags: crate::mem::memory_region::MemoryRegionFlags, + ) -> MemoryRegion { + use crate::mem::memory_region::MemoryRegionType; + use crate::mem::shared_mem::SharedMemory; + let ptr = mem.base_addr(); + let len = mem.mem_size(); + MemoryRegion { + host_region: ptr..(ptr + len), + guest_region: guest_base..(guest_base + len), + flags, + region_type: MemoryRegionType::Heap, + } + } + + #[test] + #[cfg(any(kvm, mshv3))] // Requires memory mapping support (TODO on WHP) + fn map_memory() { + use crate::mem::memory_region::MemoryRegionFlags; + + let mut vm = boxed_vm(); + + let mem1 = create_test_memory(4096); + let guest_addr: usize = 0x1000; + let region = region_for_test_memory( + &mem1, + guest_addr, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, + ); + + // SAFETY: The memory region points to valid memory allocated by ExclusiveSharedMemory, + // and will live until we drop mem1 at the end of the test. + // Slot 0 is not already mapped. + unsafe { + vm.map_memory((0, ®ion)).unwrap(); + } + + // Unmap the region + vm.unmap_memory((0, ®ion)).unwrap(); + + // Unmapping a region that was already unmapped should fail + vm.unmap_memory((0, ®ion)).unwrap_err(); + + // Unmapping a region that was never mapped should fail + vm.unmap_memory((99, ®ion)).unwrap_err(); + + // Re-map the same region to a different slot + // SAFETY: Same as above - memory is still valid and slot 1 is not mapped. + unsafe { + vm.map_memory((1, ®ion)).unwrap(); + } + + // Map a second region to a different slot + let mem2 = create_test_memory(4096); + let guest_addr2: usize = 0x2000; + let region2 = region_for_test_memory( + &mem2, + guest_addr2, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, + ); + + // SAFETY: Memory is valid from ExclusiveSharedMemory, slot 2 is not mapped. + unsafe { + vm.map_memory((2, ®ion2)).unwrap(); + } + + // Clean up: unmap both regions + vm.unmap_memory((1, ®ion)).unwrap(); + vm.unmap_memory((2, ®ion2)).unwrap(); + } }