Skip to content

Commit 1e8d95a

Browse files
committed
Reset more vcpu state on snapshot restore
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 4caccbc commit 1e8d95a

File tree

10 files changed

+1391
-35
lines changed

10 files changed

+1391
-35
lines changed

.github/workflows/ValidatePullRequest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
- docs-pr
6868
- build-guests
6969
strategy:
70-
fail-fast: true
70+
fail-fast: false
7171
matrix:
7272
hypervisor: [hyperv, 'hyperv-ws2025', mshv3, kvm]
7373
cpu: [amd, intel]

src/hyperlight_host/src/hypervisor/hyperlight_vm.rs

Lines changed: 1085 additions & 1 deletion
Large diffs are not rendered by default.

src/hyperlight_host/src/hypervisor/regs/fpu.rs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,11 @@ pub(crate) struct CommonFpu {
3636
pub fcw: u16,
3737
pub fsw: u16,
3838
pub ftwx: u8,
39-
pub pad1: u8,
4039
pub last_opcode: u16,
4140
pub last_ip: u64,
4241
pub last_dp: u64,
4342
pub xmm: [[u8; 16]; 16],
4443
pub mxcsr: u32,
45-
pub pad2: u32,
4644
}
4745

4846
impl Default for CommonFpu {
@@ -52,13 +50,11 @@ impl Default for CommonFpu {
5250
fcw: FP_CONTROL_WORD_DEFAULT,
5351
fsw: 0,
5452
ftwx: 0,
55-
pad1: 0,
5653
last_opcode: 0,
5754
last_ip: 0,
5855
last_dp: 0,
5956
xmm: [[0u8; 16]; 16],
6057
mxcsr: MXCSR_DEFAULT,
61-
pad2: 0,
6258
}
6359
}
6460
}
@@ -71,13 +67,13 @@ impl From<&CommonFpu> for kvm_fpu {
7167
fcw: common_fpu.fcw,
7268
fsw: common_fpu.fsw,
7369
ftwx: common_fpu.ftwx,
74-
pad1: common_fpu.pad1,
70+
pad1: 0,
7571
last_opcode: common_fpu.last_opcode,
7672
last_ip: common_fpu.last_ip,
7773
last_dp: common_fpu.last_dp,
7874
xmm: common_fpu.xmm,
7975
mxcsr: common_fpu.mxcsr,
80-
pad2: common_fpu.pad2,
76+
pad2: 0,
8177
}
8278
}
8379
}
@@ -90,13 +86,13 @@ impl From<&CommonFpu> for FloatingPointUnit {
9086
fcw: common_fpu.fcw,
9187
fsw: common_fpu.fsw,
9288
ftwx: common_fpu.ftwx,
93-
pad1: common_fpu.pad1,
89+
pad1: 0,
9490
last_opcode: common_fpu.last_opcode,
9591
last_ip: common_fpu.last_ip,
9692
last_dp: common_fpu.last_dp,
9793
xmm: common_fpu.xmm,
9894
mxcsr: common_fpu.mxcsr,
99-
pad2: common_fpu.pad2,
95+
pad2: 0,
10096
}
10197
}
10298
}
@@ -109,13 +105,11 @@ impl From<&kvm_fpu> for CommonFpu {
109105
fcw: kvm_fpu.fcw,
110106
fsw: kvm_fpu.fsw,
111107
ftwx: kvm_fpu.ftwx,
112-
pad1: kvm_fpu.pad1,
113108
last_opcode: kvm_fpu.last_opcode,
114109
last_ip: kvm_fpu.last_ip,
115110
last_dp: kvm_fpu.last_dp,
116111
xmm: kvm_fpu.xmm,
117112
mxcsr: kvm_fpu.mxcsr,
118-
pad2: kvm_fpu.pad2,
119113
}
120114
}
121115
}
@@ -128,13 +122,11 @@ impl From<&FloatingPointUnit> for CommonFpu {
128122
fcw: mshv_fpu.fcw,
129123
fsw: mshv_fpu.fsw,
130124
ftwx: mshv_fpu.ftwx,
131-
pad1: mshv_fpu.pad1,
132125
last_opcode: mshv_fpu.last_opcode,
133126
last_ip: mshv_fpu.last_ip,
134127
last_dp: mshv_fpu.last_dp,
135128
xmm: mshv_fpu.xmm,
136129
mxcsr: mshv_fpu.mxcsr,
137-
pad2: mshv_fpu.pad2,
138130
}
139131
}
140132
}
@@ -174,7 +166,7 @@ impl From<&CommonFpu> for [(WHV_REGISTER_NAME, Align16<WHV_REGISTER_VALUE>); WHP
174166
FpControl: fpu.fcw,
175167
FpStatus: fpu.fsw,
176168
FpTag: fpu.ftwx,
177-
Reserved: fpu.pad1,
169+
Reserved: 0,
178170
LastFpOp: fpu.last_opcode,
179171
Anonymous: WHV_X64_FP_CONTROL_STATUS_REGISTER_0_0 {
180172
LastFpRip: fpu.last_ip,
@@ -293,7 +285,6 @@ impl TryFrom<&[(WHV_REGISTER_NAME, Align16<WHV_REGISTER_VALUE>)]> for CommonFpu
293285
fpu.fcw = control.FpControl;
294286
fpu.fsw = control.FpStatus;
295287
fpu.ftwx = control.FpTag;
296-
fpu.pad1 = control.Reserved;
297288
fpu.last_opcode = control.LastFpOp;
298289
fpu.last_ip = unsafe { control.Anonymous.LastFpRip };
299290
}
@@ -355,7 +346,6 @@ mod tests {
355346
fcw: 0x1234,
356347
fsw: 0x5678,
357348
ftwx: 0x9a,
358-
pad1: 0xbc,
359349
last_opcode: 0xdef0,
360350
last_ip: 0xdeadbeefcafebabe,
361351
last_dp: 0xabad1deaf00dbabe,
@@ -365,7 +355,6 @@ mod tests {
365355
[22u8; 16], [23u8; 16],
366356
],
367357
mxcsr: 0x1f80,
368-
pad2: 0,
369358
}
370359
}
371360

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

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@ use std::sync::LazyLock;
1818

