Skip to content

Commit 9954908

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

8 files changed

Lines changed: 190 additions & 134 deletions

File tree

.github/workflows/ValidatePullRequest.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ jobs:
8989
# See: https://github.com/actions/runner/issues/2205
9090
if: ${{ !cancelled() && !failure() }}
9191
strategy:
92-
fail-fast: true
92+
fail-fast: false
9393
matrix:
9494
hypervisor: ['hyperv-ws2025', mshv3, kvm]
9595
cpu: [amd, intel]
@@ -112,7 +112,7 @@ jobs:
112112
# See: https://github.com/actions/runner/issues/2205
113113
if: ${{ !cancelled() && !failure() }}
114114
strategy:
115-
fail-fast: true
115+
fail-fast: false
116116
matrix:
117117
hypervisor: ['hyperv-ws2025', mshv3, kvm]
118118
cpu: [amd, intel]

Justfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ clippy target=default-target: (witguest-wit)
323323
clippyw target=default-target: (witguest-wit)
324324
{{ cargo-cmd }} clippy --all-targets --all-features --target x86_64-pc-windows-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
325325

326+
# Cross-check for linux from a windows host using clippy (no linking needed).
327+
# Only checks lib targets to avoid dev-dependencies (criterion->alloca) that need a C cross-compiler.
328+
clippyl target=default-target:
329+
{{ cargo-cmd }} clippy --lib --all-features --target x86_64-unknown-linux-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
330+
326331
clippy-guests target=default-target: (witguest-wit) (ensure-cargo-hyperlight)
327332
cd src/tests/rust_guests/simpleguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
328333
cd src/tests/rust_guests/witguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings

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: 119 additions & 42 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)]
@@ -329,27 +329,39 @@ impl HyperlightVm {
329329
}
330330

