Skip to content

Commit 2b61178

Browse files
committed
Use WHvResetPartition on windows
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent c171901 commit 2b61178

File tree

6 files changed

+149
-118
lines changed

6 files changed

+149
-118
lines changed

src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,15 @@ impl HyperlightVm {
8989
unimplemented!("get_snapshot_sregs")
9090
}
9191

92-
pub(crate) fn reset_vcpu(
92+
pub(crate) fn reset_vm_state(&mut self) -> std::result::Result<(), RegisterError> {
93+
unimplemented!("reset_vm_state")
94+
}
95+
96+
pub(crate) fn restore_sregs(
9397
&mut self,
9498
_cr3: u64,
9599
_sregs: &CommonSpecialRegisters,
96100
) -> std::result::Result<(), RegisterError> {
97-
unimplemented!("reset_vcpu")
101+
unimplemented!("restore_sregs")
98102
}
99103
}

src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ use crate::hypervisor::gdb::{
4040
};
4141
#[cfg(gdb)]
4242
use crate::hypervisor::gdb::{DebugError, DebugMemoryAccessError};
43-
use crate::hypervisor::regs::{
44-
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
45-
};
43+
#[cfg(not(target_os = "windows"))]
44+
use crate::hypervisor::regs::CommonDebugRegs;
45+
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
4646
#[cfg(not(gdb))]
4747
use crate::hypervisor::virtual_machine::VirtualMachine;
4848
#[cfg(kvm)]
@@ -335,23 +335,37 @@ impl HyperlightVm {
335335
}
336336

