From b15f3b9cae924fcfc5b3364cba98eef9d0579780 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:57:00 +0000 Subject: [PATCH 1/3] Batch hv calls to set multiple registers at once Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../hypervisor/hyperlight_vm/test_support.rs | 30 ++++ .../src/hypervisor/hyperlight_vm/x86_64.rs | 84 +++++++--- .../src/hypervisor/virtual_machine/hvf/mod.rs | 12 ++ .../hypervisor/virtual_machine/kvm/x86_64.rs | 11 ++ .../src/hypervisor/virtual_machine/mod.rs | 20 +++ .../hypervisor/virtual_machine/mshv/x86_64.rs | 151 ++++++++++++++++++ .../src/hypervisor/virtual_machine/whp.rs | 49 ++++++ .../src/sandbox/initialized_multi_use.rs | 14 +- 8 files changed, 339 insertions(+), 32 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm/test_support.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm/test_support.rs index 75af812fd..e410d6266 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm/test_support.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm/test_support.rs @@ -232,6 +232,36 @@ impl VirtualMachine for FaultInjectingVirtualMachine { self.inner().set_xcr0(value) } + #[cfg(target_arch = "x86_64")] + fn can_batch_registers(&self) -> bool { + self.inner().can_batch_registers() + } + + #[cfg(target_arch = "x86_64")] + fn set_batched_registers( + &mut self, + regs: &CommonRegisters, + debug_regs: &CommonDebugRegs, + sregs: &CommonSpecialRegisters, + xcr0: u64, + msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError> { + if self.should_fail(VmOperation::SetRegs) { + return Err(RegisterError::SetRegs(Self::injected_error())); + } + if self.should_fail(VmOperation::SetDebugRegs) { + return Err(RegisterError::SetDebugRegs(Self::injected_error())); + } + if self.should_fail(VmOperation::SetSregs) { + return Err(RegisterError::SetSregs(Self::injected_error())); + } + if self.should_fail(VmOperation::SetMsrs) { + return Err(RegisterError::SetMsrs(Self::injected_error())); + } + self.inner_mut() + .set_batched_registers(regs, debug_regs, sregs, xcr0, msrs) + } + #[cfg(target_arch = "aarch64")] fn can_reset_vcpu(&self) -> bool { self.inner().can_reset_vcpu() diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs index 6fabdbfc5..1591b15c3 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. +use std::borrow::Cow; #[cfg(gdb)] use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -244,18 +245,25 @@ impl HyperlightVm { &mut self, snap_msrs: Option<&Vec>, ) -> std::result::Result<(), ResetVcpuError> { - match snap_msrs { + let msrs = Self::msr_reset_values(&self.msr_reset, snap_msrs)?; + self.vm.set_msrs(&msrs)?; + Ok(()) + } + + /// Returns the MSR values to restore. Snapshot values are used when provided. + /// All other values are reset to their initial values. + fn msr_reset_values<'a>( + msr_reset: &'a MsrResetState, + snapshot_msrs: Option<&Vec>, + ) -> std::result::Result, RegisterError> { + match snapshot_msrs { // No captured MSRs. Use this VM's baseline. - None => self.vm.set_msrs(self.msr_reset.baseline())?, + None => Ok(Cow::Borrowed(msr_reset.baseline())), // Scrub the reset set to the destination baseline and write the // snapshot's captured values on top. Validation rejects any // captured index the destination cannot restore. - Some(msrs) => { - let entries = self.msr_reset.validate_snapshot(msrs)?; - self.vm.set_msrs(&entries)?; - } + Some(msrs) => Ok(Cow::Owned(msr_reset.validate_snapshot(msrs)?)), } - Ok(()) } /// Dispatch a call from the host to the guest using the given pointer @@ -320,41 +328,64 @@ impl HyperlightVm { /// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults) /// - XCR0 /// - Special registers (restored from snapshot, with CR3 updated to new page table location) + /// - Model-specific registers // TODO: check if other state needs to be reset pub(crate) fn reset_vcpu( &mut self, cr3: u64, sregs: &CommonSpecialRegisters, + snapshot_msrs: Option<&Vec>, ) -> std::result::Result<(), ResetVcpuError> { - self.vm.set_regs(&CommonRegisters { + let regs = CommonRegisters { rflags: 1 << 1, // Reserved bit always set ..Default::default() - })?; - self.vm.set_debug_regs(&CommonDebugRegs::default())?; - self.vm.reset_xsave()?; - self.vm.set_xcr0(XCR0_RESET)?; + }; + let debug_regs = CommonDebugRegs::default(); + let sregs = Self::sregs_with_cr3(cr3, sregs)?; + let msrs = Self::msr_reset_values(&self.msr_reset, snapshot_msrs)?; - self.apply_sregs(cr3, sregs)?; + self.pending_tlb_flush = true; + // Batch to avoid multiple hvcall overhead if supported + if self.vm.can_batch_registers() { + self.vm.reset_xsave()?; + self.vm + .set_batched_registers(®s, &debug_regs, &sregs, XCR0_RESET, &msrs)?; + } else { + self.vm.set_regs(®s)?; + self.vm.set_debug_regs(&debug_regs)?; + self.vm.reset_xsave()?; + self.vm.set_xcr0(XCR0_RESET)?; + self.vm.set_sregs(&sregs)?; + self.vm.set_msrs(&msrs)?; + } Ok(()) } - /// Apply special registers and mark TLB for flush. - pub(crate) fn apply_sregs( - &mut self, + fn sregs_with_cr3( cr3: u64, sregs: &CommonSpecialRegisters, - ) -> std::result::Result<(), RegisterError> { + ) -> std::result::Result { if sregs.apic_base & crate::hypervisor::regs::APIC_BASE_X2APIC_ENABLE != 0 { return Err(RegisterError::InvalidSnapshotApicBase { value: sregs.apic_base, }); } - // Restore the full special registers from snapshot, but update CR3 - // to point to the new (relocated) page tables let mut sregs = *sregs; sregs.cr3 = cr3; + Ok(sregs) + } + + /// Apply special registers and mark TLB for flush. + pub(crate) fn apply_sregs( + &mut self, + cr3: u64, + sregs: &CommonSpecialRegisters, + ) -> std::result::Result<(), RegisterError> { + // Restore the full special registers from snapshot, but update CR3 + // to point to the new (relocated) page tables + let sregs = Self::sregs_with_cr3(cr3, sregs)?; self.pending_tlb_flush = true; self.vm.set_sregs(&sregs)?; @@ -1597,7 +1628,7 @@ mod tests { assert_eq!(hyperlight_vm.vm.xcr0().unwrap(), 3); // Reset the vCPU - hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); // Verify registers are reset to defaults assert_regs_reset(hyperlight_vm.vm.as_ref()); @@ -1761,7 +1792,7 @@ mod tests { assert_eq!(regs, expected_dirty); // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); // Check registers are reset to defaults assert_regs_reset(hyperlight_vm.vm.as_ref()); @@ -1885,7 +1916,7 @@ mod tests { } // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); // Check FPU is reset to defaults assert_fpu_reset(hyperlight_vm.vm.as_ref()); @@ -1936,7 +1967,7 @@ mod tests { assert_eq!(debug_regs, expected_dirty); // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); // Check debug registers are reset to default values assert_debug_regs_reset(hyperlight_vm.vm.as_ref()); @@ -1985,7 +2016,7 @@ mod tests { assert_eq!(sregs, expected_dirty); // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); // Check registers are reset to defaults (CR3 is 0 as passed to reset_vcpu) let sregs = hyperlight_vm.vm.sregs().unwrap(); @@ -2023,7 +2054,10 @@ mod tests { let root_pt_addr = ctx.ctx.vm.get_root_pt().unwrap(); let segment_state = ctx.ctx.vm.get_snapshot_sregs().unwrap(); - ctx.ctx.vm.reset_vcpu(root_pt_addr, &segment_state).unwrap(); + ctx.ctx + .vm + .reset_vcpu(root_pt_addr, &segment_state, None) + .unwrap(); // Re-run from entrypoint (flag=1 means guest skips dirty phase, just does FXSAVE) // Use stack_top - 8 to match initialise()'s behavior (simulates call pushing return addr) diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs index 9756c3953..f8f667ac2 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs @@ -1223,6 +1223,18 @@ impl VirtualMachine for HvfVm { todo!() } + #[cfg(target_arch = "x86_64")] + fn set_batched_registers( + &mut self, + _regs: &CommonRegisters, + _debug_regs: &CommonDebugRegs, + _sregs: &CommonSpecialRegisters, + _xcr0: u64, + _msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError> { + Err(RegisterError::BatchedSetRegistersUnsupported) + } + #[cfg(target_arch = "aarch64")] fn can_reset_vcpu(&self) -> bool { true diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs index e352ab20c..fd29600b7 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs @@ -648,6 +648,17 @@ impl VirtualMachine for KvmVm { .map_err(|e| RegisterError::SetXcrs(e.into())) } + fn set_batched_registers( + &mut self, + _regs: &CommonRegisters, + _debug_regs: &CommonDebugRegs, + _sregs: &CommonSpecialRegisters, + _xcr0: u64, + _msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError> { + Err(RegisterError::BatchedSetRegistersUnsupported) + } + #[cfg(test)] fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError> { if std::mem::size_of_val(xsave) != XSAVE_BUFFER_SIZE { diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs index 7fe08dab2..7bfbcf3dc 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs @@ -309,6 +309,12 @@ pub enum RegisterError { #[error("Failed to set MSRs: {0}")] SetMsrs(HypervisorError), #[cfg(target_arch = "x86_64")] + #[error("Failed to set batched registers: {0}")] + SetBatchedRegisters(HypervisorError), + #[cfg(target_arch = "x86_64")] + #[error("Batched register writes are not supported")] + BatchedSetRegistersUnsupported, + #[cfg(target_arch = "x86_64")] #[error("Snapshot MSR index {index:#x} is not in this VM's reset set")] InvalidSnapshotMsrIndex { /// Architectural MSR index supplied by the snapshot. @@ -503,6 +509,20 @@ pub(crate) trait VirtualMachine: Debug + Send { #[cfg(target_arch = "x86_64")] fn set_xcr0(&self, value: u64) -> std::result::Result<(), RegisterError>; + #[cfg(target_arch = "x86_64")] + fn can_batch_registers(&self) -> bool { + false + } + #[cfg(target_arch = "x86_64")] + fn set_batched_registers( + &mut self, + regs: &CommonRegisters, + debug_regs: &CommonDebugRegs, + sregs: &CommonSpecialRegisters, + xcr0: u64, + msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError>; + /// Single-operation vCPU reset #[cfg(target_arch = "aarch64")] fn can_reset_vcpu(&self) -> bool { diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs index 514d4c506..482adc300 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs @@ -616,6 +616,157 @@ impl VirtualMachine for MshvVm { .map_err(|e| RegisterError::SetXcrs(e.into())) } + fn can_batch_registers(&self) -> bool { + true + } + + fn set_batched_registers( + &mut self, + regs: &CommonRegisters, + debug_regs: &CommonDebugRegs, + sregs: &CommonSpecialRegisters, + xcr0: u64, + msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError> { + use mshv_bindings::*; + + if sregs.interrupt_bitmap.iter().any(|bits| *bits != 0) { + return Err(RegisterError::SetBatchedRegisters( + mshv_ioctls::MshvError::Errno(vmm_sys_util::errno::Error::new(libc::EINVAL)).into(), + )); + } + + macro_rules! reg64 { + ($name:expr, $value:expr) => { + hv_register_assoc { + name: $name, + value: hv_register_value { reg64: $value }, + ..Default::default() + } + }; + } + + let mut registers = vec![ + reg64!(hv_register_name_HV_X64_REGISTER_RAX, regs.rax), + reg64!(hv_register_name_HV_X64_REGISTER_RBX, regs.rbx), + reg64!(hv_register_name_HV_X64_REGISTER_RCX, regs.rcx), + reg64!(hv_register_name_HV_X64_REGISTER_RDX, regs.rdx), + reg64!(hv_register_name_HV_X64_REGISTER_RSI, regs.rsi), + reg64!(hv_register_name_HV_X64_REGISTER_RDI, regs.rdi), + reg64!(hv_register_name_HV_X64_REGISTER_RSP, regs.rsp), + reg64!(hv_register_name_HV_X64_REGISTER_RBP, regs.rbp), + reg64!(hv_register_name_HV_X64_REGISTER_R8, regs.r8), + reg64!(hv_register_name_HV_X64_REGISTER_R9, regs.r9), + reg64!(hv_register_name_HV_X64_REGISTER_R10, regs.r10), + reg64!(hv_register_name_HV_X64_REGISTER_R11, regs.r11), + reg64!(hv_register_name_HV_X64_REGISTER_R12, regs.r12), + reg64!(hv_register_name_HV_X64_REGISTER_R13, regs.r13), + reg64!(hv_register_name_HV_X64_REGISTER_R14, regs.r14), + reg64!(hv_register_name_HV_X64_REGISTER_R15, regs.r15), + reg64!(hv_register_name_HV_X64_REGISTER_RIP, regs.rip), + reg64!(hv_register_name_HV_X64_REGISTER_RFLAGS, regs.rflags), + reg64!(hv_register_name_HV_X64_REGISTER_DR0, debug_regs.dr0), + reg64!(hv_register_name_HV_X64_REGISTER_DR1, debug_regs.dr1), + reg64!(hv_register_name_HV_X64_REGISTER_DR2, debug_regs.dr2), + reg64!(hv_register_name_HV_X64_REGISTER_DR3, debug_regs.dr3), + reg64!(hv_register_name_HV_X64_REGISTER_DR6, debug_regs.dr6), + reg64!(hv_register_name_HV_X64_REGISTER_DR7, debug_regs.dr7), + ]; + + let mshv_sregs: SpecialRegisters = sregs.into(); + registers.extend([ + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_CS, + value: hv_register_value { + segment: mshv_sregs.cs.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_DS, + value: hv_register_value { + segment: mshv_sregs.ds.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_ES, + value: hv_register_value { + segment: mshv_sregs.es.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_FS, + value: hv_register_value { + segment: mshv_sregs.fs.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_GS, + value: hv_register_value { + segment: mshv_sregs.gs.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_SS, + value: hv_register_value { + segment: mshv_sregs.ss.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_TR, + value: hv_register_value { + segment: mshv_sregs.tr.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_LDTR, + value: hv_register_value { + segment: mshv_sregs.ldt.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_GDTR, + value: hv_register_value { + table: mshv_sregs.gdt.into(), + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_IDTR, + value: hv_register_value { + table: mshv_sregs.idt.into(), + }, + ..Default::default() + }, + reg64!(hv_register_name_HV_X64_REGISTER_INTERMEDIATE_CR0, sregs.cr0), + reg64!(hv_register_name_HV_X64_REGISTER_CR2, sregs.cr2), + reg64!(hv_register_name_HV_X64_REGISTER_INTERMEDIATE_CR3, sregs.cr3), + reg64!(hv_register_name_HV_X64_REGISTER_INTERMEDIATE_CR4, sregs.cr4), + reg64!(hv_register_name_HV_X64_REGISTER_CR8, sregs.cr8), + reg64!(hv_register_name_HV_X64_REGISTER_EFER, sregs.efer), + reg64!(hv_register_name_HV_X64_REGISTER_APIC_BASE, sregs.apic_base), + reg64!(hv_register_name_HV_X64_REGISTER_XFEM, xcr0), + ]); + + for entry in msrs { + registers.push(reg64!( + msr_to_hv_register_name(entry.index).map_err(|_| RegisterError::MsrsUnsupported)?, + entry.value + )); + } + + self.vcpu_fd + .set_reg(®isters) + .map_err(|error| RegisterError::SetBatchedRegisters(error.into())) + } + #[cfg(test)] fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError> { if std::mem::size_of_val(xsave) != XSAVE_BUFFER_SIZE { diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs index 350607cd3..77447342a 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs @@ -1009,6 +1009,55 @@ impl VirtualMachine for WhpVm { .map_err(|e| RegisterError::SetXcrs(e.into())) } + fn can_batch_registers(&self) -> bool { + true + } + + fn set_batched_registers( + &mut self, + regs: &CommonRegisters, + debug_regs: &CommonDebugRegs, + sregs: &CommonSpecialRegisters, + xcr0: u64, + msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError> { + let regs: [(WHV_REGISTER_NAME, Align16); WHP_REGS_NAMES_LEN] = + regs.into(); + let debug_regs: [(WHV_REGISTER_NAME, Align16); + WHP_DEBUG_REGS_NAMES_LEN] = debug_regs.into(); + let sregs: [(WHV_REGISTER_NAME, Align16); WHP_SREGS_NAMES_LEN] = + sregs.into(); + let msrs: Vec<_> = msrs + .iter() + .map(|entry| { + msr_to_whv_register_name(entry.index) + .map(|name| (name, Align16(WHV_REGISTER_VALUE { Reg64: entry.value }))) + .ok_or(RegisterError::MsrsUnsupported) + }) + .collect::>()?; + + let mut registers = + Vec::with_capacity(regs.len() + debug_regs.len() + sregs.len() + 1 + msrs.len()); + registers.extend(regs); + registers.extend(debug_regs); + #[cfg(feature = "hw-interrupts")] + registers.extend( + sregs + .into_iter() + .filter(|(name, _)| *name != WHvX64RegisterApicBase), + ); + #[cfg(not(feature = "hw-interrupts"))] + registers.extend(sregs); + registers.push(( + WHvX64RegisterXCr0, + Align16(WHV_REGISTER_VALUE { Reg64: xcr0 }), + )); + registers.extend(msrs); + + self.set_registers(®isters) + .map_err(|error| RegisterError::SetBatchedRegisters(error.into())) + } + #[cfg(test)] fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError> { // Get the required buffer size by calling with NULL buffer. diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 5ccaa0d84..966718b46 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -572,14 +572,14 @@ impl MultiUseSandbox { return Err(error); } + // Restore captured MSR state as part of the x86_64 vCPU reset. self.vm - .reset_vcpu(snapshot.root_pt_gpa(), sregs) - .map_err(HyperlightVmError::Restore)?; - - // Restore captured MSR state. - #[cfg(target_arch = "x86_64")] - self.vm - .restore_msrs(snapshot.msrs()) + .reset_vcpu( + snapshot.root_pt_gpa(), + sregs, + #[cfg(target_arch = "x86_64")] + snapshot.msrs(), + ) .map_err(HyperlightVmError::Restore)?; self.vm.set_stack_top(snapshot.stack_top_gva()); From 830d446a40308c7e234d5c7c0d9d4aaddb154b2b Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:11:48 -0700 Subject: [PATCH 2/3] Clean up some option semantics that's not needed Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/hyperlight_vm/x86_64.rs | 48 ++++++------------- .../src/hypervisor/regs/x86_64/msrs.rs | 7 +-- .../src/sandbox/initialized_multi_use.rs | 12 ++++- 3 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs index 1591b15c3..6af8df7ee 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. -use std::borrow::Cow; #[cfg(gdb)] use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -240,32 +239,16 @@ impl HyperlightVm { Ok(self.vm.msrs(&self.msr_reset.persist_indices())?) } - /// Restores snapshot MSRs or the initialization baseline. + /// Restores snapshot MSRs and resets all other MSRs to their initialization values. pub(crate) fn restore_msrs( &mut self, - snap_msrs: Option<&Vec>, + snap_msrs: &[MsrEntry], ) -> std::result::Result<(), ResetVcpuError> { - let msrs = Self::msr_reset_values(&self.msr_reset, snap_msrs)?; + let msrs = self.msr_reset.validate_snapshot(snap_msrs)?; self.vm.set_msrs(&msrs)?; Ok(()) } - /// Returns the MSR values to restore. Snapshot values are used when provided. - /// All other values are reset to their initial values. - fn msr_reset_values<'a>( - msr_reset: &'a MsrResetState, - snapshot_msrs: Option<&Vec>, - ) -> std::result::Result, RegisterError> { - match snapshot_msrs { - // No captured MSRs. Use this VM's baseline. - None => Ok(Cow::Borrowed(msr_reset.baseline())), - // Scrub the reset set to the destination baseline and write the - // snapshot's captured values on top. Validation rejects any - // captured index the destination cannot restore. - Some(msrs) => Ok(Cow::Owned(msr_reset.validate_snapshot(msrs)?)), - } - } - /// Dispatch a call from the host to the guest using the given pointer /// to the dispatch function _in the guest's address space_. /// @@ -328,13 +311,14 @@ impl HyperlightVm { /// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults) /// - XCR0 /// - Special registers (restored from snapshot, with CR3 updated to new page table location) - /// - Model-specific registers + /// - Model-specific registers (restored from snapshot, with omitted values reset to their + /// initialization values) // TODO: check if other state needs to be reset pub(crate) fn reset_vcpu( &mut self, cr3: u64, sregs: &CommonSpecialRegisters, - snapshot_msrs: Option<&Vec>, + snapshot_msrs: &[MsrEntry], ) -> std::result::Result<(), ResetVcpuError> { let regs = CommonRegisters { rflags: 1 << 1, // Reserved bit always set @@ -342,7 +326,7 @@ impl HyperlightVm { }; let debug_regs = CommonDebugRegs::default(); let sregs = Self::sregs_with_cr3(cr3, sregs)?; - let msrs = Self::msr_reset_values(&self.msr_reset, snapshot_msrs)?; + let msrs = self.msr_reset.validate_snapshot(snapshot_msrs)?; self.pending_tlb_flush = true; // Batch to avoid multiple hvcall overhead if supported @@ -608,11 +592,7 @@ impl HyperlightVm { /// Tests use it to classify each resettable MSR. #[cfg(test)] pub(crate) fn reset_set_indices(&self) -> Vec { - self.msr_reset - .baseline() - .iter() - .map(|entry| entry.index) - .collect() + self.msr_reset.reset_indices() } } @@ -1628,7 +1608,7 @@ mod tests { assert_eq!(hyperlight_vm.vm.xcr0().unwrap(), 3); // Reset the vCPU - hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), &[]).unwrap(); // Verify registers are reset to defaults assert_regs_reset(hyperlight_vm.vm.as_ref()); @@ -1792,7 +1772,7 @@ mod tests { assert_eq!(regs, expected_dirty); // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), &[]).unwrap(); // Check registers are reset to defaults assert_regs_reset(hyperlight_vm.vm.as_ref()); @@ -1916,7 +1896,7 @@ mod tests { } // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), &[]).unwrap(); // Check FPU is reset to defaults assert_fpu_reset(hyperlight_vm.vm.as_ref()); @@ -1967,7 +1947,7 @@ mod tests { assert_eq!(debug_regs, expected_dirty); // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), &[]).unwrap(); // Check debug registers are reset to default values assert_debug_regs_reset(hyperlight_vm.vm.as_ref()); @@ -2016,7 +1996,7 @@ mod tests { assert_eq!(sregs, expected_dirty); // Reset vcpu - hyperlight_vm.reset_vcpu(0, &default_sregs(), None).unwrap(); + hyperlight_vm.reset_vcpu(0, &default_sregs(), &[]).unwrap(); // Check registers are reset to defaults (CR3 is 0 as passed to reset_vcpu) let sregs = hyperlight_vm.vm.sregs().unwrap(); @@ -2056,7 +2036,7 @@ mod tests { ctx.ctx .vm - .reset_vcpu(root_pt_addr, &segment_state, None) + .reset_vcpu(root_pt_addr, &segment_state, &[]) .unwrap(); // Re-run from entrypoint (flag=1 means guest skips dirty phase, just does FXSAVE) diff --git a/src/hyperlight_host/src/hypervisor/regs/x86_64/msrs.rs b/src/hyperlight_host/src/hypervisor/regs/x86_64/msrs.rs index 3292d884b..775c26a18 100644 --- a/src/hyperlight_host/src/hypervisor/regs/x86_64/msrs.rs +++ b/src/hyperlight_host/src/hypervisor/regs/x86_64/msrs.rs @@ -60,9 +60,10 @@ impl MsrResetState { }) } - /// The creation-time baseline entries. - pub fn baseline(&self) -> &[MsrEntry] { - &self.baseline + /// Every MSR index in the reset set. + #[cfg(test)] + pub fn reset_indices(&self) -> Vec { + self.baseline.iter().map(|entry| entry.index).collect() } /// The MSR indices captured into a snapshot: the declared guest MSRs plus diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 966718b46..2f7c9c1b4 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -301,6 +301,10 @@ impl MultiUseSandbox { let sregs = snapshot.sregs().ok_or_else(|| { crate::new_error!("snapshot with NextAction::Call must have captured sregs") })?; + #[cfg(target_arch = "x86_64")] + let msrs = snapshot.msrs().ok_or_else(|| { + crate::new_error!("snapshot with NextAction::Call must have captured MSRs") + })?; vm.apply_sregs(hshm.layout.get_pt_base_gpa(), sregs) .map_err(|e| { crate::HyperlightError::HyperlightVmError( @@ -310,7 +314,7 @@ impl MultiUseSandbox { // Restore captured MSR state. #[cfg(target_arch = "x86_64")] - vm.restore_msrs(snapshot.msrs()).map_err(|e| { + vm.restore_msrs(msrs).map_err(|e| { crate::HyperlightError::HyperlightVmError( crate::hypervisor::hyperlight_vm::HyperlightVmError::Restore(e), ) @@ -555,6 +559,10 @@ impl MultiUseSandbox { let sregs = snapshot.sregs().ok_or_else(|| { HyperlightError::Error("snapshot from running sandbox should have sregs".to_string()) })?; + #[cfg(target_arch = "x86_64")] + let msrs = snapshot.msrs().ok_or_else(|| { + HyperlightError::Error("snapshot from running sandbox should have MSRs".to_string()) + })?; // Errors below leave the sandbox poisoned unless base mapping updates make it unrecoverable. self.status = SandboxStatus::Poisoned; @@ -578,7 +586,7 @@ impl MultiUseSandbox { snapshot.root_pt_gpa(), sregs, #[cfg(target_arch = "x86_64")] - snapshot.msrs(), + msrs, ) .map_err(HyperlightVmError::Restore)?; From f1e46c9233452a9a4a2413659e80c78556e070e2 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:20:30 -0700 Subject: [PATCH 3/3] Add unsupported operation as default method Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/virtual_machine/hvf/mod.rs | 12 ------------ .../src/hypervisor/virtual_machine/kvm/x86_64.rs | 11 ----------- .../src/hypervisor/virtual_machine/mod.rs | 14 ++++++++------ 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs index f8f667ac2..9756c3953 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs @@ -1223,18 +1223,6 @@ impl VirtualMachine for HvfVm { todo!() } - #[cfg(target_arch = "x86_64")] - fn set_batched_registers( - &mut self, - _regs: &CommonRegisters, - _debug_regs: &CommonDebugRegs, - _sregs: &CommonSpecialRegisters, - _xcr0: u64, - _msrs: &[MsrEntry], - ) -> std::result::Result<(), RegisterError> { - Err(RegisterError::BatchedSetRegistersUnsupported) - } - #[cfg(target_arch = "aarch64")] fn can_reset_vcpu(&self) -> bool { true diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs index fd29600b7..e352ab20c 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs @@ -648,17 +648,6 @@ impl VirtualMachine for KvmVm { .map_err(|e| RegisterError::SetXcrs(e.into())) } - fn set_batched_registers( - &mut self, - _regs: &CommonRegisters, - _debug_regs: &CommonDebugRegs, - _sregs: &CommonSpecialRegisters, - _xcr0: u64, - _msrs: &[MsrEntry], - ) -> std::result::Result<(), RegisterError> { - Err(RegisterError::BatchedSetRegistersUnsupported) - } - #[cfg(test)] fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError> { if std::mem::size_of_val(xsave) != XSAVE_BUFFER_SIZE { diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs index 7bfbcf3dc..c64f657ab 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs @@ -516,12 +516,14 @@ pub(crate) trait VirtualMachine: Debug + Send { #[cfg(target_arch = "x86_64")] fn set_batched_registers( &mut self, - regs: &CommonRegisters, - debug_regs: &CommonDebugRegs, - sregs: &CommonSpecialRegisters, - xcr0: u64, - msrs: &[MsrEntry], - ) -> std::result::Result<(), RegisterError>; + _regs: &CommonRegisters, + _debug_regs: &CommonDebugRegs, + _sregs: &CommonSpecialRegisters, + _xcr0: u64, + _msrs: &[MsrEntry], + ) -> std::result::Result<(), RegisterError> { + Err(RegisterError::BatchedSetRegistersUnsupported) + } /// Single-operation vCPU reset #[cfg(target_arch = "aarch64")]