1919
#[cfg(gdb)]
2020
use kvm_bindings::kvm_guest_debug;
21-
use kvm_bindings::{kvm_debugregs, kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region};
21+
use kvm_bindings::{
22+
kvm_debugregs, kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region, kvm_xsave,
23+
};
2224
use kvm_ioctls::Cap::UserMemory;
2325
use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
2426
use tracing::{Span, instrument};
2527

2628
#[cfg(gdb)]
2729
use crate::hypervisor::gdb::DebuggableVm;
2830
use crate::hypervisor::regs::{
29-
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
31+
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters, FP_CONTROL_WORD_DEFAULT,
32+
MXCSR_DEFAULT,
3033
};
3134
use crate::hypervisor::virtual_machine::{VirtualMachine, VmExit};
3235
use crate::mem::memory_region::MemoryRegion;
@@ -142,12 +145,16 @@ impl VirtualMachine for KvmVm {
142145
}
143146

144147
fn fpu(&self) -> Result<CommonFpu> {
148+
// Note: On KVM this ignores MXCSR.
149+
// See https://github.com/torvalds/linux/blob/d358e5254674b70f34c847715ca509e46eb81e6f/arch/x86/kvm/x86.c#L12554-L12599
145150
let kvm_fpu = self.vcpu_fd.get_fpu()?;
146151
Ok((&kvm_fpu).into())
147152
}
148153

149154
fn set_fpu(&self, fpu: &CommonFpu) -> Result<()> {
150155
let kvm_fpu: kvm_fpu = fpu.into();
156+
// Note: On KVM this ignores MXCSR.
157+
// See https://github.com/torvalds/linux/blob/d358e5254674b70f34c847715ca509e46eb81e6f/arch/x86/kvm/x86.c#L12554-L12599
151158
self.vcpu_fd.set_fpu(&kvm_fpu)?;
152159
Ok(())
153160
}
@@ -174,7 +181,6 @@ impl VirtualMachine for KvmVm {
174181
Ok(())
175182
}
176183