337337
/// Resets the following vCPU state:
338-
/// - General purpose registers
339-
/// - Debug registers
340-
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
341-
/// - Special registers (restored from snapshot, with CR3 updated to new page table location)
338+
/// - On Windows: calls WHvResetPartition (resets all per-VP state including
339+
/// GP registers, debug registers, XSAVE, MSRs, APIC, etc.)
340+
/// - On Linux: explicitly resets GP registers, debug registers, and XSAVE
341+
///
342+
/// This does NOT restore special registers (except on windows). Call `restore_sregs` separately
343+
/// after memory mappings are established.
342344
// TODO: check if other state needs to be reset
343-
pub(crate) fn reset_vcpu(
345+
pub(crate) fn reset_vm_state(&mut self) -> std::result::Result<(), RegisterError> {
346+
#[cfg(target_os = "windows")]
347+
self.vm.reset_partition()?;
348+
349+
#[cfg(not(target_os = "windows"))]
350+
{
351+
self.vm.set_regs(&CommonRegisters {
352+
rflags: 1 << 1, // Reserved bit always set
353+
..Default::default()
354+
})?;
355+
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
356+
self.vm.reset_xsave()?;
357+
}
358+
359+
Ok(())
360+
}
361+
362+
/// Restores special registers from snapshot with CR3 updated to the
363+
/// new page table location.
364+
pub(crate) fn restore_sregs(
344365
&mut self,
345366
cr3: u64,
346367
sregs: &CommonSpecialRegisters,
347368
) -> std::result::Result<(), RegisterError> {
348-
self.vm.set_regs(&CommonRegisters {
349-
rflags: 1 << 1, // Reserved bit always set
350-
..Default::default()
351-
})?;
352-
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
353-
self.vm.reset_xsave()?;
354-
355369
#[cfg(not(feature = "nanvix-unstable"))]
356370
{
357371
// Restore the full special registers from snapshot, but update CR3
@@ -885,7 +899,9 @@ mod tests {
885899
use super::*;
886900
#[cfg(kvm)]
887901
use crate::hypervisor::regs::FP_CONTROL_WORD_DEFAULT;
888-
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
902+
use crate::hypervisor::regs::{
903+
CommonDebugRegs, CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT,
904+
};
889905
use crate::hypervisor::virtual_machine::VirtualMachine;
890906
use crate::mem::layout::SandboxMemoryLayout;
891907
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
@@ -1206,16 +1222,44 @@ mod tests {
12061222
}
12071223

12081224
/// Assert that general-purpose registers are in reset state.
1209-
/// After reset, all registers should be zeroed except rflags which has
1210-
/// reserved bit 1 always set.
1225+
///
1226+
/// On Linux (KVM/MSHV): reset_vm_state explicitly zeroes all GP regs and sets
1227+
/// rflags = 0x2, so we verify all-zeros.
1228+
///
1229+
/// On Windows: WHvResetPartition sets architectural power-on defaults
1230+
/// per Intel SDM Vol. 3, Table 10-1:
1231+
/// RIP = 0xFFF0 (reset vector)
1232+
/// RDX = CPUID signature (CPU-dependent stepping/model/family)
1233+
/// RFLAGS = 0x2 (only reserved bit 1 set)
1234+
/// All other GP regs = 0
1235+
/// These are overwritten by dispatch_call_from_host before guest execution,
1236+
/// but we still verify the power-on state is correct.
12111237
fn assert_regs_reset(vm: &dyn VirtualMachine) {
1238+
let regs = vm.regs().unwrap();
1239+
#[cfg(not(target_os = "windows"))]
12121240
assert_eq!(
1213-
vm.regs().unwrap(),
1241+
regs,
12141242
CommonRegisters {
1215-
rflags: 1 << 1, // Reserved bit 1 is always set
1243+
rflags: 1 << 1,
12161244
..Default::default()
12171245
}
12181246
);
1247+
#[cfg(target_os = "windows")]
1248+
{
1249+
// WHvResetPartition sets x86 power-on reset values
1250+
// (Intel SDM Vol. 3, Table 10-1)
1251+
let expected = CommonRegisters {
1252+
rip: 0xFFF0, // Reset vector
1253+
rdx: regs.rdx, // CPUID signature (CPU-dependent)
1254+
rflags: 0x2, // Reserved bit 1
1255+
..Default::default()
1256+
};
1257+
assert_ne!(
1258+
regs.rdx, 0x4444444444444444,
1259+
"RDX should not retain dirty value"
1260+
);
1261+
assert_eq!(regs, expected);
1262+
}
12191263
}
12201264

12211265
/// Assert that FPU state is in reset state.
@@ -1629,7 +1673,8 @@ mod tests {
16291673
assert_eq!(got_sregs, expected_sregs);
16301674

16311675
// Reset the vCPU
1632-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1676+
hyperlight_vm.reset_vm_state().unwrap();
1677+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
16331678

16341679
// Verify registers are reset to defaults
16351680
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1694,7 +1739,7 @@ mod tests {
16941739
"xsave should be zeroed except for hypervisor-specific fields"
16951740
);
16961741

1697-
// Verify sregs are reset to defaults (CR3 is 0 as passed to reset_vcpu)
1742+
// Verify sregs are reset to defaults
16981743
assert_sregs_reset(hyperlight_vm.vm.as_ref(), 0);
16991744
}
17001745

@@ -1758,7 +1803,8 @@ mod tests {
17581803
assert_eq!(regs, expected_dirty);
17591804

17601805
// Reset vcpu
1761-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1806+
hyperlight_vm.reset_vm_state().unwrap();
1807+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
17621808

17631809
// Check registers are reset to defaults
17641810
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1882,7 +1928,8 @@ mod tests {
18821928
}
18831929

18841930
// Reset vcpu
1885-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1931+
hyperlight_vm.reset_vm_state().unwrap();
1932+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
18861933

18871934
// Check FPU is reset to defaults
18881935
assert_fpu_reset(hyperlight_vm.vm.as_ref());
@@ -1933,7 +1980,8 @@ mod tests {
19331980
assert_eq!(debug_regs, expected_dirty);
19341981

19351982
// Reset vcpu
1936-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1983+
hyperlight_vm.reset_vm_state().unwrap();
1984+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19371985

19381986
// Check debug registers are reset to default values
19391987
assert_debug_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1982,9 +2030,10 @@ mod tests {
19822030
assert_eq!(sregs, expected_dirty);
19832031

19842032
// Reset vcpu
1985-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
2033+
hyperlight_vm.reset_vm_state().unwrap();
2034+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19862035

1987-
// Check registers are reset to defaults (CR3 is 0 as passed to reset_vcpu)
2036+
// Check registers are reset to defaults
19882037
let sregs = hyperlight_vm.vm.sregs().unwrap();
19892038
let mut expected_reset = CommonSpecialRegisters::standard_64bit_defaults(0);
19902039
normalize_sregs_for_run_tests(&mut expected_reset, &sregs);
@@ -2020,7 +2069,11 @@ mod tests {
20202069
let root_pt_addr = ctx.ctx.vm.get_root_pt().unwrap();
20212070
let segment_state = ctx.ctx.vm.get_snapshot_sregs().unwrap();
20222071

2023-
ctx.ctx.vm.reset_vcpu(root_pt_addr, &segment_state).unwrap();
2072+
ctx.ctx.vm.reset_vm_state().unwrap();
2073+
ctx.ctx
2074+
.vm
2075+
.restore_sregs(root_pt_addr, &segment_state)
2076+
.unwrap();
20242077

20252078
// Re-run from entrypoint (flag=1 means guest skips dirty phase, just does FXSAVE)
20262079
// Use stack_top - 8 to match initialise()'s behavior (simulates call pushing return addr)

src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub(crate) enum HypervisorType {
107107
/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
108108
/// Only used by MSHV and WHP which use compacted XSAVE format and need to
109109
/// validate buffer size before accessing XCOMP_BV.
110-
#[cfg(any(mshv3, target_os = "windows"))]
110+
#[cfg(mshv3)]
111111
pub(crate) const XSAVE_MIN_SIZE: usize = 576;
112112

113113
/// Standard XSAVE buffer size (4KB) used by KVM and MSHV.
@@ -244,6 +244,9 @@ pub enum RegisterError {
244244
#[cfg(target_os = "windows")]
245245
#[error("Failed to convert WHP registers: {0}")]
246246
ConversionFailed(String),
247+
#[cfg(target_os = "windows")]
248+
#[error("Failed to reset partition: {0}")]
249+
ResetPartition(HypervisorError),
247250
}
248251

249252
/// Map memory error
@@ -341,18 +344,32 @@ pub(crate) trait VirtualMachine: Debug + Send {
341344
#[allow(dead_code)]
342345
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
343346
/// Set the debug registers of the vCPU
347+
#[allow(dead_code)] // Called on Linux (reset_vm_state) and via gdb, but dead on Windows release builds without gdb
344348
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
345349

346350
/// Get xsave
347351
#[allow(dead_code)]
348352
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
349353
/// Reset xsave to default state
354+
#[cfg(any(kvm, mshv3))]
350355
fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
351356
/// Set xsave - only used for tests
352357
#[cfg(test)]
353358
#[cfg(not(feature = "nanvix-unstable"))]
354359
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
355360

361+
/// Reset the partition using WHvResetPartition.
362+
///
363+
/// Resets all per-VP state to architectural defaults: GP registers, segment
364+
/// registers, control registers, EFER, MSRs, debug registers, full XSAVE
365+
/// state (x87/SSE/AVX/AVX-512/AMX), XCR0, APIC/LAPIC, pending interrupts,
366+
/// and VMCS/VMCB internals.
367+
///
368+
/// Does NOT reset: GPA memory mappings or contents, partition configuration,
369+
/// notification ports, or VPCI device state.
370+
#[cfg(target_os = "windows")]
371+
fn reset_partition(&mut self) -> std::result::Result<(), RegisterError>;
372+
356373
/// Get partition handle
357374
#[cfg(target_os = "windows")]
358375
fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;

0 commit comments

Comments
 (0)