Skip to content

Commit 0e43292

Browse files
committed
Use WHvResetPartition to clear guest-visible state on restore()
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent de25f36 commit 0e43292

7 files changed

Lines changed: 142 additions & 120 deletions

File tree

Justfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'c.just'
22

3-
set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"]
3+
set windows-shell := ["pwsh.exe", "-NoLogo", "-NoProfile", "-Command"]
44
set dotenv-load := true
55

66
set-env-command := if os() == "windows" { "$env:" } else { "export " }
@@ -315,6 +315,11 @@ clippy target=default-target: (witguest-wit)
315315
clippyw target=default-target: (witguest-wit)
316316
{{ cargo-cmd }} clippy --all-targets --all-features --target x86_64-pc-windows-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
317317

318+
# Cross-check for linux from a windows host using clippy (no linking needed).
319+
# Only checks lib targets to avoid dev-dependencies (criterion->alloca) that need a C cross-compiler.
320+
clippyl target=default-target:
321+
{{ cargo-cmd }} clippy --lib --all-features --target x86_64-unknown-linux-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
322+
318323
clippy-guests target=default-target: (witguest-wit) (ensure-cargo-hyperlight)
319324
cd src/tests/rust_guests/simpleguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
320325
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/x86_64.rs

Lines changed: 86 additions & 23 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)]
@@ -330,22 +330,29 @@ impl HyperlightVm {
330330
}
331331

