Skip to content

Commit 9aebbcc

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

File tree

9 files changed

+225
-136
lines changed

9 files changed

+225
-136
lines changed

.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]

.github/workflows/dep_build_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defaults:
3737
jobs:
3838
build-and-test:
3939
if: ${{ inputs.docs_only == 'false' }}
40-
timeout-minutes: 45
40+
timeout-minutes: 60
4141
runs-on: ${{ fromJson(
4242
format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}"]',
4343
inputs.hypervisor == 'hyperv-ws2025' && 'Windows' || 'Linux',

Justfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ test-integration target=default-target features="":
235235
{{ cargo-cmd }} test {{ if features =="" {"--features executable_heap"} else if features=="no-default-features" {"--no-default-features --features executable_heap"} else {"--no-default-features -F executable_heap," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test integration_test execute_on_heap
236236

237237
@# run the rest of the integration tests
238-
{{ cargo-cmd }} test -p hyperlight-host {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test '*'
238+
{{ cargo-cmd }} test -p hyperlight-host {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test '*' -- --nocapture
239239

240240
# tests compilation with no default features on different platforms
241241
test-compilation-no-default-features target=default-target:
@@ -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)]
@@ -335,27 +335,39 @@ 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 — 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
{
357-
// Restore the full special registers from snapshot, but update CR3
358-
// to point to the new (relocated) page tables
359371
let mut sregs = *sregs;
360372
sregs.cr3 = cr3;
361373
self.pending_tlb_flush = true;
@@ -885,7 +897,9 @@ mod tests {
885897
use super::*;
886898
#[cfg(kvm)]
887899
use crate::hypervisor::regs::FP_CONTROL_WORD_DEFAULT;
888-
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
900+
use crate::hypervisor::regs::{
901+
CommonDebugRegs, CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT,
902+
};
889903
use crate::hypervisor::virtual_machine::VirtualMachine;
890904
use crate::mem::layout::SandboxMemoryLayout;
891905
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
@@ -912,7 +926,7 @@ mod tests {
912926
// Dirty State Builders - Create non-default vCPU state for testing reset
913927
// ==========================================================================
914928

915-
/// Build dirty general purpose registers for testing reset_vcpu.
929+
/// Build dirty general purpose registers for testing reset.
916930
fn dirty_regs() -> CommonRegisters {
917931
CommonRegisters {
918932
rax: 0x1111111111111111,
@@ -936,7 +950,7 @@ mod tests {
936950
}
937951
}
938952

939-
/// Build dirty FPU state for testing reset_vcpu.
953+
/// Build dirty FPU state for testing reset.
940954
fn dirty_fpu() -> CommonFpu {
941955
CommonFpu {
942956
fpr: [[0xAB; 16]; 8],
@@ -951,7 +965,7 @@ mod tests {
951965
}
952966
}
953967

954-
/// Build dirty special registers for testing reset_vcpu.
968+
/// Build dirty special registers for testing reset.
955969
/// Must be consistent for 64-bit long mode (CR0/CR4/EFER).
956970
fn dirty_sregs(_pml4_addr: u64) -> CommonSpecialRegisters {
957971
let segment = CommonSegmentRegister {
@@ -1020,7 +1034,7 @@ mod tests {
10201034
}
10211035
}
10221036

1023-
/// Build dirty debug registers for testing reset_vcpu.
1037+
/// Build dirty debug registers for testing reset.
10241038
///
10251039
/// DR6 bit layout (Intel SDM / AMD APM):
10261040
/// Bits 0-3 (B0-B3): Breakpoint condition detected - software writable/clearable
@@ -1064,8 +1078,8 @@ mod tests {
10641078
}
10651079
}
10661080

1067-
/// Returns default test values for reset_vcpu parameters.
1068-
/// Uses standard 64-bit defaults since reset_vcpu now restores full sregs from snapshot.
1081+
/// Returns default test values for restore_sregs parameters.
1082+
/// Uses standard 64-bit defaults since restore_sregs restores full sregs from snapshot.
10691083
fn default_sregs() -> CommonSpecialRegisters {
10701084
CommonSpecialRegisters::standard_64bit_defaults(0)
10711085
}
@@ -1189,9 +1203,18 @@ mod tests {
11891203
// Assertion Helpers - Verify vCPU state after reset
11901204
// ==========================================================================
11911205

1192-
/// Assert that debug registers are in reset state.
1193-
/// Reserved bits in DR6/DR7 are read-only (set by CPU), so we only check
1194-
/// that writable bits are cleared to 0 and DR0-DR3 are zeroed.
1206+
/// Assert that debug registers are in architectural reset state.
1207+
///
1208+
/// On Linux (KVM/MSHV): reset_vm_state explicitly zeroes debug registers.
1209+
///
1210+
/// On Windows: WHvResetPartition resets to power-on defaults per
1211+
/// Intel SDM Vol. 3, Table 10-1:
1212+
/// DR0-DR3 = 0 (breakpoint addresses cleared)
1213+
/// DR6 = 0xFFFF0FF0 (reserved bits set, writable bits cleared)
1214+
/// DR7 = 0x00000400 (reserved bit 10 set, all enables cleared)
1215+
///
1216+
/// Reserved bits in DR6/DR7 are read-only and CPU-dependent, so we only
1217+
/// verify that writable bits are cleared to 0 and DR0-DR3 are zeroed.
11951218
fn assert_debug_regs_reset(vm: &dyn VirtualMachine) {
11961219
let debug_regs = vm.debug_regs().unwrap();
11971220
let expected = CommonDebugRegs {
@@ -1206,19 +1229,58 @@ mod tests {
12061229
}
12071230

12081231
/// 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.
1232+
///
1233+
/// On Linux (KVM/MSHV): reset_vm_state explicitly zeroes all GP regs and sets
1234+
/// rflags = 0x2, so we verify all-zeros.
1235+
///
1236+
/// On Windows: WHvResetPartition sets architectural power-on defaults
1237+
/// per Intel SDM Vol. 3, Table 10-1:
1238+
/// RIP = 0xFFF0 (reset vector)
1239+
/// RDX = CPUID signature (CPU-dependent stepping/model/family)
1240+
/// RFLAGS = 0x2 (only reserved bit 1 set)
1241+
/// All other GP regs = 0
1242+
/// These are overwritten by dispatch_call_from_host before guest execution,
1243+
/// but we still verify the power-on state is correct.
12111244
fn assert_regs_reset(vm: &dyn VirtualMachine) {
1245+
let regs = vm.regs().unwrap();
1246+
#[cfg(not(target_os = "windows"))]
12121247
assert_eq!(
1213-
vm.regs().unwrap(),
1248+
regs,
12141249
CommonRegisters {
1215-
rflags: 1 << 1, // Reserved bit 1 is always set
1250+
rflags: 1 << 1,
12161251
..Default::default()
12171252
}
12181253
);
1254+
#[cfg(target_os = "windows")]
1255+
{
1256+
// WHvResetPartition sets x86 power-on reset values
1257+
// (Intel SDM Vol. 3, Table 10-1)
1258+
let expected = CommonRegisters {
1259+
rip: 0xFFF0, // Reset vector
1260+
rdx: regs.rdx, // CPUID signature (CPU-dependent)
1261+
rflags: 0x2, // Reserved bit 1
1262+
..Default::default()
1263+
};
1264+
assert_ne!(
1265+
regs.rdx, 0x4444444444444444,
1266+
"RDX should not retain dirty value"
1267+
);
1268+
assert_eq!(regs, expected);
1269+
}
12191270
}
12201271

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

1231-
/// Assert that special registers are in reset state.
1232-
/// Handles hypervisor-specific differences in hidden descriptor cache fields.
1293+
/// Assert that special registers match the expected snapshot state.
1294+
///
1295+
/// After reset, sregs are explicitly restored from the snapshot
1296+
/// (with CR3 updated). This verifies they match the expected 64-bit
1297+
/// long mode configuration from CommonSpecialRegisters::standard_64bit_defaults.
1298+
///
1299+
/// Handles hypervisor-specific differences in hidden descriptor cache fields
1300+
/// (unusable, granularity, type_ for unused segments).
12331301
fn assert_sregs_reset(vm: &dyn VirtualMachine, pml4_addr: u64) {
12341302
let defaults = CommonSpecialRegisters::standard_64bit_defaults(pml4_addr);
12351303
let sregs = vm.sregs().unwrap();
@@ -1339,7 +1407,7 @@ mod tests {
13391407
dirtied_mask
13401408
}
13411409

1342-
/// Dirty the legacy XSAVE region (bytes 0-511) for testing reset_vcpu.
1410+
/// Dirty the legacy XSAVE region (bytes 0-511) for testing reset.
13431411
/// This includes FPU/x87 state, SSE state, and reserved areas.
13441412
///
13451413
/// Layout (from Intel SDM Table 13-1):
@@ -1629,7 +1697,8 @@ mod tests {
16291697
assert_eq!(got_sregs, expected_sregs);
16301698

16311699
// Reset the vCPU
1632-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1700+
hyperlight_vm.reset_vm_state().unwrap();
1701+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
16331702

16341703
// Verify registers are reset to defaults
16351704
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1694,7 +1763,7 @@ mod tests {
16941763
"xsave should be zeroed except for hypervisor-specific fields"
16951764
);
16961765

1697-
// Verify sregs are reset to defaults (CR3 is 0 as passed to reset_vcpu)
1766+
// Verify sregs are reset to defaults
16981767
assert_sregs_reset(hyperlight_vm.vm.as_ref(), 0);
16991768
}
17001769

@@ -1758,7 +1827,8 @@ mod tests {
17581827
assert_eq!(regs, expected_dirty);
17591828

17601829
// Reset vcpu
1761-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1830+
hyperlight_vm.reset_vm_state().unwrap();
1831+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
17621832

17631833
// Check registers are reset to defaults
17641834
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1882,7 +1952,8 @@ mod tests {
18821952
}
18831953

18841954
// Reset vcpu
1885-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1955+
hyperlight_vm.reset_vm_state().unwrap();
1956+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
18861957

18871958
// Check FPU is reset to defaults
18881959
assert_fpu_reset(hyperlight_vm.vm.as_ref());
@@ -1933,7 +2004,8 @@ mod tests {
19332004
assert_eq!(debug_regs, expected_dirty);
19342005

19352006
// Reset vcpu
1936-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
2007+
hyperlight_vm.reset_vm_state().unwrap();
2008+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19372009

19382010
// Check debug registers are reset to default values
19392011
assert_debug_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1982,9 +2054,10 @@ mod tests {
19822054
assert_eq!(sregs, expected_dirty);
19832055

19842056
// Reset vcpu
1985-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
2057+
hyperlight_vm.reset_vm_state().unwrap();
2058+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19862059

1987-
// Check registers are reset to defaults (CR3 is 0 as passed to reset_vcpu)
2060+
// Check registers are reset to defaults
19882061
let sregs = hyperlight_vm.vm.sregs().unwrap();
19892062
let mut expected_reset = CommonSpecialRegisters::standard_64bit_defaults(0);
19902063
normalize_sregs_for_run_tests(&mut expected_reset, &sregs);
@@ -2020,7 +2093,11 @@ mod tests {
20202093
let root_pt_addr = ctx.ctx.vm.get_root_pt().unwrap();
20212094
let segment_state = ctx.ctx.vm.get_snapshot_sregs().unwrap();
20222095

2023-
ctx.ctx.vm.reset_vcpu(root_pt_addr, &segment_state).unwrap();
2096+
ctx.ctx.vm.reset_vm_state().unwrap();
2097+
ctx.ctx
2098+
.vm
2099+
.restore_sregs(root_pt_addr, &segment_state)
2100+
.unwrap();
20242101

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

0 commit comments

Comments
 (0)