331331
/// Resets the following vCPU state:
332-
/// - General purpose registers
333-
/// - Debug registers
334-
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
335-
/// - Special registers (restored from snapshot, with CR3 updated to new page table location)
332+
/// - On Windows: calls WHvResetPartition (resets all per-VP state including
333+
/// GP registers, debug registers, XSAVE, MSRs, APIC, etc.)
334+
/// - On Linux: explicitly resets GP registers, debug registers, and XSAVE
335+
///
336+
/// This does NOT restore special registers — call `restore_sregs` separately
337+
/// after memory mappings are established.
336338
// TODO: check if other state needs to be reset
337-
pub(crate) fn reset_vcpu(
339+
pub(crate) fn reset_vm_state(&mut self) -> std::result::Result<(), RegisterError> {
340+
#[cfg(target_os = "windows")]
341+
self.vm.reset_partition()?;
342+
343+
#[cfg(not(target_os = "windows"))]
344+
{
345+
self.vm.set_regs(&CommonRegisters {
346+
rflags: 1 << 1, // Reserved bit always set
347+
..Default::default()
348+
})?;
349+
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
350+
self.vm.reset_xsave()?;
351+
}
352+
353+
Ok(())
354+
}
355+
356+
/// Restores special registers from snapshot with CR3 updated to the
357+
/// new page table location.
358+
pub(crate) fn restore_sregs(
338359
&mut self,
339360
cr3: u64,
340361
sregs: &CommonSpecialRegisters,
341362
) -> std::result::Result<(), RegisterError> {
342-
self.vm.set_regs(&CommonRegisters {
343-
rflags: 1 << 1, // Reserved bit always set
344-
..Default::default()
345-
})?;
346-
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
347-
self.vm.reset_xsave()?;
348-
349363
#[cfg(not(feature = "nanvix-unstable"))]
350364
{
351-
// Restore the full special registers from snapshot, but update CR3
352-
// to point to the new (relocated) page tables
353365
let mut sregs = *sregs;
354366
sregs.cr3 = cr3;
355367
self.pending_tlb_flush = true;
@@ -879,7 +891,9 @@ mod tests {
879891
use super::*;
880892
#[cfg(kvm)]
881893
use crate::hypervisor::regs::FP_CONTROL_WORD_DEFAULT;
882-
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
894+
use crate::hypervisor::regs::{
895+
CommonDebugRegs, CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT,
896+
};
883897
use crate::hypervisor::virtual_machine::VirtualMachine;
884898
use crate::mem::layout::SandboxMemoryLayout;
885899
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
@@ -906,7 +920,7 @@ mod tests {
906920
// Dirty State Builders - Create non-default vCPU state for testing reset
907921
// ==========================================================================
908922

909-
/// Build dirty general purpose registers for testing reset_vcpu.
923+
/// Build dirty general purpose registers for testing reset.
910924
fn dirty_regs() -> CommonRegisters {
911925
CommonRegisters {
912926
rax: 0x1111111111111111,
@@ -930,7 +944,7 @@ mod tests {
930944
}
931945
}
932946

933-
/// Build dirty FPU state for testing reset_vcpu.
947+
/// Build dirty FPU state for testing reset.
934948
fn dirty_fpu() -> CommonFpu {
935949
CommonFpu {
936950
fpr: [[0xAB; 16]; 8],
@@ -945,7 +959,7 @@ mod tests {
945959
}
946960
}
947961

948-
/// Build dirty special registers for testing reset_vcpu.
962+
/// Build dirty special registers for testing reset.
949963
/// Must be consistent for 64-bit long mode (CR0/CR4/EFER).
950964
fn dirty_sregs(_pml4_addr: u64) -> CommonSpecialRegisters {
951965
let segment = CommonSegmentRegister {
@@ -1014,7 +1028,7 @@ mod tests {
10141028
}
10151029
}
10161030

1017-
/// Build dirty debug registers for testing reset_vcpu.
1031+
/// Build dirty debug registers for testing reset.
10181032
///
10191033
/// DR6 bit layout (Intel SDM / AMD APM):
10201034
/// Bits 0-3 (B0-B3): Breakpoint condition detected - software writable/clearable
@@ -1058,8 +1072,8 @@ mod tests {
10581072
}
10591073
}
10601074

1061-
/// Returns default test values for reset_vcpu parameters.
1062-
/// Uses standard 64-bit defaults since reset_vcpu now restores full sregs from snapshot.
1075+
/// Returns default test values for restore_sregs parameters.
1076+
/// Uses standard 64-bit defaults since restore_sregs restores full sregs from snapshot.
10631077
fn default_sregs() -> CommonSpecialRegisters {
10641078
CommonSpecialRegisters::standard_64bit_defaults(0)
10651079
}
@@ -1183,9 +1197,18 @@ mod tests {
11831197
// Assertion Helpers - Verify vCPU state after reset
11841198
// ==========================================================================
11851199

1186-
/// Assert that debug registers are in reset state.
1187-
/// Reserved bits in DR6/DR7 are read-only (set by CPU), so we only check
1188-
/// that writable bits are cleared to 0 and DR0-DR3 are zeroed.
1200+
/// Assert that debug registers are in architectural reset state.
1201+
///
1202+
/// On Linux (KVM/MSHV): reset_vm_state explicitly zeroes debug registers.
1203+
///
1204+
/// On Windows: WHvResetPartition resets to power-on defaults per
1205+
/// Intel SDM Vol. 3, Table 10-1:
1206+
/// DR0-DR3 = 0 (breakpoint addresses cleared)
1207+
/// DR6 = 0xFFFF0FF0 (reserved bits set, writable bits cleared)
1208+
/// DR7 = 0x00000400 (reserved bit 10 set, all enables cleared)
1209+
///
1210+
/// Reserved bits in DR6/DR7 are read-only and CPU-dependent, so we only
1211+
/// verify that writable bits are cleared to 0 and DR0-DR3 are zeroed.
11891212
fn assert_debug_regs_reset(vm: &dyn VirtualMachine) {
11901213
let debug_regs = vm.debug_regs().unwrap();
11911214
let expected = CommonDebugRegs {
@@ -1200,19 +1223,58 @@ mod tests {
12001223
}
12011224

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

12151266
/// Assert that FPU state is in reset state.
1267+
///
1268+
/// On Linux (KVM/MSHV): reset_vm_state calls reset_xsave which zeroes FPU state
1269+
/// and sets FCW/MXCSR to defaults.
1270+
///
1271+
/// On Windows: WHvResetPartition resets to power-on defaults per
1272+
/// Intel SDM Vol. 3, Table 10-1 (FINIT-equivalent state):
1273+
/// FCW = 0x037F (all exceptions masked, precision=64-bit, round=nearest)
1274+
/// FSW = 0, FTW = 0 (all empty), FOP = 0, FIP = 0, FDP = 0
1275+
/// MXCSR = 0x1F80 (all SIMD exceptions masked, round=nearest)
1276+
/// ST0-ST7 = 0, XMM0-XMM15 = 0
1277+
///
12161278
/// Handles hypervisor-specific quirks (KVM MXCSR, empty FPU registers).
12171279
fn assert_fpu_reset(vm: &dyn VirtualMachine) {
12181280
let fpu = vm.fpu().unwrap();
@@ -1222,8 +1284,14 @@ mod tests {
12221284
assert_eq!(fpu, expected_fpu);
12231285
}
12241286

1225-
/// Assert that special registers are in reset state.
1226-
/// Handles hypervisor-specific differences in hidden descriptor cache fields.
1287+
/// Assert that special registers match the expected snapshot state.
1288+
///
1289+
/// After reset, sregs are explicitly restored from the snapshot
1290+
/// (with CR3 updated). This verifies they match the expected 64-bit
1291+
/// long mode configuration from CommonSpecialRegisters::standard_64bit_defaults.
1292+
///
1293+
/// Handles hypervisor-specific differences in hidden descriptor cache fields
1294+
/// (unusable, granularity, type_ for unused segments).
12271295
fn assert_sregs_reset(vm: &dyn VirtualMachine, pml4_addr: u64) {
12281296
let defaults = CommonSpecialRegisters::standard_64bit_defaults(pml4_addr);
12291297
let sregs = vm.sregs().unwrap();
@@ -1333,7 +1401,7 @@ mod tests {
13331401
dirtied_mask
13341402
}
13351403

1336-
/// Dirty the legacy XSAVE region (bytes 0-511) for testing reset_vcpu.
1404+
/// Dirty the legacy XSAVE region (bytes 0-511) for testing reset.
13371405
/// This includes FPU/x87 state, SSE state, and reserved areas.
13381406
///
13391407
/// Layout (from Intel SDM Table 13-1):
@@ -1621,7 +1689,8 @@ mod tests {
16211689
assert_eq!(got_sregs, expected_sregs);
16221690

16231691
// Reset the vCPU
1624-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1692+
hyperlight_vm.reset_vm_state().unwrap();
1693+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
16251694

16261695
// Verify registers are reset to defaults
16271696
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1686,7 +1755,7 @@ mod tests {
16861755
"xsave should be zeroed except for hypervisor-specific fields"
16871756
);
16881757

1689-
// Verify sregs are reset to defaults (CR3 is 0 as passed to reset_vcpu)
1758+
// Verify sregs are reset to defaults
16901759
assert_sregs_reset(hyperlight_vm.vm.as_ref(), 0);
16911760
}
16921761

@@ -1750,7 +1819,8 @@ mod tests {
17501819
assert_eq!(regs, expected_dirty);
17511820

17521821
// Reset vcpu
1753-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1822+
hyperlight_vm.reset_vm_state().unwrap();
1823+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
17541824

17551825
// Check registers are reset to defaults
17561826
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1874,7 +1944,8 @@ mod tests {
18741944
}
18751945

18761946
// Reset vcpu
1877-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1947+
hyperlight_vm.reset_vm_state().unwrap();
1948+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
18781949

18791950
// Check FPU is reset to defaults
18801951
assert_fpu_reset(hyperlight_vm.vm.as_ref());
@@ -1925,7 +1996,8 @@ mod tests {
19251996
assert_eq!(debug_regs, expected_dirty);
19261997

19271998
// Reset vcpu
1928-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1999+
hyperlight_vm.reset_vm_state().unwrap();
2000+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19292001

19302002
// Check debug registers are reset to default values
19312003
assert_debug_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1974,9 +2046,10 @@ mod tests {
19742046
assert_eq!(sregs, expected_dirty);
19752047

19762048
// Reset vcpu
1977-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
2049+
hyperlight_vm.reset_vm_state().unwrap();
2050+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19782051

1979-
// Check registers are reset to defaults (CR3 is 0 as passed to reset_vcpu)
2052+
// Check registers are reset to defaults
19802053
let sregs = hyperlight_vm.vm.sregs().unwrap();
19812054
let mut expected_reset = CommonSpecialRegisters::standard_64bit_defaults(0);
19822055
normalize_sregs_for_run_tests(&mut expected_reset, &sregs);
@@ -2012,7 +2085,11 @@ mod tests {
20122085
let root_pt_addr = ctx.ctx.vm.get_root_pt().unwrap();
20132086
let segment_state = ctx.ctx.vm.get_snapshot_sregs().unwrap();
20142087

2015-
ctx.ctx.vm.reset_vcpu(root_pt_addr, &segment_state).unwrap();
2088+
ctx.ctx.vm.reset_vm_state().unwrap();
2089+
ctx.ctx
2090+
.vm
2091+
.restore_sregs(root_pt_addr, &segment_state)
2092+
.unwrap();
20162093

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

0 commit comments

Comments
 (0)