332332
/// Resets the following vCPU state:
333-
/// - General purpose registers
334-
/// - Debug registers
335-
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
336-
/// - Special registers (restored from snapshot, with CR3 updated to new page table location)
333+
/// - On Windows: calls WHvResetPartition (resets all per-VP state including
334+
/// GP registers, debug registers, XSAVE, MSRs, APIC, etc.)
335+
/// - On Linux: explicitly resets GP registers, debug registers, and XSAVE
336+
/// - On all platforms: restores special registers from snapshot with CR3
337+
/// updated to new page table location
337338
// TODO: check if other state needs to be reset
338339
pub(crate) fn reset_vcpu(
339340
&mut self,
340341
cr3: u64,
341342
sregs: &CommonSpecialRegisters,
342343
) -> std::result::Result<(), RegisterError> {
343-
self.vm.set_regs(&CommonRegisters {
344-
rflags: 1 << 1, // Reserved bit always set
345-
..Default::default()
346-
})?;
347-
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
348-
self.vm.reset_xsave()?;
344+
#[cfg(target_os = "windows")]
345+
self.vm.reset_partition()?;
346+
347+
#[cfg(not(target_os = "windows"))]
348+
{
349+
self.vm.set_regs(&CommonRegisters {
350+
rflags: 1 << 1, // Reserved bit always set
351+
..Default::default()
352+
})?;
353+
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
354+
self.vm.reset_xsave()?;
355+
}
349356

350357
#[cfg(not(feature = "nanvix-unstable"))]
351358
{
@@ -880,7 +887,9 @@ mod tests {
880887
use super::*;
881888
#[cfg(kvm)]
882889
use crate::hypervisor::regs::FP_CONTROL_WORD_DEFAULT;
883-
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
890+
use crate::hypervisor::regs::{
891+
CommonDebugRegs, CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT,
892+
};
884893
use crate::hypervisor::virtual_machine::VirtualMachine;
885894
use crate::mem::layout::SandboxMemoryLayout;
886895
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
@@ -1184,9 +1193,18 @@ mod tests {
11841193
// Assertion Helpers - Verify vCPU state after reset
11851194
// ==========================================================================
11861195

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

12031221
/// Assert that general-purpose registers are in reset state.
1204-
/// After reset, all registers should be zeroed except rflags which has
1205-
/// reserved bit 1 always set.
1222+
///
1223+
/// On Linux (KVM/MSHV): reset_vcpu explicitly zeroes all GP regs and sets
1224+
/// rflags = 0x2, so we verify all-zeros.
1225+
///
1226+
/// On Windows: WHvResetPartition sets architectural power-on defaults
1227+
/// per Intel SDM Vol. 3, Table 10-1:
1228+
/// RIP = 0xFFF0 (reset vector)
1229+
/// RDX = CPUID signature (CPU-dependent stepping/model/family)
1230+
/// RFLAGS = 0x2 (only reserved bit 1 set)
1231+
/// All other GP regs = 0
1232+
/// These are overwritten by dispatch_call_from_host before guest execution,
1233+
/// but we still verify the power-on state is correct.
12061234
fn assert_regs_reset(vm: &dyn VirtualMachine) {
1235+
let regs = vm.regs().unwrap();
1236+
#[cfg(not(target_os = "windows"))]
12071237
assert_eq!(
1208-
vm.regs().unwrap(),
1238+
regs,
12091239
CommonRegisters {
1210-
rflags: 1 << 1, // Reserved bit 1 is always set
1240+
rflags: 1 << 1,
12111241
..Default::default()
12121242
}
12131243
);
1244+
#[cfg(target_os = "windows")]
1245+
{
1246+
// WHvResetPartition sets x86 power-on reset values
1247+
// (Intel SDM Vol. 3, Table 10-1)
1248+
let expected = CommonRegisters {
1249+
rip: 0xFFF0, // Reset vector
1250+
rdx: regs.rdx, // CPUID signature (CPU-dependent)
1251+
rflags: 0x2, // Reserved bit 1
1252+
..Default::default()
1253+
};
1254+
assert_ne!(
1255+
regs.rdx, 0x4444444444444444,
1256+
"RDX should not retain dirty value"
1257+
);
1258+
assert_eq!(regs, expected);
1259+
}
12141260
}
12151261

12161262
/// Assert that FPU state is in reset state.
1263+
///
1264+
/// On Linux (KVM/MSHV): reset_vcpu calls reset_xsave which zeroes FPU state
1265+
/// and sets FCW/MXCSR to defaults.
1266+
///
1267+
/// On Windows: WHvResetPartition resets to power-on defaults per
1268+
/// Intel SDM Vol. 3, Table 10-1 (FINIT-equivalent state):
1269+
/// FCW = 0x037F (all exceptions masked, precision=64-bit, round=nearest)
1270+
/// FSW = 0, FTW = 0 (all empty), FOP = 0, FIP = 0, FDP = 0
1271+
/// MXCSR = 0x1F80 (all SIMD exceptions masked, round=nearest)
1272+
/// ST0-ST7 = 0, XMM0-XMM15 = 0
1273+
///
12171274
/// Handles hypervisor-specific quirks (KVM MXCSR, empty FPU registers).
12181275
fn assert_fpu_reset(vm: &dyn VirtualMachine) {
12191276
let fpu = vm.fpu().unwrap();
@@ -1223,8 +1280,14 @@ mod tests {
12231280
assert_eq!(fpu, expected_fpu);
12241281
}
12251282

1226-
/// Assert that special registers are in reset state.
1227-
/// Handles hypervisor-specific differences in hidden descriptor cache fields.
1283+
/// Assert that special registers match the expected snapshot state.
1284+
///
1285+
/// After reset_vcpu, sregs are explicitly restored from the snapshot
1286+
/// (with CR3 updated). This verifies they match the expected 64-bit
1287+
/// long mode configuration from CommonSpecialRegisters::standard_64bit_defaults.
1288+
///
1289+
/// Handles hypervisor-specific differences in hidden descriptor cache fields
1290+
/// (unusable, granularity, type_ for unused segments).
12281291
fn assert_sregs_reset(vm: &dyn VirtualMachine, pml4_addr: u64) {
12291292
let defaults = CommonSpecialRegisters::standard_64bit_defaults(pml4_addr);
12301293
let sregs = vm.sregs().unwrap();

src/hyperlight_host/src/hypervisor/regs/x86_64/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod fpu;
1919
mod special_regs;
2020
mod standard_regs;
2121

22+
#[cfg(any(not(target_os = "windows"), gdb, test))]
2223
pub(crate) use debug_regs::*;
2324
pub(crate) use fpu::*;
2425
pub(crate) use special_regs::*;

src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ impl VirtualMachine for KvmVm {
210210
Ok(())
211211
}
212212

213+
#[cfg(any(gdb, test))]
213214
fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError> {
214215
// Note: On KVM this ignores MXCSR.
215216
// See https://github.com/torvalds/linux/blob/d358e5254674b70f34c847715ca509e46eb81e6f/arch/x86/kvm/x86.c#L12554-L12599
@@ -246,6 +247,7 @@ impl VirtualMachine for KvmVm {
246247
Ok(())
247248
}
248249

250+
#[cfg(any(gdb, test))]
249251
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError> {
250252
let kvm_debug_regs = self
251253
.vcpu_fd
@@ -262,7 +264,7 @@ impl VirtualMachine for KvmVm {
262264
Ok(())
263265
}
264266

265-
#[allow(dead_code)]
267+
#[cfg(any(crashdump, test))]
266268
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError> {
267269
let xsave = self
268270
.vcpu_fd

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ use tracing::{Span, instrument};
2121

2222
#[cfg(gdb)]
2323
use crate::hypervisor::gdb::DebugError;
24-
use crate::hypervisor::regs::{
25-
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
26-
};
24+
#[cfg(any(not(target_os = "windows"), gdb, test))]
25+
use crate::hypervisor::regs::CommonDebugRegs;
26+
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
2727
use crate::mem::memory_region::MemoryRegion;
2828
#[cfg(feature = "trace_guest")]
2929
use crate::sandbox::trace::TraceContext as SandboxTraceContext;
@@ -103,7 +103,7 @@ pub(crate) enum HypervisorType {
103103
/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
104104
/// Only used by MSHV and WHP which use compacted XSAVE format and need to
105105
/// validate buffer size before accessing XCOMP_BV.
106-
#[cfg(any(mshv3, target_os = "windows"))]
106+
#[cfg(mshv3)]
107107
pub(crate) const XSAVE_MIN_SIZE: usize = 576;
108108

109109
/// Standard XSAVE buffer size (4KB) used by KVM and MSHV.
@@ -240,6 +240,9 @@ pub enum RegisterError {
240240
#[cfg(target_os = "windows")]
241241
#[error("Failed to convert WHP registers: {0}")]
242242
ConversionFailed(String),
243+
#[cfg(target_os = "windows")]
244+
#[error("Failed to reset partition: {0}")]
245+
ResetPartition(HypervisorError),
243246
}
244247

245248
/// Map memory error
@@ -319,36 +322,48 @@ pub(crate) trait VirtualMachine: Debug + Send {
319322
) -> std::result::Result<VmExit, RunVcpuError>;
320323

321324
/// Get regs
322-
#[allow(dead_code)]
323325
fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
324326
/// Set regs
325327
fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
326328
/// Get fpu regs
327-
#[allow(dead_code)]
329+
#[cfg(any(gdb, test))]
328330
fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
329331
/// Set fpu regs
330332
fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
331333
/// Get special regs
332-
#[allow(dead_code)]
333334
fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
334335
/// Set special regs
335336
fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
336337
/// Get the debug registers of the vCPU
337-
#[allow(dead_code)]
338+
#[cfg(any(gdb, test))]
338339
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
339340
/// Set the debug registers of the vCPU
341+
#[cfg(any(not(target_os = "windows"), gdb, test))]
340342
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
341343

342344
/// Get xsave
343-
#[allow(dead_code)]
345+
#[cfg(any(crashdump, test))]
344346
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
345347
/// Reset xsave to default state
348+
#[cfg(not(target_os = "windows"))]
346349
fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
347350
/// Set xsave - only used for tests
348351
#[cfg(test)]
349352
#[cfg(not(feature = "nanvix-unstable"))]
350353
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
351354

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

src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ impl VirtualMachine for MshvVm {
238238
Ok(())
239239
}
240240

241+
#[cfg(any(gdb, test))]
241242
fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError> {
242243
let mshv_fpu = self
243244
.vcpu_fd
@@ -270,6 +271,7 @@ impl VirtualMachine for MshvVm {
270271
Ok(())
271272
}
272273

274+
#[cfg(any(gdb, test))]
273275
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError> {
274276
let debug_regs = self
275277
.vcpu_fd
@@ -286,7 +288,7 @@ impl VirtualMachine for MshvVm {
286288
Ok(())
287289
}
288290

289-
#[allow(dead_code)]
291+
#[cfg(any(crashdump, test))]
290292
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError> {
291293
let xsave = self
292294
.vcpu_fd

0 commit comments

Comments
 (0)