177-
#[cfg(crashdump)]
178184
fn xsave(&self) -> Result<Vec<u8>> {
179185
let xsave = self.vcpu_fd.get_xsave()?;
180186
Ok(xsave
@@ -183,6 +189,51 @@ impl VirtualMachine for KvmVm {
183189
.flat_map(u32::to_le_bytes)
184190
.collect())
185191
}
192+
193+
fn reset_xsave(&self) -> Result<()> {
194+
let mut xsave = kvm_xsave::default(); // default is zeroed 4KB buffer with no FAM
195+
196+
// XSAVE area layout from Intel SDM Vol. 1 Section 13.4.1:
197+
// - Bytes 0-1: FCW (x87 FPU Control Word)
198+
// - Bytes 24-27: MXCSR
199+
// - Bytes 512-519: XSTATE_BV (bitmap of valid state components)
200+
xsave.region[0] = FP_CONTROL_WORD_DEFAULT as u32;
201+
xsave.region[6] = MXCSR_DEFAULT;
202+
// XSTATE_BV = 0x3: bits 0,1 = x87 + SSE valid. This tells KVM to apply
203+
// the legacy region from this buffer. Without this, some KVM versions
204+
// may ignore set_xsave entirely when XSTATE_BV=0.
205+
xsave.region[128] = 0x3;
206+
207+
// SAFETY: No dynamic features enabled, 4KB is sufficient
208+
unsafe { self.vcpu_fd.set_xsave(&xsave)? };
209+
210+
Ok(())
211+
}
212+
213+
#[cfg(test)]
214+
#[cfg(feature = "init-paging")]
215+
fn set_xsave(&self, xsave: &[u32]) -> Result<()> {
216+
const KVM_XSAVE_SIZE: usize = 4096;
217+
218+
if std::mem::size_of_val(xsave) != KVM_XSAVE_SIZE {
219+
return Err(new_error!(
220+
"Provided xsave size {} does not match KVM supported size {}",
221+
std::mem::size_of_val(xsave),
222+
KVM_XSAVE_SIZE
223+
));
224+
}
225+
let xsave = kvm_xsave {
226+
region: xsave
227+
.try_into()
228+
.map_err(|_| new_error!("kvm xsave must be 1024 u32s"))?,
229+
..Default::default()
230+
};
231+
// Safety: Safe because we only copy 4096 bytes
232+
// and have not enabled any dynamic xsave features
233+
unsafe { self.vcpu_fd.set_xsave(&xsave)? };
234+
235+
Ok(())
236+
}
186237
}
187238

188239
#[cfg(gdb)]

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub fn is_hypervisor_present() -> bool {
8585
}
8686

8787
/// The hypervisor types available for the current platform
88-
#[derive(PartialEq, Eq, Debug)]
88+
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
8989
pub(crate) enum HypervisorType {
9090
#[cfg(kvm)]
9191
Kvm,
@@ -170,12 +170,17 @@ pub(crate) trait VirtualMachine: Debug + Send {
170170
#[allow(dead_code)]
171171
fn debug_regs(&self) -> Result<CommonDebugRegs>;
172172
/// Set the debug registers of the vCPU
173-
#[allow(dead_code)]
174173
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> Result<()>;
175174

176-
/// xsave
177-
#[cfg(crashdump)]
175+
/// Get xsave
176+
#[allow(dead_code)]
178177
fn xsave(&self) -> Result<Vec<u8>>;
178+
/// Reset xsave to default state
179+
fn reset_xsave(&self) -> Result<()>;
180+
/// Set xsave - only used for tests
181+
#[cfg(test)]
182+
#[cfg(feature = "init-paging")]
183+
fn set_xsave(&self, xsave: &[u32]) -> Result<()>;
179184

180185
/// Get partition handle
181186
#[cfg(target_os = "windows")]

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

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::sync::LazyLock;
2121
#[cfg(gdb)]
2222
use mshv_bindings::{DebugRegisters, hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT};
2323
use mshv_bindings::{
24-
FloatingPointUnit, SpecialRegisters, StandardRegisters, hv_message_type,
24+
FloatingPointUnit, SpecialRegisters, StandardRegisters, XSave, hv_message_type,
2525
hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA,
2626
hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT,
2727
hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES,
@@ -34,7 +34,8 @@ use tracing::{Span, instrument};
3434
#[cfg(gdb)]
3535
use crate::hypervisor::gdb::DebuggableVm;
3636
use crate::hypervisor::regs::{
37-
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
37+
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters, FP_CONTROL_WORD_DEFAULT,
38+
MXCSR_DEFAULT,
3839
};
3940
use crate::hypervisor::virtual_machine::{VirtualMachine, VmExit};
4041
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
@@ -222,11 +223,65 @@ impl VirtualMachine for MshvVm {
222223
Ok(())
223224
}
224225

225-
#[cfg(crashdump)]
226226
fn xsave(&self) -> Result<Vec<u8>> {
227227
let xsave = self.vcpu_fd.get_xsave()?;
228228
Ok(xsave.buffer.to_vec())
229229
}
230+
231+
fn reset_xsave(&self) -> Result<()> {
232+
let current_xsave = self.vcpu_fd.get_xsave()?;
233+
if current_xsave.buffer.len() < 576 {
234+
// Minimum: 512 legacy + 64 header
235+
return Err(new_error!(
236+
"Unexpected xsave length {}",
237+
current_xsave.buffer.len()
238+
));
239+
}
240+
241+
let mut buf = XSave::default(); // default is zeroed 4KB buffer
242+
243+
// Copy XCOMP_BV (offset 520-527) - preserves feature mask + compacted bit
244+
buf.buffer[520..528].copy_from_slice(&current_xsave.buffer[520..528]);
245+
246+
// XSAVE area layout from Intel SDM Vol. 1 Section 13.4.1:
247+
// - Bytes 0-1: FCW (x87 FPU Control Word)
248+
// - Bytes 24-27: MXCSR
249+
// - Bytes 512-519: XSTATE_BV (bitmap of valid state components)
250+
buf.buffer[0..2].copy_from_slice(&FP_CONTROL_WORD_DEFAULT.to_le_bytes());
251+
buf.buffer[24..28].copy_from_slice(&MXCSR_DEFAULT.to_le_bytes());
252+
// XSTATE_BV = 0x3: bits 0,1 = x87 + SSE valid. Explicitly tell hypervisor
253+
// to apply the legacy region from this buffer for consistent behavior.
254+
buf.buffer[512..520].copy_from_slice(&0x3u64.to_le_bytes());
255+
256+
self.vcpu_fd.set_xsave(&buf)?;
257+
Ok(())
258+
}
259+
260+
#[cfg(test)]
261+
#[cfg(feature = "init-paging")]
262+
fn set_xsave(&self, xsave: &[u32]) -> Result<()> {
263+
const MSHV_XSAVE_SIZE: usize = 4096;
264+
if std::mem::size_of_val(xsave) != MSHV_XSAVE_SIZE {
265+
return Err(new_error!(
266+
"Provided xsave size {} does not match MSHV supported size {}",
267+
std::mem::size_of_val(xsave),
268+
MSHV_XSAVE_SIZE
269+
));
270+
}
271+
272+
// Safety: all valid u32 values are 4 valid u8 values
273+
let (prefix, bytes, suffix) = unsafe { xsave.align_to() };
274+
if !prefix.is_empty() || !suffix.is_empty() {
275+
return Err(new_error!("Invalid xsave buffer alignment"));
276+
}
277+
let buf = XSave {
278+
buffer: bytes
279+
.try_into()
280+
.map_err(|_| new_error!("mshv xsave must be 4096 u8s"))?,
281+
};
282+
self.vcpu_fd.set_xsave(&buf)?;
283+
Ok(())
284+
}
230285
}
231286

232287
#[cfg(gdb)]

0 commit comments

Comments
 (0)