From ffeb934a2bc90a005b001fd3ba0686505585ceff Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:48:40 -0700 Subject: [PATCH] Unify register representation Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/gdb/hyperv_debug.rs | 119 +--- .../src/hypervisor/gdb/kvm_debug.rs | 100 +-- src/hyperlight_host/src/hypervisor/gdb/mod.rs | 44 +- .../src/hypervisor/gdb/mshv_debug.rs | 93 +-- .../src/hypervisor/gdb/x86_64_target.rs | 29 +- .../src/hypervisor/hyperv_linux.rs | 184 ++--- .../src/hypervisor/hyperv_windows.rs | 331 +++------ src/hyperlight_host/src/hypervisor/kvm.rs | 149 ++-- src/hyperlight_host/src/hypervisor/mod.rs | 110 ++- .../src/hypervisor/{fpu.rs => regs.rs} | 23 +- .../src/hypervisor/regs/fpu.rs | 438 ++++++++++++ .../src/hypervisor/regs/special_regs.rs | 667 ++++++++++++++++++ .../src/hypervisor/regs/standard_regs.rs | 423 +++++++++++ .../hypervisor/windows_hypervisor_platform.rs | 491 +++---------- .../src/hypervisor/wrappers.rs | 79 --- src/hyperlight_host/src/sandbox/outb.rs | 35 +- typos.toml | 1 + 17 files changed, 2087 insertions(+), 1229 deletions(-) rename src/hyperlight_host/src/hypervisor/{fpu.rs => regs.rs} (56%) create mode 100644 src/hyperlight_host/src/hypervisor/regs/fpu.rs create mode 100644 src/hyperlight_host/src/hypervisor/regs/special_regs.rs create mode 100644 src/hyperlight_host/src/hypervisor/regs/standard_regs.rs diff --git a/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs b/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs index c6528f8ab..f5b332d02 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs @@ -19,9 +19,10 @@ use std::collections::HashMap; use windows::Win32::System::Hypervisor::WHV_VP_EXCEPTION_CONTEXT; use super::arch::{MAX_NO_OF_HW_BP, vcpu_stop_reason}; -use super::{GuestDebug, SW_BP_SIZE, VcpuStopReason, X86_64Regs}; +use super::{GuestDebug, SW_BP_SIZE, VcpuStopReason}; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; use crate::hypervisor::windows_hypervisor_platform::VMProcessor; -use crate::hypervisor::wrappers::{WHvDebugRegisters, WHvGeneralRegisters}; +use crate::hypervisor::wrappers::WHvDebugRegisters; use crate::{HyperlightError, Result, new_error}; /// KVM Debug struct @@ -54,7 +55,7 @@ impl HypervDebug { /// Returns the instruction pointer from the stopped vCPU fn get_instruction_pointer(&self, vcpu_fd: &VMProcessor) -> Result { let regs = vcpu_fd - .get_regs() + .regs() .map_err(|e| new_error!("Could not retrieve registers from vCPU: {:?}", e))?; Ok(regs.rip) @@ -103,7 +104,7 @@ impl HypervDebug { self.single_step = step; let mut regs = vcpu_fd - .get_regs() + .regs() .map_err(|e| new_error!("Could not get registers: {:?}", e))?; // Set TF Flag to enable Traps @@ -114,7 +115,7 @@ impl HypervDebug { } vcpu_fd - .set_general_purpose_registers(®s) + .set_regs(®s) .map_err(|e| new_error!("Could not set guest registers: {:?}", e))?; Ok(()) @@ -185,45 +186,17 @@ impl GuestDebug for HypervDebug { self.sw_breakpoints.remove(addr) } - fn read_regs(&self, vcpu_fd: &Self::Vcpu, regs: &mut X86_64Regs) -> Result<()> { + fn read_regs(&self, vcpu_fd: &Self::Vcpu) -> Result<(CommonRegisters, CommonFpu)> { log::debug!("Read registers"); - let vcpu_regs = vcpu_fd - .get_regs() + let regs = vcpu_fd + .regs() .map_err(|e| new_error!("Could not read guest registers: {:?}", e))?; - regs.rax = vcpu_regs.rax; - regs.rbx = vcpu_regs.rbx; - regs.rcx = vcpu_regs.rcx; - regs.rdx = vcpu_regs.rdx; - regs.rsi = vcpu_regs.rsi; - regs.rdi = vcpu_regs.rdi; - regs.rbp = vcpu_regs.rbp; - regs.rsp = vcpu_regs.rsp; - regs.r8 = vcpu_regs.r8; - regs.r9 = vcpu_regs.r9; - regs.r10 = vcpu_regs.r10; - regs.r11 = vcpu_regs.r11; - regs.r12 = vcpu_regs.r12; - regs.r13 = vcpu_regs.r13; - regs.r14 = vcpu_regs.r14; - regs.r15 = vcpu_regs.r15; - - regs.rip = vcpu_regs.rip; - regs.rflags = vcpu_regs.rflags; - - // Fetch XMM from WHVP - if let Ok(fpu) = vcpu_fd.get_fpu() { - regs.xmm = [ - fpu.xmm0, fpu.xmm1, fpu.xmm2, fpu.xmm3, fpu.xmm4, fpu.xmm5, fpu.xmm6, fpu.xmm7, - fpu.xmm8, fpu.xmm9, fpu.xmm10, fpu.xmm11, fpu.xmm12, fpu.xmm13, fpu.xmm14, - fpu.xmm15, - ]; - regs.mxcsr = fpu.mxcsr; - } else { - log::warn!("Failed to read FPU/XMM via WHVP for debug registers"); - } + let fpu = vcpu_fd + .fpu() + .map_err(|e| new_error!("Could not read guest FPU registers: {:?}", e))?; - Ok(()) + Ok((regs, fpu)) } fn set_single_step(&mut self, vcpu_fd: &Self::Vcpu, enable: bool) -> Result<()> { @@ -236,62 +209,28 @@ impl GuestDebug for HypervDebug { .map_err(|_| HyperlightError::TranslateGuestAddress(gva)) } - fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> { + fn write_regs( + &self, + vcpu_fd: &Self::Vcpu, + regs: &CommonRegisters, + fpu: &CommonFpu, + ) -> Result<()> { log::debug!("Write registers"); - let gprs = WHvGeneralRegisters { - rax: regs.rax, - rbx: regs.rbx, - rcx: regs.rcx, - rdx: regs.rdx, - rsi: regs.rsi, - rdi: regs.rdi, - rbp: regs.rbp, - rsp: regs.rsp, - r8: regs.r8, - r9: regs.r9, - r10: regs.r10, - r11: regs.r11, - r12: regs.r12, - r13: regs.r13, - r14: regs.r14, - r15: regs.r15, - - rip: regs.rip, - rflags: regs.rflags, - }; vcpu_fd - .set_general_purpose_registers(&gprs) + .set_regs(regs) .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; - // Load existing FPU state, replace XMM and MXCSR, and write it back. - let mut fpu = match vcpu_fd.get_fpu() { - Ok(f) => f, - Err(e) => { - return Err(new_error!("Could not write guest registers: {:?}", e)); - } - }; - - fpu.xmm0 = regs.xmm[0]; - fpu.xmm1 = regs.xmm[1]; - fpu.xmm2 = regs.xmm[2]; - fpu.xmm3 = regs.xmm[3]; - fpu.xmm4 = regs.xmm[4]; - fpu.xmm5 = regs.xmm[5]; - fpu.xmm6 = regs.xmm[6]; - fpu.xmm7 = regs.xmm[7]; - fpu.xmm8 = regs.xmm[8]; - fpu.xmm9 = regs.xmm[9]; - fpu.xmm10 = regs.xmm[10]; - fpu.xmm11 = regs.xmm[11]; - fpu.xmm12 = regs.xmm[12]; - fpu.xmm13 = regs.xmm[13]; - fpu.xmm14 = regs.xmm[14]; - fpu.xmm15 = regs.xmm[15]; - fpu.mxcsr = regs.mxcsr; + // Only xmm and mxcsr is piped though in the given fpu, so only set those + let mut current_fpu: CommonFpu = vcpu_fd + .fpu() + .map_err(|e| new_error!("Could not read guest FPU registers: {:?}", e))?; + current_fpu.mxcsr = fpu.mxcsr; + current_fpu.xmm = fpu.xmm; vcpu_fd - .set_fpu(&fpu) - .map_err(|e| new_error!("Could not write guest registers: {:?}", e)) + .set_fpu(¤t_fpu) + .map_err(|e| new_error!("Could not write guest FPU registers: {:?}", e))?; + Ok(()) } } diff --git a/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs b/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs index fdd6943fa..ae5996d49 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs @@ -18,12 +18,13 @@ use std::collections::HashMap; use kvm_bindings::{ KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_GUESTDBG_USE_HW_BP, KVM_GUESTDBG_USE_SW_BP, - kvm_debug_exit_arch, kvm_guest_debug, kvm_regs, + kvm_debug_exit_arch, kvm_guest_debug, }; use kvm_ioctls::VcpuFd; use super::arch::{MAX_NO_OF_HW_BP, SW_BP_SIZE, vcpu_stop_reason}; -use super::{GuestDebug, VcpuStopReason, X86_64Regs}; +use super::{GuestDebug, VcpuStopReason}; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; use crate::{HyperlightError, Result, new_error}; /// KVM Debug struct @@ -167,57 +168,29 @@ impl GuestDebug for KvmDebug { self.sw_breakpoints.remove(addr) } - fn read_regs(&self, vcpu_fd: &Self::Vcpu, regs: &mut X86_64Regs) -> Result<()> { + fn read_regs(&self, vcpu_fd: &Self::Vcpu) -> Result<(CommonRegisters, CommonFpu)> { log::debug!("Read registers"); - let vcpu_regs = vcpu_fd + let regs = vcpu_fd .get_regs() .map_err(|e| new_error!("Could not read guest registers: {:?}", e))?; - regs.rax = vcpu_regs.rax; - regs.rbx = vcpu_regs.rbx; - regs.rcx = vcpu_regs.rcx; - regs.rdx = vcpu_regs.rdx; - regs.rsi = vcpu_regs.rsi; - regs.rdi = vcpu_regs.rdi; - regs.rbp = vcpu_regs.rbp; - regs.rsp = vcpu_regs.rsp; - regs.r8 = vcpu_regs.r8; - regs.r9 = vcpu_regs.r9; - regs.r10 = vcpu_regs.r10; - regs.r11 = vcpu_regs.r11; - regs.r12 = vcpu_regs.r12; - regs.r13 = vcpu_regs.r13; - regs.r14 = vcpu_regs.r14; - regs.r15 = vcpu_regs.r15; - - regs.rip = vcpu_regs.rip; - regs.rflags = vcpu_regs.rflags; - - // Read XMM registers from FPU state - // note kvm get_fpu doesn't actually set or read the mxcsr value - // https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kvm/x86.c#L12229 - match vcpu_fd.get_fpu() { - Ok(fpu) => { - // Convert KVM XMM registers ([u8; 16] x 16) to [u128; 16] - regs.xmm = fpu.xmm.map(u128::from_le_bytes); - } - Err(e) => { - log::warn!("Failed to read FPU state for XMM registers: {:?}", e); - } - } + let fpu_data = vcpu_fd + .get_fpu() + .map_err(|e| new_error!("Could not read guest FPU registers: {:?}", e))?; + let mut fpu: CommonFpu = CommonFpu::from(&fpu_data); // Read MXCSR from XSAVE (MXCSR is at byte offset 24 -> u32 index 6) // 11.5.10 Mode-Specific XSAVE/XRSTOR State Management match vcpu_fd.get_xsave() { Ok(xsave) => { - regs.mxcsr = xsave.region[6]; + fpu.mxcsr = xsave.region[6]; } Err(e) => { log::warn!("Failed to read XSAVE for MXCSR: {:?}", e); } } - Ok(()) + Ok((CommonRegisters::from(®s), fpu)) } fn set_single_step(&mut self, vcpu_fd: &Self::Vcpu, enable: bool) -> Result<()> { @@ -236,49 +209,30 @@ impl GuestDebug for KvmDebug { } } - fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> { + fn write_regs( + &self, + vcpu_fd: &Self::Vcpu, + regs: &CommonRegisters, + fpu: &CommonFpu, + ) -> Result<()> { log::debug!("Write registers"); - let new_regs = kvm_regs { - rax: regs.rax, - rbx: regs.rbx, - rcx: regs.rcx, - rdx: regs.rdx, - rsi: regs.rsi, - rdi: regs.rdi, - rbp: regs.rbp, - rsp: regs.rsp, - r8: regs.r8, - r9: regs.r9, - r10: regs.r10, - r11: regs.r11, - r12: regs.r12, - r13: regs.r13, - r14: regs.r14, - r15: regs.r15, - - rip: regs.rip, - rflags: regs.rflags, - }; + let new_regs = regs.into(); vcpu_fd .set_regs(&new_regs) .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; - // load existing values and replace the xmm registers - let mut fpu = match vcpu_fd.get_fpu() { - Ok(fpu) => fpu, - Err(e) => { - return Err(new_error!("Could not write guest registers: {:?}", e)); - } - }; - - // Convert XMM registers from [u128; 16] (our internal representation) - // to [[u8; 16]; 16] (KVM FPU representation) using little-endian byte order. - fpu.xmm = regs.xmm.map(u128::to_le_bytes); + // Only xmm and mxcsr is piped though in the given fpu, so only set those + let mut current_fpu: CommonFpu = (&vcpu_fd.get_fpu()?).into(); + current_fpu.mxcsr = fpu.mxcsr; + current_fpu.xmm = fpu.xmm; vcpu_fd - .set_fpu(&fpu) + .set_fpu(&(¤t_fpu).into()) .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; + // Read XMM registers from FPU state + // note kvm get_fpu doesn't actually set or read the mxcsr value + // https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kvm/x86.c#L12229 // Update MXCSR using XSAVE region entry 6 (MXCSR) if available. let mut xsave = match vcpu_fd.get_xsave() { Ok(xsave) => xsave, @@ -287,7 +241,7 @@ impl GuestDebug for KvmDebug { } }; - xsave.region[6] = regs.mxcsr; + xsave.region[6] = fpu.mxcsr; unsafe { vcpu_fd .set_xsave(&xsave) diff --git a/src/hyperlight_host/src/hypervisor/gdb/mod.rs b/src/hyperlight_host/src/hypervisor/gdb/mod.rs index e8daf5aa9..d6333e456 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mod.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mod.rs @@ -46,6 +46,7 @@ use thiserror::Error; use x86_64_target::HyperlightSandboxTarget; use super::InterruptHandle; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; @@ -88,31 +89,6 @@ impl From for TargetError { } } -/// Struct that contains the x86_64 core registers -#[derive(Debug, Default)] -pub(crate) struct X86_64Regs { - pub(crate) rax: u64, - pub(crate) rbx: u64, - pub(crate) rcx: u64, - pub(crate) rdx: u64, - pub(crate) rsi: u64, - pub(crate) rdi: u64, - pub(crate) rbp: u64, - pub(crate) rsp: u64, - pub(crate) r8: u64, - pub(crate) r9: u64, - pub(crate) r10: u64, - pub(crate) r11: u64, - pub(crate) r12: u64, - pub(crate) r13: u64, - pub(crate) r14: u64, - pub(crate) r15: u64, - pub(crate) rip: u64, - pub(crate) rflags: u64, - pub(crate) xmm: [u128; 16], - pub(crate) mxcsr: u32, -} - /// Defines the possible reasons for which a vCPU can be stopped when debugging #[derive(Debug)] pub enum VcpuStopReason { @@ -142,7 +118,7 @@ pub(crate) enum DebugMsg { RemoveSwBreakpoint(u64), Step, WriteAddr(u64, Vec), - WriteRegisters(Box), + WriteRegisters(Box<(CommonRegisters, CommonFpu)>), } /// Enumerates the possible responses that a hypervisor can provide to a debugger @@ -157,7 +133,7 @@ pub(crate) enum DebugResponse { NotAllowed, InterruptHandle(Arc), ReadAddr(Vec), - ReadRegisters(Box), + ReadRegisters(Box<(CommonRegisters, CommonFpu)>), RemoveHwBreakpoint(bool), RemoveSwBreakpoint(bool), Step, @@ -185,13 +161,18 @@ pub(super) trait GuestDebug { fn delete_sw_breakpoint_data(&mut self, addr: &u64) -> Option<[u8; 1]>; /// Read registers - fn read_regs(&self, vcpu_fd: &Self::Vcpu, regs: &mut X86_64Regs) -> crate::Result<()>; + fn read_regs(&self, vcpu_fd: &Self::Vcpu) -> crate::Result<(CommonRegisters, CommonFpu)>; /// Enables or disables stepping and sets the vCPU debug configuration fn set_single_step(&mut self, vcpu_fd: &Self::Vcpu, enable: bool) -> crate::Result<()>; /// Translates the guest address to physical address fn translate_gva(&self, vcpu_fd: &Self::Vcpu, gva: u64) -> crate::Result; /// Write registers - fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> crate::Result<()>; + fn write_regs( + &self, + vcpu_fd: &Self::Vcpu, + regs: &CommonRegisters, + fpu: &CommonFpu, + ) -> crate::Result<()>; /// Adds hardware breakpoint fn add_hw_breakpoint(&mut self, vcpu_fd: &Self::Vcpu, addr: u64) -> crate::Result<()> { @@ -451,7 +432,10 @@ mod tests { let res = gdb_conn.try_recv(); assert!(res.is_err()); - let res = hyp_conn.send(DebugResponse::ReadRegisters(Box::default())); + let res = hyp_conn.send(DebugResponse::ReadRegisters(Box::new(( + Default::default(), + Default::default(), + )))); assert!(res.is_ok()); let res = gdb_conn.recv(); diff --git a/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs b/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs index 540581426..92569a2fd 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs @@ -28,12 +28,12 @@ use std::collections::HashMap; use mshv_bindings::{ DebugRegisters, HV_TRANSLATE_GVA_VALIDATE_READ, HV_TRANSLATE_GVA_VALIDATE_WRITE, - StandardRegisters, }; use mshv_ioctls::VcpuFd; use super::arch::{MAX_NO_OF_HW_BP, SW_BP_SIZE, vcpu_stop_reason}; -use super::{GuestDebug, VcpuStopReason, X86_64Regs}; +use super::{GuestDebug, VcpuStopReason}; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; use crate::{HyperlightError, Result, new_error}; #[derive(Debug, Default)] @@ -194,45 +194,20 @@ impl GuestDebug for MshvDebug { self.sw_breakpoints.remove(addr) } - fn read_regs(&self, vcpu_fd: &Self::Vcpu, regs: &mut X86_64Regs) -> Result<()> { + fn read_regs(&self, vcpu_fd: &Self::Vcpu) -> Result<(CommonRegisters, CommonFpu)> { log::debug!("Read registers"); - let vcpu_regs = vcpu_fd + + let regs = vcpu_fd .get_regs() .map_err(|e| new_error!("Could not read guest registers: {:?}", e))?; + let regs = CommonRegisters::from(®s); - regs.rax = vcpu_regs.rax; - regs.rbx = vcpu_regs.rbx; - regs.rcx = vcpu_regs.rcx; - regs.rdx = vcpu_regs.rdx; - regs.rsi = vcpu_regs.rsi; - regs.rdi = vcpu_regs.rdi; - regs.rbp = vcpu_regs.rbp; - regs.rsp = vcpu_regs.rsp; - regs.r8 = vcpu_regs.r8; - regs.r9 = vcpu_regs.r9; - regs.r10 = vcpu_regs.r10; - regs.r11 = vcpu_regs.r11; - regs.r12 = vcpu_regs.r12; - regs.r13 = vcpu_regs.r13; - regs.r14 = vcpu_regs.r14; - regs.r15 = vcpu_regs.r15; - - regs.rip = vcpu_regs.rip; - regs.rflags = vcpu_regs.rflags; - - // Try to read XMM from the FPU state - match vcpu_fd.get_fpu() { - Ok(fpu) => { - // MSHV exposes XMM as [[u8; 16]; 16]. Convert to [u128; 16] - regs.xmm = fpu.xmm.map(u128::from_le_bytes); - regs.mxcsr = fpu.mxcsr; - } - Err(e) => { - log::warn!("Failed to read FPU state for XMM registers (MSHV): {:?}", e); - } - } + let fpu_data = vcpu_fd + .get_fpu() + .map_err(|e| new_error!("Could not read guest FPU registers: {:?}", e))?; + let fpu = CommonFpu::from(&fpu_data); - Ok(()) + Ok((regs, fpu)) } fn set_single_step(&mut self, vcpu_fd: &Self::Vcpu, enable: bool) -> Result<()> { @@ -248,47 +223,25 @@ impl GuestDebug for MshvDebug { Ok(addr) } - fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> { + fn write_regs( + &self, + vcpu_fd: &Self::Vcpu, + regs: &CommonRegisters, + fpu: &CommonFpu, + ) -> Result<()> { log::debug!("Write registers"); - let new_regs = StandardRegisters { - rax: regs.rax, - rbx: regs.rbx, - rcx: regs.rcx, - rdx: regs.rdx, - rsi: regs.rsi, - rdi: regs.rdi, - rbp: regs.rbp, - rsp: regs.rsp, - r8: regs.r8, - r9: regs.r9, - r10: regs.r10, - r11: regs.r11, - r12: regs.r12, - r13: regs.r13, - r14: regs.r14, - r15: regs.r15, - - rip: regs.rip, - rflags: regs.rflags, - }; vcpu_fd - .set_regs(&new_regs) + .set_regs(®s.into()) .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; - // Load existing FPU state, replace XMM and MXCSR, and write it back. - let mut fpu = match vcpu_fd.get_fpu() { - Ok(f) => f, - Err(e) => { - return Err(new_error!("Could not write guest registers: {:?}", e)); - } - }; - - fpu.xmm = regs.xmm.map(u128::to_le_bytes); - fpu.mxcsr = regs.mxcsr; + // Only xmm and mxcsr is piped though in the given fpu, so only set those + let mut current_fpu: CommonFpu = (&vcpu_fd.get_fpu()?).into(); + current_fpu.mxcsr = fpu.mxcsr; + current_fpu.xmm = fpu.xmm; vcpu_fd - .set_fpu(&fpu) + .set_fpu(&(¤t_fpu).into()) .map_err(|e| new_error!("Could not write guest registers: {:?}", e)) } } diff --git a/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs b/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs index 3a67d3017..7ba38fc48 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs @@ -31,8 +31,9 @@ use gdbstub::target::ext::section_offsets::{Offsets, SectionOffsets}; use gdbstub::target::{Target, TargetError, TargetResult}; use gdbstub_arch::x86::X86_64_SSE as GdbTargetArch; -use super::{DebugCommChannel, DebugMsg, DebugResponse, GdbTargetError, X86_64Regs}; +use super::{DebugCommChannel, DebugMsg, DebugResponse, GdbTargetError}; use crate::hypervisor::InterruptHandle; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; /// Gdbstub target used by the gdbstub crate to provide GDB protocol implementation pub(crate) struct HyperlightSandboxTarget { @@ -208,7 +209,8 @@ impl SingleThreadBase for HyperlightSandboxTarget { log::debug!("Read regs"); match self.send_command(DebugMsg::ReadRegisters)? { - DebugResponse::ReadRegisters(read_regs) => { + DebugResponse::ReadRegisters(boxed_regs) => { + let (read_regs, read_fpu) = boxed_regs.as_ref(); regs.regs[0] = read_regs.rax; regs.regs[1] = read_regs.rbp; regs.regs[2] = read_regs.rcx; @@ -227,8 +229,9 @@ impl SingleThreadBase for HyperlightSandboxTarget { regs.regs[15] = read_regs.r15; regs.rip = read_regs.rip; regs.eflags = read_regs.rflags as u32; - regs.xmm = read_regs.xmm; - regs.mxcsr = read_regs.mxcsr; + + regs.xmm = read_fpu.xmm.map(u128::from_le_bytes); + regs.mxcsr = read_fpu.mxcsr; Ok(()) } @@ -250,7 +253,7 @@ impl SingleThreadBase for HyperlightSandboxTarget { ) -> TargetResult<(), Self> { log::debug!("Write regs"); - let regs = X86_64Regs { + let common_regs = CommonRegisters { rax: regs.regs[0], rbx: regs.regs[1], rcx: regs.regs[2], @@ -269,11 +272,23 @@ impl SingleThreadBase for HyperlightSandboxTarget { r15: regs.regs[15], rip: regs.rip, rflags: u64::from(regs.eflags), - xmm: regs.xmm, + }; + + let mut xmm = [[0u8; 16]; 16]; + for (i, ®) in regs.xmm.iter().enumerate() { + xmm[i] = reg.to_le_bytes(); + } + + let common_fpu = CommonFpu { + xmm, mxcsr: regs.mxcsr, + ..Default::default() }; - match self.send_command(DebugMsg::WriteRegisters(Box::new(regs)))? { + match self.send_command(DebugMsg::WriteRegisters(Box::new(( + common_regs, + common_fpu, + ))))? { DebugResponse::WriteRegisters => Ok(()), DebugResponse::NotAllowed => { log::error!("Action not allowed at this time, crash might have occurred"); diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index 5a009823e..80ee0b16b 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -32,7 +32,7 @@ use log::{LevelFilter, error}; #[cfg(mshv2)] use mshv_bindings::hv_message; use mshv_bindings::{ - FloatingPointUnit, SegmentRegister, SpecialRegisters, StandardRegisters, hv_message_type, + FloatingPointUnit, SpecialRegisters, StandardRegisters, hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT, hv_register_assoc, hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_user_mem_region, @@ -48,32 +48,20 @@ use mshv_bindings::{ hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, hv_partition_synthetic_processor_features, }; -#[cfg(feature = "trace_guest")] -use mshv_bindings::{ - hv_register_name, hv_register_name_HV_X64_REGISTER_RAX, hv_register_name_HV_X64_REGISTER_RBP, - hv_register_name_HV_X64_REGISTER_RCX, hv_register_name_HV_X64_REGISTER_RSP, -}; use mshv_ioctls::{Mshv, VcpuFd, VmFd}; use tracing::{Span, instrument}; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; -#[cfg(feature = "trace_guest")] -use super::TraceRegister; -use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; #[cfg(gdb)] use super::gdb::{ DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, MshvDebug, VcpuStopReason, }; -#[cfg(feature = "init-paging")] -use super::{ - CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, - EFER_LMA, EFER_LME, EFER_NX, EFER_SCE, -}; use super::{HyperlightExit, Hypervisor, InterruptHandle, LinuxInterruptHandle, VirtualCPU}; #[cfg(gdb)] use crate::HyperlightError; use crate::hypervisor::get_memory_access_violation; +use crate::hypervisor::regs::CommonFpu; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; @@ -93,7 +81,7 @@ mod debug { use super::mshv_bindings::hv_x64_exception_intercept_message; use super::{HypervLinuxDriver, *}; - use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason, X86_64Regs}; + use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::{Result, new_error}; @@ -192,18 +180,14 @@ mod debug { Ok(DebugResponse::ReadAddr(data)) } - DebugMsg::ReadRegisters => { - let mut regs = X86_64Regs::default(); - - debug - .read_regs(&self.vcpu_fd, &mut regs) - .map_err(|e| { - log::error!("Failed to read registers: {:?}", e); + DebugMsg::ReadRegisters => debug + .read_regs(&self.vcpu_fd) + .map_err(|e| { + log::error!("Failed to read registers: {:?}", e); - e - }) - .map(|_| DebugResponse::ReadRegisters(Box::new(regs))) - } + e + }) + .map(|(regs, fpu)| DebugResponse::ReadRegisters(Box::new((regs, fpu)))), DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint( debug .remove_hw_breakpoint(&self.vcpu_fd, addr) @@ -244,14 +228,17 @@ mod debug { Ok(DebugResponse::WriteAddr) } - DebugMsg::WriteRegisters(regs) => debug - .write_regs(&self.vcpu_fd, ®s) - .map_err(|e| { - log::error!("Failed to write registers: {:?}", e); + DebugMsg::WriteRegisters(boxed_regs) => { + let (regs, fpu) = boxed_regs.as_ref(); + debug + .write_regs(&self.vcpu_fd, regs, fpu) + .map_err(|e| { + log::error!("Failed to write registers: {:?}", e); - e - }) - .map(|_| DebugResponse::WriteRegisters), + e + }) + .map(|_| DebugResponse::WriteRegisters) + } } } else { Err(new_error!("Debugging is not enabled")) @@ -370,7 +357,7 @@ impl HypervLinuxDriver { vm_fd }; - let mut vcpu_fd = vm_fd.create_vcpu(0)?; + let vcpu_fd = vm_fd.create_vcpu(0)?; #[cfg(gdb)] let (debug, gdb_conn) = if let Some(gdb_conn) = gdb_conn { @@ -414,8 +401,6 @@ impl HypervLinuxDriver { vm_fd.map_user_memory(mshv_region) })?; - Self::setup_initial_sregs(&mut vcpu_fd, pml4_ptr.absolute()?)?; - let interrupt_handle = Arc::new(LinuxInterruptHandle { running: AtomicU64::new(0), cancel_requested: AtomicBool::new(false), @@ -440,7 +425,6 @@ impl HypervLinuxDriver { dropped: AtomicBool::new(false), }); - #[allow(unused_mut)] let mut hv = Self { _mshv: mshv, page_size: 0, @@ -463,6 +447,8 @@ impl HypervLinuxDriver { trace_info, }; + hv.setup_initial_sregs(pml4_ptr.absolute()?)?; + // Send the interrupt handle to the GDB thread if debugging is enabled // This is used to allow the GDB thread to stop the vCPU #[cfg(gdb)] @@ -472,65 +458,6 @@ impl HypervLinuxDriver { Ok(hv) } - - #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] - fn setup_initial_sregs(vcpu: &mut VcpuFd, _pml4_addr: u64) -> Result<()> { - #[cfg(feature = "init-paging")] - let sregs = SpecialRegisters { - cr0: CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP, - cr4: CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT, - cr3: _pml4_addr, - efer: EFER_LME | EFER_LMA | EFER_SCE | EFER_NX, - cs: SegmentRegister { - type_: 11, - present: 1, - s: 1, - l: 1, - ..Default::default() - }, - tr: SegmentRegister { - limit: 65535, - type_: 11, - present: 1, - ..Default::default() - }, - ..Default::default() - }; - - #[cfg(not(feature = "init-paging"))] - let sregs = SpecialRegisters { - cs: SegmentRegister { - base: 0, - selector: 0, - limit: 0xFFFF, - type_: 11, - present: 1, - s: 1, - ..Default::default() - }, - ds: SegmentRegister { - base: 0, - selector: 0, - limit: 0xFFFF, - type_: 3, - present: 1, - s: 1, - ..Default::default() - }, - tr: SegmentRegister { - base: 0, - selector: 0, - limit: 0xFFFF, - type_: 11, - present: 1, - s: 0, - ..Default::default() - }, - ..Default::default() - }; - vcpu.set_sregs(&sregs)?; - Ok(()) - } } impl Debug for HypervLinuxDriver { @@ -563,19 +490,6 @@ impl Debug for HypervLinuxDriver { } } -#[cfg(feature = "trace_guest")] -impl From for hv_register_name { - fn from(r: TraceRegister) -> Self { - match r { - TraceRegister::RAX => hv_register_name_HV_X64_REGISTER_RAX, - TraceRegister::RCX => hv_register_name_HV_X64_REGISTER_RCX, - TraceRegister::RIP => hv_register_name_HV_X64_REGISTER_RIP, - TraceRegister::RSP => hv_register_name_HV_X64_REGISTER_RSP, - TraceRegister::RBP => hv_register_name_HV_X64_REGISTER_RBP, - } - } -} - impl Hypervisor for HypervLinuxDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn initialise( @@ -670,13 +584,7 @@ impl Hypervisor for HypervLinuxDriver { self.vcpu_fd.set_regs(®s)?; // reset fpu state - let fpu = FloatingPointUnit { - fcw: FP_CONTROL_WORD_DEFAULT, - ftwx: FP_TAG_WORD_DEFAULT, - mxcsr: MXCSR_DEFAULT, - ..Default::default() // zero out the rest - }; - self.vcpu_fd.set_fpu(&fpu)?; + self.set_fpu(&CommonFpu::default())?; // run VirtualCPU::run( @@ -950,6 +858,39 @@ impl Hypervisor for HypervLinuxDriver { Ok(result) } + fn regs(&self) -> Result { + let mshv_regs = self.vcpu_fd.get_regs()?; + Ok((&mshv_regs).into()) + } + + fn set_regs(&mut self, regs: &super::regs::CommonRegisters) -> Result<()> { + let mshv_regs: StandardRegisters = regs.into(); + self.vcpu_fd.set_regs(&mshv_regs)?; + Ok(()) + } + + fn fpu(&self) -> Result { + let mshv_fpu = self.vcpu_fd.get_fpu()?; + Ok((&mshv_fpu).into()) + } + + fn set_fpu(&mut self, fpu: &super::regs::CommonFpu) -> Result<()> { + let mshv_fpu: FloatingPointUnit = fpu.into(); + self.vcpu_fd.set_fpu(&mshv_fpu)?; + Ok(()) + } + + fn sregs(&self) -> Result { + let mshv_sregs = self.vcpu_fd.get_sregs()?; + Ok((&mshv_sregs).into()) + } + + fn set_sregs(&mut self, sregs: &super::regs::CommonSpecialRegisters) -> Result<()> { + let mshv_sregs: SpecialRegisters = sregs.into(); + self.vcpu_fd.set_sregs(&mshv_sregs)?; + Ok(()) + } + #[instrument(skip_all, parent = Span::current(), level = "Trace")] fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor { self as &mut dyn Hypervisor @@ -1159,17 +1100,6 @@ impl Hypervisor for HypervLinuxDriver { } } - #[cfg(feature = "trace_guest")] - fn read_trace_reg(&self, reg: TraceRegister) -> Result { - let mut assoc = [hv_register_assoc { - name: reg.into(), - ..Default::default() - }]; - self.vcpu_fd.get_reg(&mut assoc)?; - // safety: all registers that we currently support are 64-bit - unsafe { Ok(assoc[0].value.reg64) } - } - #[cfg(feature = "trace_guest")] fn trace_info_as_ref(&self) -> &TraceInfo { &self.trace_info diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index e9aa45703..490e1f72e 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -23,13 +23,8 @@ use std::sync::{Arc, Mutex}; use log::LevelFilter; use tracing::{Span, instrument}; use windows::Win32::System::Hypervisor::{ - WHV_MEMORY_ACCESS_TYPE, WHV_PARTITION_HANDLE, WHV_REGISTER_VALUE, WHV_RUN_VP_EXIT_CONTEXT, - WHV_RUN_VP_EXIT_REASON, WHV_X64_SEGMENT_REGISTER, WHV_X64_SEGMENT_REGISTER_0, - WHvCancelRunVirtualProcessor, WHvX64RegisterCs, -}; -#[cfg(feature = "init-paging")] -use windows::Win32::System::Hypervisor::{ - WHvX64RegisterCr0, WHvX64RegisterCr3, WHvX64RegisterCr4, WHvX64RegisterEfer, + WHV_MEMORY_ACCESS_TYPE, WHV_PARTITION_HANDLE, WHV_RUN_VP_EXIT_CONTEXT, WHV_RUN_VP_EXIT_REASON, + WHvCancelRunVirtualProcessor, }; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; @@ -41,22 +36,14 @@ use { crate::HyperlightError, }; -#[cfg(feature = "trace_guest")] -use super::TraceRegister; -use super::fpu::{FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; +use super::regs::CommonSpecialRegisters; use super::surrogate_process::SurrogateProcess; use super::surrogate_process_manager::*; use super::windows_hypervisor_platform::{VMPartition, VMProcessor}; -use super::wrappers::{HandleWrapper, WHvFPURegisters}; -#[cfg(feature = "init-paging")] -use super::{ - CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, - EFER_LMA, EFER_LME, EFER_NX, EFER_SCE, -}; +use super::wrappers::HandleWrapper; use super::{HyperlightExit, Hypervisor, InterruptHandle, VirtualCPU}; -use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT; use crate::hypervisor::get_memory_access_violation; -use crate::hypervisor::wrappers::WHvGeneralRegisters; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; @@ -77,7 +64,7 @@ mod debug { use super::{HypervWindowsDriver, *}; use crate::Result; - use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason, X86_64Regs}; + use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; @@ -175,18 +162,14 @@ mod debug { Ok(DebugResponse::ReadAddr(data)) } - DebugMsg::ReadRegisters => { - let mut regs = X86_64Regs::default(); - - debug - .read_regs(&self.processor, &mut regs) - .map_err(|e| { - log::error!("Failed to read registers: {:?}", e); + DebugMsg::ReadRegisters => debug + .read_regs(&self.processor) + .map_err(|e| { + log::error!("Failed to read registers: {:?}", e); - e - }) - .map(|_| DebugResponse::ReadRegisters(Box::new(regs))) - } + e + }) + .map(|(regs, fpu)| DebugResponse::ReadRegisters(Box::new((regs, fpu)))), DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint( debug .remove_hw_breakpoint(&self.processor, addr) @@ -227,14 +210,17 @@ mod debug { Ok(DebugResponse::WriteAddr) } - DebugMsg::WriteRegisters(regs) => debug - .write_regs(&self.processor, ®s) - .map_err(|e| { - log::error!("Failed to write registers: {:?}", e); + DebugMsg::WriteRegisters(boxed_regs) => { + let (regs, fpu) = boxed_regs.as_ref(); + debug + .write_regs(&self.processor, regs, fpu) + .map_err(|e| { + log::error!("Failed to write registers: {:?}", e); - e - }) - .map(|_| DebugResponse::WriteRegisters), + e + }) + .map(|_| DebugResponse::WriteRegisters) + } } } else { Err(new_error!("Debugging is not enabled")) @@ -328,8 +314,7 @@ impl HypervWindowsDriver { partition.map_gpa_range(&mem_regions, &surrogate_process)?; - let mut proc = VMProcessor::new(partition)?; - Self::setup_initial_sregs(&mut proc, pml4_address)?; + let proc = VMProcessor::new(partition)?; let partition_handle = proc.get_partition_hdl(); #[cfg(gdb)] @@ -351,17 +336,16 @@ impl HypervWindowsDriver { dropped: AtomicBool::new(false), }); - #[allow(unused_mut)] let mut hv = Self { processor: proc, _surrogate_process: surrogate_process, entrypoint, orig_rsp: GuestPtr::try_from(RawPtr::from(rsp))?, - sandbox_regions: mem_regions, - mmap_regions: Vec::new(), interrupt_handle: interrupt_handle.clone(), mem_mgr: None, host_funcs: None, + sandbox_regions: mem_regions, + mmap_regions: Vec::new(), #[cfg(gdb)] debug, #[cfg(gdb)] @@ -372,6 +356,8 @@ impl HypervWindowsDriver { trace_info, }; + hv.setup_initial_sregs(pml4_address)?; + // Send the interrupt handle to the GDB thread if debugging is enabled // This is used to allow the GDB thread to stop the vCPU #[cfg(gdb)] @@ -382,62 +368,6 @@ impl HypervWindowsDriver { Ok(hv) } - fn setup_initial_sregs(proc: &mut VMProcessor, _pml4_addr: u64) -> Result<()> { - #[cfg(feature = "init-paging")] - proc.set_registers(&[ - (WHvX64RegisterCr3, WHV_REGISTER_VALUE { Reg64: _pml4_addr }), - ( - WHvX64RegisterCr4, - WHV_REGISTER_VALUE { - Reg64: CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT, - }, - ), - ( - WHvX64RegisterCr0, - WHV_REGISTER_VALUE { - Reg64: CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP, - }, - ), - ( - WHvX64RegisterEfer, - WHV_REGISTER_VALUE { - Reg64: EFER_LME | EFER_LMA | EFER_SCE | EFER_NX, - }, - ), - ( - WHvX64RegisterCs, - WHV_REGISTER_VALUE { - Segment: WHV_X64_SEGMENT_REGISTER { - Anonymous: WHV_X64_SEGMENT_REGISTER_0 { - Attributes: 0b1011 | (1 << 4) | (1 << 7) | (1 << 13), // Type (11: Execute/Read, accessed) | L (64-bit mode) | P (present) | S (code segment) - }, - ..Default::default() // zero out the rest - }, - }, - ), - ])?; - - #[cfg(not(feature = "init-paging"))] - { - proc.set_registers(&[( - WHvX64RegisterCs, - WHV_REGISTER_VALUE { - Segment: WHV_X64_SEGMENT_REGISTER { - Base: 0, - Selector: 0, - Limit: 0xFFFF, - Anonymous: WHV_X64_SEGMENT_REGISTER_0 { - Attributes: 0b1011 | (1 << 4) | (1 << 7), // Type (11: Execute/Read, accessed) | S (code segment) | P (present) - }, - ..Default::default() - }, - }, - )])?; - } - - Ok(()) - } - #[inline] #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn get_exit_details(&self, exit_reason: WHV_RUN_VP_EXIT_REASON) -> Result { @@ -445,7 +375,7 @@ impl HypervWindowsDriver { error.push_str(&format!( "Did not receive a halt from Hypervisor as expected - Received {exit_reason:?}!\n" )); - error.push_str(&format!("Registers: \n{:#?}", self.processor.get_regs()?)); + error.push_str(&format!("Registers: \n{:#?}", self.processor.regs()?)); Ok(error) } } @@ -465,127 +395,14 @@ impl Debug for HypervWindowsDriver { } // Get the registers - - let regs = self.processor.get_regs(); - - if let Ok(regs) = regs { - { - fs.field("Registers", ®s); - } + if let Ok(regs) = self.processor.regs() { + fs.field("Registers", ®s); } // Get the special registers - - let special_regs = self.processor.get_sregs(); - if let Ok(special_regs) = special_regs { - fs.field("CR0", unsafe { &special_regs.cr0.Reg64 }); - fs.field("CR2", unsafe { &special_regs.cr2.Reg64 }); - fs.field("CR3", unsafe { &special_regs.cr3.Reg64 }); - fs.field("CR4", unsafe { &special_regs.cr4.Reg64 }); - fs.field("CR8", unsafe { &special_regs.cr8.Reg64 }); - fs.field("EFER", unsafe { &special_regs.efer.Reg64 }); - fs.field("APIC_BASE", unsafe { &special_regs.apic_base.Reg64 }); - - // Segment registers - fs.field( - "CS", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.cs.Segment.Base }, - unsafe { &special_regs.cs.Segment.Limit }, - unsafe { &special_regs.cs.Segment.Selector }, - unsafe { &special_regs.cs.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "DS", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.ds.Segment.Base }, - unsafe { &special_regs.ds.Segment.Limit }, - unsafe { &special_regs.ds.Segment.Selector }, - unsafe { &special_regs.ds.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "ES", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.es.Segment.Base }, - unsafe { &special_regs.es.Segment.Limit }, - unsafe { &special_regs.es.Segment.Selector }, - unsafe { &special_regs.es.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "FS", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.fs.Segment.Base }, - unsafe { &special_regs.fs.Segment.Limit }, - unsafe { &special_regs.fs.Segment.Selector }, - unsafe { &special_regs.fs.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "GS", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.gs.Segment.Base }, - unsafe { &special_regs.gs.Segment.Limit }, - unsafe { &special_regs.gs.Segment.Selector }, - unsafe { &special_regs.gs.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "SS", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.ss.Segment.Base }, - unsafe { &special_regs.ss.Segment.Limit }, - unsafe { &special_regs.ss.Segment.Selector }, - unsafe { &special_regs.ss.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "TR", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.tr.Segment.Base }, - unsafe { &special_regs.tr.Segment.Limit }, - unsafe { &special_regs.tr.Segment.Selector }, - unsafe { &special_regs.tr.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "LDTR", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Selector: {:?}, Attributes: {:?} }}", - unsafe { &special_regs.ldtr.Segment.Base }, - unsafe { &special_regs.ldtr.Segment.Limit }, - unsafe { &special_regs.ldtr.Segment.Selector }, - unsafe { &special_regs.ldtr.Segment.Anonymous.Attributes } - ), - ); - fs.field( - "GDTR", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Pad: {:?} }}", - unsafe { &special_regs.gdtr.Table.Base }, - unsafe { &special_regs.gdtr.Table.Limit }, - unsafe { &special_regs.gdtr.Table.Pad } - ), - ); - fs.field( - "IDTR", - &format_args!( - "{{ Base: {:?}, Limit: {:?}, Pad: {:?} }}", - unsafe { &special_regs.idtr.Table.Base }, - unsafe { &special_regs.idtr.Table.Limit }, - unsafe { &special_regs.idtr.Table.Pad } - ), - ); - }; + if let Ok(special_regs) = self.processor.sregs() { + fs.field("SpecialRegisters", &special_regs); + } fs.finish() } @@ -611,7 +428,7 @@ impl Hypervisor for HypervWindowsDriver { None => self.get_max_log_level().into(), }; - let regs = WHvGeneralRegisters { + let regs = CommonRegisters { rip: self.entrypoint, rsp: self.orig_rsp.absolute()?, @@ -624,7 +441,7 @@ impl Hypervisor for HypervWindowsDriver { ..Default::default() }; - self.processor.set_general_purpose_registers(®s)?; + self.set_regs(®s)?; VirtualCPU::run( self.as_mut_hypervisor(), @@ -654,21 +471,16 @@ impl Hypervisor for HypervWindowsDriver { #[cfg(gdb)] dbg_mem_access_hdl: Arc>>, ) -> Result<()> { // Reset general purpose registers, then set RIP and RSP - let regs = WHvGeneralRegisters { + let regs = CommonRegisters { rip: dispatch_func_addr.into(), rsp: self.orig_rsp.absolute()?, rflags: 1 << 1, // eflags bit index 1 is reserved and always needs to be 1 ..Default::default() }; - self.processor.set_general_purpose_registers(®s)?; + self.processor.set_regs(®s)?; // reset fpu state - self.processor.set_fpu(&WHvFPURegisters { - fp_control_word: FP_CONTROL_WORD_DEFAULT, - fp_tag_word: FP_TAG_WORD_DEFAULT, - mxcsr: MXCSR_DEFAULT, - ..Default::default() // zero out the rest - })?; + self.processor.set_fpu(&CommonFpu::default())?; VirtualCPU::run( self.as_mut_hypervisor(), @@ -728,9 +540,9 @@ impl Hypervisor for HypervWindowsDriver { handle_outb(mem_mgr, host_funcs, port, val)?; } - let mut regs = self.processor.get_regs()?; + let mut regs = self.regs()?; regs.rip = rip + instruction_length; - self.processor.set_general_purpose_registers(®s) + self.set_regs(®s) } #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] @@ -884,6 +696,35 @@ impl Hypervisor for HypervWindowsDriver { Ok(result) } + /// Get regs + #[allow(dead_code)] + fn regs(&self) -> Result { + self.processor.regs() + } + /// Set regs + fn set_regs(&mut self, regs: &CommonRegisters) -> Result<()> { + self.processor.set_regs(regs) + } + /// Get fpu regs + #[allow(dead_code)] + fn fpu(&self) -> Result { + self.processor.fpu() + } + /// Set fpu regs + fn set_fpu(&mut self, fpu: &CommonFpu) -> Result<()> { + self.processor.set_fpu(fpu) + } + /// Get special regs + #[allow(dead_code)] + fn sregs(&self) -> Result { + self.processor.sregs() + } + /// Set special regs + #[allow(dead_code)] + fn set_sregs(&mut self, sregs: &CommonSpecialRegisters) -> Result<()> { + self.processor.set_sregs(sregs) + } + fn interrupt_handle(&self) -> Arc { self.interrupt_handle.clone() } @@ -898,8 +739,8 @@ impl Hypervisor for HypervWindowsDriver { if self.rt_cfg.guest_core_dump { let mut regs = [0; 27]; - let vcpu_regs = self.processor.get_regs()?; - let sregs = self.processor.get_sregs()?; + let vcpu_regs = self.processor.regs()?; + let sregs = self.processor.sregs()?; let xsave = self.processor.get_xsave()?; // Set the registers in the order expected by the crashdump context @@ -920,16 +761,16 @@ impl Hypervisor for HypervWindowsDriver { regs[14] = vcpu_regs.rdi; // rdi regs[15] = 0; // orig rax regs[16] = vcpu_regs.rip; // rip - regs[17] = unsafe { sregs.cs.Segment.Selector } as u64; // cs + regs[17] = sregs.cs.selector as u64; // cs regs[18] = vcpu_regs.rflags; // eflags regs[19] = vcpu_regs.rsp; // rsp - regs[20] = unsafe { sregs.ss.Segment.Selector } as u64; // ss - regs[21] = unsafe { sregs.fs.Segment.Base }; // fs_base - regs[22] = unsafe { sregs.gs.Segment.Base }; // gs_base - regs[23] = unsafe { sregs.ds.Segment.Selector } as u64; // ds - regs[24] = unsafe { sregs.es.Segment.Selector } as u64; // es - regs[25] = unsafe { sregs.fs.Segment.Selector } as u64; // fs - regs[26] = unsafe { sregs.gs.Segment.Selector } as u64; // gs + regs[20] = sregs.ss.selector as u64; // ss + regs[21] = sregs.fs.base; // fs_base + regs[22] = sregs.gs.base; // gs_base + regs[23] = sregs.ds.selector as u64; // ds + regs[24] = sregs.es.selector as u64; // es + regs[25] = sregs.fs.selector as u64; // fs + regs[26] = sregs.gs.selector as u64; // gs // Get the filename from the config let filename = self.rt_cfg.binary_path.clone().and_then(|path| { @@ -1092,18 +933,6 @@ impl Hypervisor for HypervWindowsDriver { } } - #[cfg(feature = "trace_guest")] - fn read_trace_reg(&self, reg: TraceRegister) -> Result { - let regs = self.processor.get_regs()?; - match reg { - TraceRegister::RAX => Ok(regs.rax), - TraceRegister::RCX => Ok(regs.rcx), - TraceRegister::RIP => Ok(regs.rip), - TraceRegister::RSP => Ok(regs.rsp), - TraceRegister::RBP => Ok(regs.rbp), - } - } - #[cfg(feature = "trace_guest")] fn trace_info_as_ref(&self) -> &TraceInfo { &self.trace_info diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 36a7066b6..a42538b33 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -14,12 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -use std::convert::TryFrom; use std::fmt::Debug; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; -use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region}; +use kvm_bindings::{kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region}; use kvm_ioctls::Cap::UserMemory; use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd}; use log::LevelFilter; @@ -27,20 +26,13 @@ use tracing::{Span, instrument}; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; -#[cfg(feature = "trace_guest")] -use super::TraceRegister; -use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; #[cfg(gdb)] use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason}; -#[cfg(feature = "init-paging")] -use super::{ - CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, - EFER_LMA, EFER_LME, EFER_NX, EFER_SCE, -}; use super::{HyperlightExit, Hypervisor, InterruptHandle, LinuxInterruptHandle, VirtualCPU}; #[cfg(gdb)] use crate::HyperlightError; use crate::hypervisor::get_memory_access_violation; +use crate::hypervisor::regs::{CommonFpu, CommonRegisters}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; @@ -83,9 +75,7 @@ mod debug { use kvm_bindings::kvm_debug_exit_arch; use super::KVMDriver; - use crate::hypervisor::gdb::{ - DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason, X86_64Regs, - }; + use crate::hypervisor::gdb::{DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::{Result, new_error}; @@ -184,18 +174,14 @@ mod debug { Ok(DebugResponse::ReadAddr(data)) } - DebugMsg::ReadRegisters => { - let mut regs = X86_64Regs::default(); - - debug - .read_regs(&self.vcpu_fd, &mut regs) - .map_err(|e| { - log::error!("Failed to read registers: {:?}", e); + DebugMsg::ReadRegisters => debug + .read_regs(&self.vcpu_fd) + .map_err(|e| { + log::error!("Failed to read registers: {:?}", e); - e - }) - .map(|_| DebugResponse::ReadRegisters(Box::new(regs))) - } + e + }) + .map(|(regs, fpu)| DebugResponse::ReadRegisters(Box::new((regs, fpu)))), DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint( debug .remove_hw_breakpoint(&self.vcpu_fd, addr) @@ -236,14 +222,17 @@ mod debug { Ok(DebugResponse::WriteAddr) } - DebugMsg::WriteRegisters(regs) => debug - .write_regs(&self.vcpu_fd, ®s) - .map_err(|e| { - log::error!("Failed to write registers: {:?}", e); + DebugMsg::WriteRegisters(boxed_regs) => { + let (regs, fpu) = boxed_regs.as_ref(); + debug + .write_regs(&self.vcpu_fd, regs, fpu) + .map_err(|e| { + log::error!("Failed to write registers: {:?}", e); - e - }) - .map(|_| DebugResponse::WriteRegisters), + e + }) + .map(|_| DebugResponse::WriteRegisters) + } } } else { Err(new_error!("Debugging is not enabled")) @@ -337,8 +326,7 @@ impl KVMDriver { unsafe { vm_fd.set_user_memory_region(kvm_region) } })?; - let mut vcpu_fd = vm_fd.create_vcpu(0)?; - Self::setup_initial_sregs(&mut vcpu_fd, pml4_addr)?; + let vcpu_fd = vm_fd.create_vcpu(0)?; #[cfg(gdb)] let (debug, gdb_conn) = if let Some(gdb_conn) = gdb_conn { @@ -377,8 +365,7 @@ impl KVMDriver { sig_rt_min_offset: config.get_interrupt_vcpu_sigrtmin_offset(), }); - #[allow(unused_mut)] - let mut hv = Self { + let mut kvm = Self { _kvm: kvm, vm_fd, page_size: 0, @@ -402,34 +389,16 @@ impl KVMDriver { trace_info, }; + kvm.setup_initial_sregs(pml4_addr)?; + // Send the interrupt handle to the GDB thread if debugging is enabled // This is used to allow the GDB thread to stop the vCPU #[cfg(gdb)] - if hv.debug.is_some() { - hv.send_dbg_msg(DebugResponse::InterruptHandle(interrupt_handle))?; + if kvm.debug.is_some() { + kvm.send_dbg_msg(DebugResponse::InterruptHandle(interrupt_handle))?; } - Ok(hv) - } - - #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] - fn setup_initial_sregs(vcpu_fd: &mut VcpuFd, _pml4_addr: u64) -> Result<()> { - // setup paging and IA-32e (64-bit) mode - let mut sregs = vcpu_fd.get_sregs()?; - cfg_if::cfg_if! { - if #[cfg(feature = "init-paging")] { - sregs.cr3 = _pml4_addr; - sregs.cr4 = CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT; - sregs.cr0 = CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP; - sregs.efer = EFER_LME | EFER_LMA | EFER_SCE | EFER_NX; - sregs.cs.l = 1; // required for 64-bit mode - } else { - sregs.cs.base = 0; - sregs.cs.selector = 0; - } - } - vcpu_fd.set_sregs(&sregs)?; - Ok(()) + Ok(kvm) } } @@ -485,7 +454,7 @@ impl Hypervisor for KVMDriver { None => self.get_max_log_level().into(), }; - let regs = kvm_regs { + let regs = CommonRegisters { rip: self.entrypoint, rsp: self.orig_rsp.absolute()?, @@ -497,7 +466,7 @@ impl Hypervisor for KVMDriver { ..Default::default() }; - self.vcpu_fd.set_regs(®s)?; + self.set_regs(®s)?; VirtualCPU::run( self.as_mut_hypervisor(), @@ -572,24 +541,15 @@ impl Hypervisor for KVMDriver { #[cfg(gdb)] dbg_mem_access_fn: Arc>>, ) -> Result<()> { // Reset general purpose registers, then set RIP and RSP - let regs = kvm_regs { + let regs = CommonRegisters { rip: dispatch_func_addr.into(), rsp: self.orig_rsp.absolute()?, ..Default::default() }; - self.vcpu_fd.set_regs(®s)?; + self.set_regs(®s)?; // reset fpu state - let fpu = kvm_fpu { - fcw: FP_CONTROL_WORD_DEFAULT, - ftwx: FP_TAG_WORD_DEFAULT, - mxcsr: MXCSR_DEFAULT, - ..Default::default() // zero out the rest - }; - - // note kvm set_fpu doesn't actually set or read the mxcsr value - // https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kvm/x86.c#L12229 - self.vcpu_fd.set_fpu(&fpu)?; + self.set_fpu(&CommonFpu::default())?; // run VirtualCPU::run( @@ -824,6 +784,39 @@ impl Hypervisor for KVMDriver { Ok(result) } + fn regs(&self) -> Result { + let kvm_regs = self.vcpu_fd.get_regs()?; + Ok((&kvm_regs).into()) + } + + fn set_regs(&mut self, regs: &super::regs::CommonRegisters) -> Result<()> { + let kvm_regs: kvm_regs = regs.into(); + self.vcpu_fd.set_regs(&kvm_regs)?; + Ok(()) + } + + fn fpu(&self) -> Result { + let kvm_fpu = self.vcpu_fd.get_fpu()?; + Ok((&kvm_fpu).into()) + } + + fn set_fpu(&mut self, fpu: &super::regs::CommonFpu) -> Result<()> { + let kvm_fpu: kvm_fpu = fpu.into(); + self.vcpu_fd.set_fpu(&kvm_fpu)?; + Ok(()) + } + + fn sregs(&self) -> Result { + let kvm_sregs = self.vcpu_fd.get_sregs()?; + Ok((&kvm_sregs).into()) + } + + fn set_sregs(&mut self, sregs: &super::regs::CommonSpecialRegisters) -> Result<()> { + let kvm_sregs: kvm_sregs = sregs.into(); + self.vcpu_fd.set_sregs(&kvm_sregs)?; + Ok(()) + } + #[instrument(skip_all, parent = Span::current(), level = "Trace")] fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor { self as &mut dyn Hypervisor @@ -1039,18 +1032,6 @@ impl Hypervisor for KVMDriver { } } - #[cfg(feature = "trace_guest")] - fn read_trace_reg(&self, reg: TraceRegister) -> Result { - let regs = self.vcpu_fd.get_regs()?; - Ok(match reg { - TraceRegister::RAX => regs.rax, - TraceRegister::RCX => regs.rcx, - TraceRegister::RIP => regs.rip, - TraceRegister::RSP => regs.rsp, - TraceRegister::RBP => regs.rbp, - }) - } - #[cfg(feature = "trace_guest")] fn trace_info_as_ref(&self) -> &TraceInfo { &self.trace_info diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index 9c04ad3a0..781099134 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -19,16 +19,15 @@ use tracing::{Span, instrument}; use crate::HyperlightError::StackOverflow; use crate::error::HyperlightError::ExecutionCanceledByHost; +use crate::hypervisor::regs::{ + CommonFpu, CommonRegisters, CommonSegmentRegister, CommonSpecialRegisters, +}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::metrics::METRIC_GUEST_CANCELLATION; #[cfg(feature = "trace_guest")] use crate::sandbox::TraceInfo; use crate::{HyperlightError, Result, log_then_return}; -/// Util for handling x87 fpu state -#[cfg(any(kvm, mshv, target_os = "windows"))] -pub mod fpu; - /// HyperV-on-linux functionality #[cfg(mshv)] pub mod hyperv_linux; @@ -40,6 +39,9 @@ pub(crate) mod hyperv_windows; #[cfg(gdb)] pub(crate) mod gdb; +/// Abstracts over different hypervisor register representations +pub(crate) mod regs; + #[cfg(kvm)] /// Functionality to manipulate KVM-based virtual machines pub mod kvm; @@ -116,21 +118,6 @@ pub enum HyperlightExit { Retry(), } -/// Registers which may be useful for tracing/stack unwinding -#[cfg(feature = "trace_guest")] -pub enum TraceRegister { - /// RAX - RAX, - /// RCX - RCX, - /// RIP - RIP, - /// RSP - RSP, - /// RBP - RBP, -} - /// A common set of hypervisor functionality pub(crate) trait Hypervisor: Debug + Send { /// Initialise the internally stored vCPU with the given PEB address and @@ -189,6 +176,87 @@ pub(crate) trait Hypervisor: Debug + Send { /// Get InterruptHandle to underlying VM fn interrupt_handle(&self) -> Arc; + /// Get regs + #[allow(dead_code)] + fn regs(&self) -> Result; + /// Set regs + #[allow(dead_code)] + fn set_regs(&mut self, regs: &CommonRegisters) -> Result<()>; + /// Get fpu regs + #[allow(dead_code)] + fn fpu(&self) -> Result; + /// Set fpu regs + #[allow(dead_code)] + fn set_fpu(&mut self, fpu: &CommonFpu) -> Result<()>; + /// Get special regs + #[allow(dead_code)] + fn sregs(&self) -> Result; + /// Set special regs + #[allow(dead_code)] + fn set_sregs(&mut self, sregs: &CommonSpecialRegisters) -> Result<()>; + + /// Setup initial special registers for the hypervisor + /// This is a default implementation that works for all hypervisors + fn setup_initial_sregs(&mut self, _pml4_addr: u64) -> Result<()> { + #[cfg(feature = "init-paging")] + let sregs = CommonSpecialRegisters { + cr0: CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP, + cr4: CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT, + cr3: _pml4_addr, + efer: EFER_LME | EFER_LMA | EFER_SCE | EFER_NX, + cs: CommonSegmentRegister { + type_: 11, + present: 1, + s: 1, + l: 1, + ..Default::default() + }, + tr: CommonSegmentRegister { + limit: 65535, + type_: 11, + present: 1, + s: 0, + ..Default::default() + }, + ..Default::default() + }; + + #[cfg(not(feature = "init-paging"))] + let sregs = CommonSpecialRegisters { + cs: CommonSegmentRegister { + base: 0, + selector: 0, + limit: 0xFFFF, + type_: 11, + present: 1, + s: 1, + ..Default::default() + }, + ds: CommonSegmentRegister { + base: 0, + selector: 0, + limit: 0xFFFF, + type_: 3, + present: 1, + s: 1, + ..Default::default() + }, + tr: CommonSegmentRegister { + base: 0, + selector: 0, + limit: 0xFFFF, + type_: 11, + present: 1, + s: 0, + ..Default::default() + }, + ..Default::default() + }; + + self.set_sregs(&sregs)?; + Ok(()) + } + /// Get the logging level to pass to the guest entrypoint fn get_max_log_level(&self) -> u32 { // Check to see if the RUST_LOG environment variable is set @@ -245,10 +313,6 @@ pub(crate) trait Hypervisor: Debug + Send { /// Check stack guard to see if the stack is still valid fn check_stack_guard(&self) -> Result; - /// Read a register for trace/unwind purposes - #[cfg(feature = "trace_guest")] - fn read_trace_reg(&self, reg: TraceRegister) -> Result; - /// Get a reference of the trace info for the guest #[cfg(feature = "trace_guest")] fn trace_info_as_ref(&self) -> &TraceInfo; diff --git a/src/hyperlight_host/src/hypervisor/fpu.rs b/src/hyperlight_host/src/hypervisor/regs.rs similarity index 56% rename from src/hyperlight_host/src/hypervisor/fpu.rs rename to src/hyperlight_host/src/hypervisor/regs.rs index f0b6cb6a2..d29edf4bf 100644 --- a/src/hyperlight_host/src/hypervisor/fpu.rs +++ b/src/hyperlight_host/src/hypervisor/regs.rs @@ -14,6 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -pub(crate) const FP_CONTROL_WORD_DEFAULT: u16 = 0x37f; // mask all fp-exception, set rounding to nearest, set precision to 64-bit -pub(crate) const FP_TAG_WORD_DEFAULT: u8 = 0xff; // each 8 of x87 fpu registers is empty -pub(crate) const MXCSR_DEFAULT: u32 = 0x1f80; // mask simd fp-exceptions, clear exception flags, set rounding to nearest, disable flush-to-zero mode, disable denormals-are-zero mode +mod fpu; +mod special_regs; +mod standard_regs; + +#[cfg(target_os = "windows")] +use std::collections::HashSet; + +pub(crate) use fpu::*; +pub(crate) use special_regs::*; +pub(crate) use standard_regs::*; + +#[cfg(target_os = "windows")] +#[derive(Debug, PartialEq)] +pub(crate) enum FromWhpRegisterError { + MissingRegister(HashSet), + InvalidLength(usize), + InvalidEncoding, + DuplicateRegister(i32), + InvalidRegister(i32), +} diff --git a/src/hyperlight_host/src/hypervisor/regs/fpu.rs b/src/hyperlight_host/src/hypervisor/regs/fpu.rs new file mode 100644 index 000000000..0ccd080ab --- /dev/null +++ b/src/hyperlight_host/src/hypervisor/regs/fpu.rs @@ -0,0 +1,438 @@ +/* +Copyright 2024 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#[cfg(mshv2)] +extern crate mshv_bindings2 as mshv_bindings; +#[cfg(mshv2)] +extern crate mshv_ioctls2 as mshv_ioctls; + +#[cfg(mshv3)] +extern crate mshv_bindings3 as mshv_bindings; +#[cfg(mshv3)] +extern crate mshv_ioctls3 as mshv_ioctls; + +#[cfg(target_os = "windows")] +use std::collections::HashSet; + +#[cfg(kvm)] +use kvm_bindings::kvm_fpu; +#[cfg(mshv)] +use mshv_bindings::FloatingPointUnit; + +#[cfg(target_os = "windows")] +use super::Align16; +#[cfg(target_os = "windows")] +use crate::hypervisor::regs::FromWhpRegisterError; + +pub(crate) const FP_CONTROL_WORD_DEFAULT: u16 = 0x37f; // mask all fp-exception, set rounding to nearest, set precision to 64-bit +pub(crate) const MXCSR_DEFAULT: u32 = 0x1f80; // mask simd fp-exceptions, clear exception flags, set rounding to nearest, disable flush-to-zero mode, disable denormals-are-zero mode + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct CommonFpu { + pub fpr: [[u8; 16]; 8], + pub fcw: u16, + pub fsw: u16, + pub ftwx: u8, + pub pad1: u8, + pub last_opcode: u16, + pub last_ip: u64, + pub last_dp: u64, + pub xmm: [[u8; 16]; 16], + pub mxcsr: u32, + pub pad2: u32, +} + +impl Default for CommonFpu { + fn default() -> Self { + Self { + fpr: [[0u8; 16]; 8], + fcw: FP_CONTROL_WORD_DEFAULT, + fsw: 0, + ftwx: 0, + pad1: 0, + last_opcode: 0, + last_ip: 0, + last_dp: 0, + xmm: [[0u8; 16]; 16], + mxcsr: MXCSR_DEFAULT, + pad2: 0, + } + } +} + +#[cfg(kvm)] +impl From<&CommonFpu> for kvm_fpu { + fn from(common_fpu: &CommonFpu) -> Self { + kvm_fpu { + fpr: common_fpu.fpr, + fcw: common_fpu.fcw, + fsw: common_fpu.fsw, + ftwx: common_fpu.ftwx, + pad1: common_fpu.pad1, + last_opcode: common_fpu.last_opcode, + last_ip: common_fpu.last_ip, + last_dp: common_fpu.last_dp, + xmm: common_fpu.xmm, + mxcsr: common_fpu.mxcsr, + pad2: common_fpu.pad2, + } + } +} + +#[cfg(mshv)] +impl From<&CommonFpu> for FloatingPointUnit { + fn from(common_fpu: &CommonFpu) -> FloatingPointUnit { + FloatingPointUnit { + fpr: common_fpu.fpr, + fcw: common_fpu.fcw, + fsw: common_fpu.fsw, + ftwx: common_fpu.ftwx, + pad1: common_fpu.pad1, + last_opcode: common_fpu.last_opcode, + last_ip: common_fpu.last_ip, + last_dp: common_fpu.last_dp, + xmm: common_fpu.xmm, + mxcsr: common_fpu.mxcsr, + pad2: common_fpu.pad2, + } + } +} + +#[cfg(kvm)] +impl From<&kvm_fpu> for CommonFpu { + fn from(kvm_fpu: &kvm_fpu) -> Self { + Self { + fpr: kvm_fpu.fpr, + fcw: kvm_fpu.fcw, + fsw: kvm_fpu.fsw, + ftwx: kvm_fpu.ftwx, + pad1: kvm_fpu.pad1, + last_opcode: kvm_fpu.last_opcode, + last_ip: kvm_fpu.last_ip, + last_dp: kvm_fpu.last_dp, + xmm: kvm_fpu.xmm, + mxcsr: kvm_fpu.mxcsr, + pad2: kvm_fpu.pad2, + } + } +} + +#[cfg(mshv)] +impl From<&FloatingPointUnit> for CommonFpu { + fn from(mshv_fpu: &FloatingPointUnit) -> Self { + Self { + fpr: mshv_fpu.fpr, + fcw: mshv_fpu.fcw, + fsw: mshv_fpu.fsw, + ftwx: mshv_fpu.ftwx, + pad1: mshv_fpu.pad1, + last_opcode: mshv_fpu.last_opcode, + last_ip: mshv_fpu.last_ip, + last_dp: mshv_fpu.last_dp, + xmm: mshv_fpu.xmm, + mxcsr: mshv_fpu.mxcsr, + pad2: mshv_fpu.pad2, + } + } +} + +#[cfg(target_os = "windows")] +use windows::Win32::System::Hypervisor::*; + +#[cfg(target_os = "windows")] +impl From<&CommonFpu> for [(WHV_REGISTER_NAME, Align16); WHP_FPU_NAMES_LEN] { + fn from(fpu: &CommonFpu) -> Self { + let mut regs: [(WHV_REGISTER_NAME, Align16); WHP_FPU_NAMES_LEN] = + [Default::default(); WHP_FPU_NAMES_LEN]; + let mut idx = 0; + + // FPU/MMX registers (8 x 128-bit) + for (i, reg) in fpu.fpr.iter().enumerate() { + let mut value = WHV_REGISTER_VALUE::default(); + value.Reg128 = WHV_UINT128 { + Dword: [ + u32::from_le_bytes([reg[0], reg[1], reg[2], reg[3]]), + u32::from_le_bytes([reg[4], reg[5], reg[6], reg[7]]), + u32::from_le_bytes([reg[8], reg[9], reg[10], reg[11]]), + u32::from_le_bytes([reg[12], reg[13], reg[14], reg[15]]), + ], + }; + regs[idx] = ( + WHV_REGISTER_NAME(WHvX64RegisterFpMmx0.0 + i as i32), + Align16(value), + ); + idx += 1; + } + + // FpControlStatus + let mut fp_control_status = WHV_REGISTER_VALUE::default(); + fp_control_status.FpControlStatus = WHV_X64_FP_CONTROL_STATUS_REGISTER { + Anonymous: WHV_X64_FP_CONTROL_STATUS_REGISTER_0 { + FpControl: fpu.fcw, + FpStatus: fpu.fsw, + FpTag: fpu.ftwx, + Reserved: fpu.pad1, + LastFpOp: fpu.last_opcode, + Anonymous: WHV_X64_FP_CONTROL_STATUS_REGISTER_0_0 { + LastFpRip: fpu.last_ip, + }, + }, + }; + regs[idx] = (WHvX64RegisterFpControlStatus, Align16(fp_control_status)); + idx += 1; + + // XMM registers (16 x 128-bit) + for (i, reg) in fpu.xmm.iter().enumerate() { + let mut value = WHV_REGISTER_VALUE::default(); + value.Reg128 = WHV_UINT128 { + Dword: [ + u32::from_le_bytes([reg[0], reg[1], reg[2], reg[3]]), + u32::from_le_bytes([reg[4], reg[5], reg[6], reg[7]]), + u32::from_le_bytes([reg[8], reg[9], reg[10], reg[11]]), + u32::from_le_bytes([reg[12], reg[13], reg[14], reg[15]]), + ], + }; + regs[idx] = ( + WHV_REGISTER_NAME(WHvX64RegisterXmm0.0 + i as i32), + Align16(value), + ); + idx += 1; + } + + // XmmControlStatus + let mut xmm_control_status = WHV_REGISTER_VALUE::default(); + xmm_control_status.XmmControlStatus = WHV_X64_XMM_CONTROL_STATUS_REGISTER { + Anonymous: WHV_X64_XMM_CONTROL_STATUS_REGISTER_0 { + XmmStatusControl: fpu.mxcsr, + XmmStatusControlMask: !0, + Anonymous: WHV_X64_XMM_CONTROL_STATUS_REGISTER_0_0 { + LastFpRdp: fpu.last_dp, + }, + }, + }; + regs[idx] = (WHvX64RegisterXmmControlStatus, Align16(xmm_control_status)); + + regs + } +} + +#[cfg(target_os = "windows")] +pub(crate) const WHP_FPU_NAMES_LEN: usize = 26; +#[cfg(target_os = "windows")] +pub(crate) const WHP_FPU_NAMES: [WHV_REGISTER_NAME; WHP_FPU_NAMES_LEN] = [ + WHvX64RegisterFpMmx0, + WHvX64RegisterFpMmx1, + WHvX64RegisterFpMmx2, + WHvX64RegisterFpMmx3, + WHvX64RegisterFpMmx4, + WHvX64RegisterFpMmx5, + WHvX64RegisterFpMmx6, + WHvX64RegisterFpMmx7, + WHvX64RegisterFpControlStatus, + WHvX64RegisterXmm0, + WHvX64RegisterXmm1, + WHvX64RegisterXmm2, + WHvX64RegisterXmm3, + WHvX64RegisterXmm4, + WHvX64RegisterXmm5, + WHvX64RegisterXmm6, + WHvX64RegisterXmm7, + WHvX64RegisterXmm8, + WHvX64RegisterXmm9, + WHvX64RegisterXmm10, + WHvX64RegisterXmm11, + WHvX64RegisterXmm12, + WHvX64RegisterXmm13, + WHvX64RegisterXmm14, + WHvX64RegisterXmm15, + WHvX64RegisterXmmControlStatus, +]; + +#[cfg(target_os = "windows")] +impl TryFrom<&[(WHV_REGISTER_NAME, Align16)]> for CommonFpu { + type Error = FromWhpRegisterError; + + fn try_from( + regs: &[(WHV_REGISTER_NAME, Align16)], + ) -> Result { + if regs.len() != WHP_FPU_NAMES_LEN { + return Err(FromWhpRegisterError::InvalidLength(regs.len())); + } + + let mut fpu = CommonFpu::default(); + let mut seen_registers = HashSet::new(); + + for (name, value) in regs { + let name_id = name.0; + + // Check for duplicates + if !seen_registers.insert(name_id) { + return Err(FromWhpRegisterError::DuplicateRegister(name_id)); + } + + match name_id { + id if (WHvX64RegisterFpMmx0.0..WHvX64RegisterFpMmx0.0 + 8).contains(&id) => { + let idx = (id - WHvX64RegisterFpMmx0.0) as usize; + let dwords = unsafe { value.0.Reg128.Dword }; + fpu.fpr[idx] = [ + dwords[0].to_le_bytes(), + dwords[1].to_le_bytes(), + dwords[2].to_le_bytes(), + dwords[3].to_le_bytes(), + ] + .concat() + .try_into() + .map_err(|_| FromWhpRegisterError::InvalidEncoding)?; + } + + id if id == WHvX64RegisterFpControlStatus.0 => { + let control = unsafe { value.0.FpControlStatus.Anonymous }; + fpu.fcw = control.FpControl; + fpu.fsw = control.FpStatus; + fpu.ftwx = control.FpTag; + fpu.pad1 = control.Reserved; + fpu.last_opcode = control.LastFpOp; + fpu.last_ip = unsafe { control.Anonymous.LastFpRip }; + } + + id if (WHvX64RegisterXmm0.0..WHvX64RegisterXmm0.0 + 16).contains(&id) => { + let idx = (id - WHvX64RegisterXmm0.0) as usize; + let dwords = unsafe { value.0.Reg128.Dword }; + fpu.xmm[idx] = [ + dwords[0].to_le_bytes(), + dwords[1].to_le_bytes(), + dwords[2].to_le_bytes(), + dwords[3].to_le_bytes(), + ] + .concat() + .try_into() + .map_err(|_| FromWhpRegisterError::InvalidEncoding)?; + } + + id if id == WHvX64RegisterXmmControlStatus.0 => { + let control = unsafe { value.0.XmmControlStatus.Anonymous }; + fpu.mxcsr = control.XmmStatusControl; + fpu.last_dp = unsafe { control.Anonymous.LastFpRdp }; + } + + _ => { + return Err(FromWhpRegisterError::InvalidRegister(name_id)); + } + } + } + + // Set of all expected register names + let expected_registers: HashSet = WHP_FPU_NAMES.iter().map(|reg| reg.0).collect(); + + // Technically it should not be possible to have any missing registers at this point + // since we are guaranteed to have WHP_FPU_NAMES_LEN non-duplicate registers that have passed the match-arm above, but leaving this here for safety anyway + let missing: HashSet = expected_registers + .difference(&seen_registers) + .cloned() + .collect(); + + if !missing.is_empty() { + return Err(FromWhpRegisterError::MissingRegister(missing)); + } + + Ok(fpu) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_common_fpu() -> CommonFpu { + CommonFpu { + fpr: [ + [1u8; 16], [2u8; 16], [3u8; 16], [4u8; 16], [5u8; 16], [6u8; 16], [7u8; 16], + [8u8; 16], + ], + fcw: 0x1234, + fsw: 0x5678, + ftwx: 0x9a, + pad1: 0xbc, + last_opcode: 0xdef0, + last_ip: 0xdeadbeefcafebabe, + last_dp: 0xabad1deaf00dbabe, + xmm: [ + [8u8; 16], [9u8; 16], [10u8; 16], [11u8; 16], [12u8; 16], [13u8; 16], [14u8; 16], + [15u8; 16], [16u8; 16], [17u8; 16], [18u8; 16], [19u8; 16], [20u8; 16], [21u8; 16], + [22u8; 16], [23u8; 16], + ], + mxcsr: 0x1f80, + pad2: 0, + } + } + + #[cfg(kvm)] + #[test] + fn round_trip_kvm_fpu() { + use kvm_bindings::kvm_fpu; + + let original = sample_common_fpu(); + let kvm: kvm_fpu = (&original).into(); + let round_tripped = CommonFpu::from(&kvm); + + assert_eq!(original, round_tripped); + } + + #[cfg(mshv)] + #[test] + fn round_trip_mshv_fpu() { + use mshv_bindings::FloatingPointUnit; + + let original = sample_common_fpu(); + let mshv: FloatingPointUnit = (&original).into(); + let round_tripped = CommonFpu::from(&mshv); + + assert_eq!(original, round_tripped); + } + + #[cfg(target_os = "windows")] + #[test] + fn round_trip_windows_fpu() { + use windows::Win32::System::Hypervisor::*; + + let original = sample_common_fpu(); + let windows: [(WHV_REGISTER_NAME, Align16); WHP_FPU_NAMES_LEN] = + (&original).into(); + let round_tripped = CommonFpu::try_from(windows.as_ref()).unwrap(); + assert_eq!(original, round_tripped); + + // test for duplicate register error handling + let original = sample_common_fpu(); + let mut windows: [(WHV_REGISTER_NAME, Align16); WHP_FPU_NAMES_LEN] = + (&original).into(); + windows[0].0 = WHvX64RegisterFpMmx1; + let err = CommonFpu::try_from(windows.as_ref()).unwrap_err(); + assert_eq!( + err, + FromWhpRegisterError::DuplicateRegister(WHvX64RegisterFpMmx1.0) + ); + + // test for passing non-fpu register (e.g. RAX) + let original = sample_common_fpu(); + let mut windows: [(WHV_REGISTER_NAME, Align16); WHP_FPU_NAMES_LEN] = + (&original).into(); + windows[0] = (WHvX64RegisterRax, windows[0].1); + let err = CommonFpu::try_from(windows.as_ref()).unwrap_err(); + assert_eq!( + err, + FromWhpRegisterError::InvalidRegister(WHvX64RegisterRax.0) + ); + } +} diff --git a/src/hyperlight_host/src/hypervisor/regs/special_regs.rs b/src/hyperlight_host/src/hypervisor/regs/special_regs.rs new file mode 100644 index 000000000..b20291d16 --- /dev/null +++ b/src/hyperlight_host/src/hypervisor/regs/special_regs.rs @@ -0,0 +1,667 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#[cfg(mshv2)] +extern crate mshv_bindings2 as mshv_bindings; +#[cfg(mshv2)] +extern crate mshv_ioctls2 as mshv_ioctls; + +#[cfg(mshv3)] +extern crate mshv_bindings3 as mshv_bindings; +#[cfg(mshv3)] +extern crate mshv_ioctls3 as mshv_ioctls; + +#[cfg(target_os = "windows")] +use std::collections::HashSet; + +#[cfg(kvm)] +use kvm_bindings::{kvm_dtable, kvm_segment, kvm_sregs}; +#[cfg(mshv)] +use mshv_bindings::{SegmentRegister, SpecialRegisters, TableRegister}; +#[cfg(target_os = "windows")] +use windows::Win32::System::Hypervisor::*; + +#[cfg(target_os = "windows")] +use super::FromWhpRegisterError; + +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub(crate) struct CommonSpecialRegisters { + pub cs: CommonSegmentRegister, + pub ds: CommonSegmentRegister, + pub es: CommonSegmentRegister, + pub fs: CommonSegmentRegister, + pub gs: CommonSegmentRegister, + pub ss: CommonSegmentRegister, + pub tr: CommonSegmentRegister, + pub ldt: CommonSegmentRegister, + pub gdt: CommonTableRegister, + pub idt: CommonTableRegister, + pub cr0: u64, + pub cr2: u64, + pub cr3: u64, + pub cr4: u64, + pub cr8: u64, + pub efer: u64, + pub apic_base: u64, + pub interrupt_bitmap: [u64; 4], +} + +#[cfg(mshv)] +impl From<&SpecialRegisters> for CommonSpecialRegisters { + fn from(value: &SpecialRegisters) -> Self { + CommonSpecialRegisters { + cs: value.cs.into(), + ds: value.ds.into(), + es: value.es.into(), + fs: value.fs.into(), + gs: value.gs.into(), + ss: value.ss.into(), + tr: value.tr.into(), + ldt: value.ldt.into(), + gdt: value.gdt.into(), + idt: value.idt.into(), + cr0: value.cr0, + cr2: value.cr2, + cr3: value.cr3, + cr4: value.cr4, + cr8: value.cr8, + efer: value.efer, + apic_base: value.apic_base, + interrupt_bitmap: value.interrupt_bitmap, + } + } +} + +#[cfg(mshv)] +impl From<&CommonSpecialRegisters> for SpecialRegisters { + fn from(other: &CommonSpecialRegisters) -> Self { + SpecialRegisters { + cs: other.cs.into(), + ds: other.ds.into(), + es: other.es.into(), + fs: other.fs.into(), + gs: other.gs.into(), + ss: other.ss.into(), + tr: other.tr.into(), + ldt: other.ldt.into(), + gdt: other.gdt.into(), + idt: other.idt.into(), + cr0: other.cr0, + cr2: other.cr2, + cr3: other.cr3, + cr4: other.cr4, + cr8: other.cr8, + efer: other.efer, + apic_base: other.apic_base, + interrupt_bitmap: other.interrupt_bitmap, + } + } +} + +#[cfg(kvm)] +impl From<&kvm_sregs> for CommonSpecialRegisters { + fn from(kvm_sregs: &kvm_sregs) -> Self { + CommonSpecialRegisters { + cs: kvm_sregs.cs.into(), + ds: kvm_sregs.ds.into(), + es: kvm_sregs.es.into(), + fs: kvm_sregs.fs.into(), + gs: kvm_sregs.gs.into(), + ss: kvm_sregs.ss.into(), + tr: kvm_sregs.tr.into(), + ldt: kvm_sregs.ldt.into(), + gdt: kvm_sregs.gdt.into(), + idt: kvm_sregs.idt.into(), + cr0: kvm_sregs.cr0, + cr2: kvm_sregs.cr2, + cr3: kvm_sregs.cr3, + cr4: kvm_sregs.cr4, + cr8: kvm_sregs.cr8, + efer: kvm_sregs.efer, + apic_base: kvm_sregs.apic_base, + interrupt_bitmap: kvm_sregs.interrupt_bitmap, + } + } +} + +#[cfg(kvm)] +impl From<&CommonSpecialRegisters> for kvm_sregs { + fn from(common_sregs: &CommonSpecialRegisters) -> Self { + kvm_sregs { + cs: common_sregs.cs.into(), + ds: common_sregs.ds.into(), + es: common_sregs.es.into(), + fs: common_sregs.fs.into(), + gs: common_sregs.gs.into(), + ss: common_sregs.ss.into(), + tr: common_sregs.tr.into(), + ldt: common_sregs.ldt.into(), + gdt: common_sregs.gdt.into(), + idt: common_sregs.idt.into(), + cr0: common_sregs.cr0, + cr2: common_sregs.cr2, + cr3: common_sregs.cr3, + cr4: common_sregs.cr4, + cr8: common_sregs.cr8, + efer: common_sregs.efer, + apic_base: common_sregs.apic_base, + interrupt_bitmap: common_sregs.interrupt_bitmap, + } + } +} + +/// WHV_REGISTER_VALUE must be 16-byte aligned, but the rust struct is incorrectly generated +/// as 8-byte aligned. This is a workaround to ensure that the struct is 16-byte aligned. +#[cfg(target_os = "windows")] +#[repr(C, align(16))] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub(crate) struct Align16(pub(crate) T); + +#[cfg(target_os = "windows")] +#[allow(clippy::disallowed_macros)] // compile time +const _: () = { + assert!( + std::mem::size_of::>() + == std::mem::size_of::() + ); +}; + +#[cfg(target_os = "windows")] +pub(crate) const WHP_SREGS_NAMES_LEN: usize = 17; +#[cfg(target_os = "windows")] +pub(crate) static WHP_SREGS_NAMES: [WHV_REGISTER_NAME; WHP_SREGS_NAMES_LEN] = [ + WHvX64RegisterCs, + WHvX64RegisterDs, + WHvX64RegisterEs, + WHvX64RegisterFs, + WHvX64RegisterGs, + WHvX64RegisterSs, + WHvX64RegisterTr, + WHvX64RegisterLdtr, + WHvX64RegisterGdtr, + WHvX64RegisterIdtr, + WHvX64RegisterCr0, + WHvX64RegisterCr2, + WHvX64RegisterCr3, + WHvX64RegisterCr4, + WHvX64RegisterCr8, + WHvX64RegisterEfer, + WHvX64RegisterApicBase, +]; + +#[cfg(target_os = "windows")] +impl From<&CommonSpecialRegisters> + for [(WHV_REGISTER_NAME, Align16); WHP_SREGS_NAMES_LEN] +{ + fn from(other: &CommonSpecialRegisters) -> Self { + [ + (WHvX64RegisterCs, Align16(other.cs.into())), + (WHvX64RegisterDs, Align16(other.ds.into())), + (WHvX64RegisterEs, Align16(other.es.into())), + (WHvX64RegisterFs, Align16(other.fs.into())), + (WHvX64RegisterGs, Align16(other.gs.into())), + (WHvX64RegisterSs, Align16(other.ss.into())), + (WHvX64RegisterTr, Align16(other.tr.into())), + (WHvX64RegisterLdtr, Align16(other.ldt.into())), + (WHvX64RegisterGdtr, Align16(other.gdt.into())), + (WHvX64RegisterIdtr, Align16(other.idt.into())), + ( + WHvX64RegisterCr0, + Align16(WHV_REGISTER_VALUE { Reg64: other.cr0 }), + ), + ( + WHvX64RegisterCr2, + Align16(WHV_REGISTER_VALUE { Reg64: other.cr2 }), + ), + ( + WHvX64RegisterCr3, + Align16(WHV_REGISTER_VALUE { Reg64: other.cr3 }), + ), + ( + WHvX64RegisterCr4, + Align16(WHV_REGISTER_VALUE { Reg64: other.cr4 }), + ), + ( + WHvX64RegisterCr8, + Align16(WHV_REGISTER_VALUE { Reg64: other.cr8 }), + ), + ( + WHvX64RegisterEfer, + Align16(WHV_REGISTER_VALUE { Reg64: other.efer }), + ), + ( + WHvX64RegisterApicBase, + Align16(WHV_REGISTER_VALUE { + Reg64: other.apic_base, + }), + ), + ] + } +} + +#[cfg(target_os = "windows")] +impl TryFrom<&[(WHV_REGISTER_NAME, Align16)]> for CommonSpecialRegisters { + type Error = FromWhpRegisterError; + + #[expect( + non_upper_case_globals, + reason = "Windows API has lowercase register names" + )] + fn try_from( + regs: &[(WHV_REGISTER_NAME, Align16)], + ) -> Result { + if regs.len() != WHP_SREGS_NAMES_LEN { + return Err(FromWhpRegisterError::InvalidLength(regs.len())); + } + let mut registers = CommonSpecialRegisters::default(); + let mut seen_registers = HashSet::new(); + + for &(name, ref value) in regs { + let name_id = name.0; + + // Check for duplicates + if !seen_registers.insert(name_id) { + return Err(FromWhpRegisterError::DuplicateRegister(name_id)); + } + + unsafe { + match name { + WHvX64RegisterCs => registers.cs = value.0.into(), + WHvX64RegisterDs => registers.ds = value.0.into(), + WHvX64RegisterEs => registers.es = value.0.into(), + WHvX64RegisterFs => registers.fs = value.0.into(), + WHvX64RegisterGs => registers.gs = value.0.into(), + WHvX64RegisterSs => registers.ss = value.0.into(), + WHvX64RegisterTr => registers.tr = value.0.into(), + WHvX64RegisterLdtr => registers.ldt = value.0.into(), + WHvX64RegisterGdtr => registers.gdt = value.0.into(), + WHvX64RegisterIdtr => registers.idt = value.0.into(), + WHvX64RegisterCr0 => registers.cr0 = value.0.Reg64, + WHvX64RegisterCr2 => registers.cr2 = value.0.Reg64, + WHvX64RegisterCr3 => registers.cr3 = value.0.Reg64, + WHvX64RegisterCr4 => registers.cr4 = value.0.Reg64, + WHvX64RegisterCr8 => registers.cr8 = value.0.Reg64, + WHvX64RegisterEfer => registers.efer = value.0.Reg64, + WHvX64RegisterApicBase => registers.apic_base = value.0.Reg64, + _ => { + // Given unexpected register + return Err(FromWhpRegisterError::InvalidRegister(name_id)); + } + } + } + } + + // TODO: I'm not sure how to get this from WHP at the moment + registers.interrupt_bitmap = Default::default(); + + // Set of all expected register names + let expected_registers: HashSet = + WHP_SREGS_NAMES.map(|name| name.0).into_iter().collect(); + + // Technically it should not be possible to have any missing registers at this point + // since we are guaranteed to have WHP_SREGS_NAMES_LEN (17) non-duplicate registers that have passed the match-arm above, but leaving this here for safety anyway + let missing: HashSet<_> = expected_registers + .difference(&seen_registers) + .cloned() + .collect(); + + if !missing.is_empty() { + return Err(FromWhpRegisterError::MissingRegister(missing)); + } + + Ok(registers) + } +} + +// --- Segment Register --- + +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub(crate) struct CommonSegmentRegister { + pub base: u64, + pub limit: u32, + pub selector: u16, + pub type_: u8, + pub present: u8, + pub dpl: u8, + pub db: u8, + pub s: u8, + pub l: u8, + pub g: u8, + pub avl: u8, + pub unusable: u8, + pub padding: u8, +} + +#[cfg(mshv)] +impl From for CommonSegmentRegister { + fn from(other: SegmentRegister) -> Self { + CommonSegmentRegister { + base: other.base, + limit: other.limit, + selector: other.selector, + type_: other.type_, + present: other.present, + dpl: other.dpl, + db: other.db, + s: other.s, + l: other.l, + g: other.g, + avl: other.avl, + unusable: other.unusable, + padding: other.padding, + } + } +} + +#[cfg(mshv)] +impl From for SegmentRegister { + fn from(other: CommonSegmentRegister) -> Self { + SegmentRegister { + base: other.base, + limit: other.limit, + selector: other.selector, + type_: other.type_, + present: other.present, + dpl: other.dpl, + db: other.db, + s: other.s, + l: other.l, + g: other.g, + avl: other.avl, + unusable: other.unusable, + padding: other.padding, + } + } +} + +#[cfg(kvm)] +impl From for CommonSegmentRegister { + fn from(kvm_segment: kvm_segment) -> Self { + CommonSegmentRegister { + base: kvm_segment.base, + limit: kvm_segment.limit, + selector: kvm_segment.selector, + type_: kvm_segment.type_, + present: kvm_segment.present, + dpl: kvm_segment.dpl, + db: kvm_segment.db, + s: kvm_segment.s, + l: kvm_segment.l, + g: kvm_segment.g, + avl: kvm_segment.avl, + unusable: kvm_segment.unusable, + padding: kvm_segment.padding, + } + } +} + +#[cfg(kvm)] +impl From for kvm_segment { + fn from(common_segment: CommonSegmentRegister) -> Self { + kvm_segment { + base: common_segment.base, + limit: common_segment.limit, + selector: common_segment.selector, + type_: common_segment.type_, + present: common_segment.present, + dpl: common_segment.dpl, + db: common_segment.db, + s: common_segment.s, + l: common_segment.l, + g: common_segment.g, + avl: common_segment.avl, + unusable: common_segment.unusable, + padding: common_segment.padding, + } + } +} + +#[cfg(target_os = "windows")] +impl From for CommonSegmentRegister { + fn from(other: WHV_REGISTER_VALUE) -> Self { + unsafe { + let segment = other.Segment; + let bits = segment.Anonymous.Attributes; + + // Source of bit layout: https://learn.microsoft.com/en-us/virtualization/api/hypervisor-platform/funcs/whvvirtualprocessordatatypes + CommonSegmentRegister { + base: segment.Base, + limit: segment.Limit, + selector: segment.Selector, + type_: (bits & 0b1111) as u8, // bits 0–3: SegmentType + s: ((bits >> 4) & 0b1) as u8, // bit 4: NonSystemSegment + dpl: ((bits >> 5) & 0b11) as u8, // bits 5–6: DPL + present: ((bits >> 7) & 0b1) as u8, // bit 7: Present + // bits 8–11: Reserved + avl: ((bits >> 12) & 0b1) as u8, // bit 12: Available + l: ((bits >> 13) & 0b1) as u8, // bit 13: Long mode + db: ((bits >> 14) & 0b1) as u8, // bit 14: Default + g: ((bits >> 15) & 0b1) as u8, // bit 15: Granularity + unusable: 0, + padding: 0, + } + } + } +} + +#[cfg(target_os = "windows")] +impl From for WHV_REGISTER_VALUE { + fn from(other: CommonSegmentRegister) -> Self { + // Truncate each field to its valid bit width before composing `Attributes`. + let type_ = other.type_ & 0xF; // 4 bits + let s = other.s & 0x1; // 1 bit + let dpl = other.dpl & 0x3; // 2 bits + let present = other.present & 0x1; // 1 bit + let avl = other.avl & 0x1; // 1 bit + let l = other.l & 0x1; // 1 bit + let db = other.db & 0x1; // 1 bit + let g = other.g & 0x1; // 1 bit + + WHV_REGISTER_VALUE { + Segment: WHV_X64_SEGMENT_REGISTER { + Base: other.base, + Limit: other.limit, + Selector: other.selector, + Anonymous: WHV_X64_SEGMENT_REGISTER_0 { + Attributes: (type_ as u16) // bit 0-3 + | ((s as u16) << 4) // bit 4 + | ((dpl as u16) << 5) // bit 5-6 + | ((present as u16) << 7) // bit 7 + | ((avl as u16) << 12) // bit 12 + | ((l as u16) << 13) // bit 13 + | ((db as u16) << 14) // bit 14 + | ((g as u16) << 15), // bit 15 + }, + }, + } + } +} + +// --- Table Register --- + +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub(crate) struct CommonTableRegister { + pub base: u64, + pub limit: u16, +} + +#[cfg(mshv)] +impl From for CommonTableRegister { + fn from(other: TableRegister) -> Self { + CommonTableRegister { + base: other.base, + limit: other.limit, + } + } +} + +#[cfg(mshv)] +impl From for TableRegister { + fn from(other: CommonTableRegister) -> Self { + TableRegister { + base: other.base, + limit: other.limit, + } + } +} + +#[cfg(kvm)] +impl From for CommonTableRegister { + fn from(kvm_dtable: kvm_dtable) -> Self { + CommonTableRegister { + base: kvm_dtable.base, + limit: kvm_dtable.limit, + } + } +} + +#[cfg(kvm)] +impl From for kvm_dtable { + fn from(common_dtable: CommonTableRegister) -> Self { + kvm_dtable { + base: common_dtable.base, + limit: common_dtable.limit, + padding: Default::default(), + } + } +} + +#[cfg(target_os = "windows")] +impl From for CommonTableRegister { + fn from(other: WHV_REGISTER_VALUE) -> Self { + unsafe { + let table = other.Table; + CommonTableRegister { + base: table.Base, + limit: table.Limit, + } + } + } +} + +#[cfg(target_os = "windows")] +impl From for WHV_REGISTER_VALUE { + fn from(other: CommonTableRegister) -> Self { + WHV_REGISTER_VALUE { + Table: WHV_X64_TABLE_REGISTER { + Base: other.base, + Limit: other.limit, + Pad: Default::default(), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_common_special_registers() -> CommonSpecialRegisters { + let sample_segment = CommonSegmentRegister { + base: 0x1000, + limit: 0xFFFF, + selector: 0x10, + type_: 0xB, + present: 1, + dpl: 0, + db: 1, + s: 1, + l: 0, + g: 1, + avl: 0, + unusable: 0, + padding: 0, + }; + + let sample_table = CommonTableRegister { + base: 0x2000, + limit: 0x1000, + }; + + CommonSpecialRegisters { + cs: sample_segment, + ds: sample_segment, + es: sample_segment, + fs: sample_segment, + gs: sample_segment, + ss: sample_segment, + tr: sample_segment, + ldt: sample_segment, + gdt: sample_table, + idt: sample_table, + cr0: 0xDEAD_BEEF, + cr2: 0xBAD_C0DE, + cr3: 0xC0FFEE, + cr4: 0xFACE_CAFE, + cr8: 0x1234, + efer: 0x5678, + apic_base: 0x9ABC, + interrupt_bitmap: [0; 4], + } + } + + #[cfg(kvm)] + #[test] + fn round_trip_kvm_sregs() { + let original = sample_common_special_registers(); + let kvm_sregs: kvm_sregs = (&original).into(); + let roundtrip = CommonSpecialRegisters::from(&kvm_sregs); + + assert_eq!(original, roundtrip); + } + + #[cfg(mshv)] + #[test] + fn round_trip_mshv_sregs() { + let original = sample_common_special_registers(); + let mshv_sregs: SpecialRegisters = (&original).into(); + let roundtrip = CommonSpecialRegisters::from(&mshv_sregs); + + assert_eq!(original, roundtrip); + } + + #[cfg(target_os = "windows")] + #[test] + fn round_trip_whp_sregs() { + let original = sample_common_special_registers(); + let whp_sregs: [(WHV_REGISTER_NAME, Align16); WHP_SREGS_NAMES_LEN] = + (&original).into(); + let roundtrip = CommonSpecialRegisters::try_from(whp_sregs.as_ref()).unwrap(); + assert_eq!(original, roundtrip); + + // Test duplicate register error + let original = sample_common_special_registers(); + let mut whp_sregs: [(WHV_REGISTER_NAME, Align16); WHP_SREGS_NAMES_LEN] = + (&original).into(); + whp_sregs[0].0 = WHvX64RegisterDs; + let err = CommonSpecialRegisters::try_from(whp_sregs.as_ref()).unwrap_err(); + assert_eq!( + err, + FromWhpRegisterError::DuplicateRegister(WHvX64RegisterDs.0) + ); + + // Test passing non-sregs register (e.g. RIP) + let original = sample_common_special_registers(); + let mut whp_sregs: [(WHV_REGISTER_NAME, Align16); WHP_SREGS_NAMES_LEN] = + (&original).into(); + whp_sregs[0].0 = WHvX64RegisterRip; + let err = CommonSpecialRegisters::try_from(whp_sregs.as_ref()).unwrap_err(); + assert_eq!( + err, + FromWhpRegisterError::InvalidRegister(WHvX64RegisterRip.0) + ); + } +} diff --git a/src/hyperlight_host/src/hypervisor/regs/standard_regs.rs b/src/hyperlight_host/src/hypervisor/regs/standard_regs.rs new file mode 100644 index 000000000..cc39b3247 --- /dev/null +++ b/src/hyperlight_host/src/hypervisor/regs/standard_regs.rs @@ -0,0 +1,423 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#[cfg(mshv2)] +extern crate mshv_bindings2 as mshv_bindings; +#[cfg(mshv2)] +extern crate mshv_ioctls2 as mshv_ioctls; + +#[cfg(mshv3)] +extern crate mshv_bindings3 as mshv_bindings; +#[cfg(mshv3)] +extern crate mshv_ioctls3 as mshv_ioctls; + +#[cfg(kvm)] +use kvm_bindings::kvm_regs; +#[cfg(mshv)] +use mshv_bindings::StandardRegisters; + +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub(crate) struct CommonRegisters { + pub rax: u64, + pub rbx: u64, + pub rcx: u64, + pub rdx: u64, + pub rsi: u64, + pub rdi: u64, + pub rsp: u64, + pub rbp: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + pub rip: u64, + pub rflags: u64, +} + +// --- KVM --- +#[cfg(kvm)] +impl From<&kvm_regs> for CommonRegisters { + fn from(kvm_regs: &kvm_regs) -> Self { + CommonRegisters { + rax: kvm_regs.rax, + rbx: kvm_regs.rbx, + rcx: kvm_regs.rcx, + rdx: kvm_regs.rdx, + rsi: kvm_regs.rsi, + rdi: kvm_regs.rdi, + rsp: kvm_regs.rsp, + rbp: kvm_regs.rbp, + r8: kvm_regs.r8, + r9: kvm_regs.r9, + r10: kvm_regs.r10, + r11: kvm_regs.r11, + r12: kvm_regs.r12, + r13: kvm_regs.r13, + r14: kvm_regs.r14, + r15: kvm_regs.r15, + rip: kvm_regs.rip, + rflags: kvm_regs.rflags, + } + } +} + +#[cfg(kvm)] +impl From<&CommonRegisters> for kvm_regs { + fn from(regs: &CommonRegisters) -> Self { + kvm_regs { + rax: regs.rax, + rbx: regs.rbx, + rcx: regs.rcx, + rdx: regs.rdx, + rsi: regs.rsi, + rdi: regs.rdi, + rsp: regs.rsp, + rbp: regs.rbp, + r8: regs.r8, + r9: regs.r9, + r10: regs.r10, + r11: regs.r11, + r12: regs.r12, + r13: regs.r13, + r14: regs.r14, + r15: regs.r15, + rip: regs.rip, + rflags: regs.rflags, + } + } +} + +// --- MSHV --- + +#[cfg(mshv)] +impl From<&StandardRegisters> for CommonRegisters { + fn from(mshv_regs: &StandardRegisters) -> Self { + CommonRegisters { + rax: mshv_regs.rax, + rbx: mshv_regs.rbx, + rcx: mshv_regs.rcx, + rdx: mshv_regs.rdx, + rsi: mshv_regs.rsi, + rdi: mshv_regs.rdi, + rsp: mshv_regs.rsp, + rbp: mshv_regs.rbp, + r8: mshv_regs.r8, + r9: mshv_regs.r9, + r10: mshv_regs.r10, + r11: mshv_regs.r11, + r12: mshv_regs.r12, + r13: mshv_regs.r13, + r14: mshv_regs.r14, + r15: mshv_regs.r15, + rip: mshv_regs.rip, + rflags: mshv_regs.rflags, + } + } +} + +#[cfg(mshv)] +impl From<&CommonRegisters> for StandardRegisters { + fn from(regs: &CommonRegisters) -> Self { + StandardRegisters { + rax: regs.rax, + rbx: regs.rbx, + rcx: regs.rcx, + rdx: regs.rdx, + rsi: regs.rsi, + rdi: regs.rdi, + rsp: regs.rsp, + rbp: regs.rbp, + r8: regs.r8, + r9: regs.r9, + r10: regs.r10, + r11: regs.r11, + r12: regs.r12, + r13: regs.r13, + r14: regs.r14, + r15: regs.r15, + rip: regs.rip, + rflags: regs.rflags, + } + } +} + +#[cfg(target_os = "windows")] +use windows::Win32::System::Hypervisor::*; + +#[cfg(target_os = "windows")] +impl From<&CommonRegisters> + for [(WHV_REGISTER_NAME, Align16); WHP_REGS_NAMES_LEN] +{ + fn from(regs: &CommonRegisters) -> Self { + [ + ( + WHvX64RegisterRax, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rax }), + ), + ( + WHvX64RegisterRbx, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rbx }), + ), + ( + WHvX64RegisterRcx, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rcx }), + ), + ( + WHvX64RegisterRdx, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rdx }), + ), + ( + WHvX64RegisterRsi, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rsi }), + ), + ( + WHvX64RegisterRdi, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rdi }), + ), + ( + WHvX64RegisterRsp, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rsp }), + ), + ( + WHvX64RegisterRbp, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rbp }), + ), + ( + WHvX64RegisterR8, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r8 }), + ), + ( + WHvX64RegisterR9, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r9 }), + ), + ( + WHvX64RegisterR10, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r10 }), + ), + ( + WHvX64RegisterR11, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r11 }), + ), + ( + WHvX64RegisterR12, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r12 }), + ), + ( + WHvX64RegisterR13, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r13 }), + ), + ( + WHvX64RegisterR14, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r14 }), + ), + ( + WHvX64RegisterR15, + Align16(WHV_REGISTER_VALUE { Reg64: regs.r15 }), + ), + ( + WHvX64RegisterRip, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rip }), + ), + ( + WHvX64RegisterRflags, + Align16(WHV_REGISTER_VALUE { Reg64: regs.rflags }), + ), + ] + } +} + +#[cfg(target_os = "windows")] +use std::collections::HashSet; + +#[cfg(target_os = "windows")] +use super::{Align16, FromWhpRegisterError}; + +#[cfg(target_os = "windows")] +pub(crate) const WHP_REGS_NAMES_LEN: usize = 18; +#[cfg(target_os = "windows")] +pub(crate) const WHP_REGS_NAMES: [WHV_REGISTER_NAME; WHP_REGS_NAMES_LEN] = [ + WHvX64RegisterRax, + WHvX64RegisterRbx, + WHvX64RegisterRcx, + WHvX64RegisterRdx, + WHvX64RegisterRsi, + WHvX64RegisterRdi, + WHvX64RegisterRsp, + WHvX64RegisterRbp, + WHvX64RegisterR8, + WHvX64RegisterR9, + WHvX64RegisterR10, + WHvX64RegisterR11, + WHvX64RegisterR12, + WHvX64RegisterR13, + WHvX64RegisterR14, + WHvX64RegisterR15, + WHvX64RegisterRip, + WHvX64RegisterRflags, +]; + +#[cfg(target_os = "windows")] +impl TryFrom<&[(WHV_REGISTER_NAME, Align16)]> for CommonRegisters { + type Error = FromWhpRegisterError; + + #[expect( + non_upper_case_globals, + reason = "Windows API has lowercase register names" + )] + fn try_from( + regs: &[(WHV_REGISTER_NAME, Align16)], + ) -> Result { + if regs.len() != WHP_REGS_NAMES_LEN { + return Err(FromWhpRegisterError::InvalidLength(regs.len())); + } + let mut registers = CommonRegisters::default(); + let mut seen_registers = HashSet::new(); + + for &(name, value) in regs { + let name_id = name.0; + + // Check for duplicates + if !seen_registers.insert(name_id) { + return Err(FromWhpRegisterError::DuplicateRegister(name_id)); + } + + unsafe { + match name { + WHvX64RegisterRax => registers.rax = value.0.Reg64, + WHvX64RegisterRbx => registers.rbx = value.0.Reg64, + WHvX64RegisterRcx => registers.rcx = value.0.Reg64, + WHvX64RegisterRdx => registers.rdx = value.0.Reg64, + WHvX64RegisterRsi => registers.rsi = value.0.Reg64, + WHvX64RegisterRdi => registers.rdi = value.0.Reg64, + WHvX64RegisterRsp => registers.rsp = value.0.Reg64, + WHvX64RegisterRbp => registers.rbp = value.0.Reg64, + WHvX64RegisterR8 => registers.r8 = value.0.Reg64, + WHvX64RegisterR9 => registers.r9 = value.0.Reg64, + WHvX64RegisterR10 => registers.r10 = value.0.Reg64, + WHvX64RegisterR11 => registers.r11 = value.0.Reg64, + WHvX64RegisterR12 => registers.r12 = value.0.Reg64, + WHvX64RegisterR13 => registers.r13 = value.0.Reg64, + WHvX64RegisterR14 => registers.r14 = value.0.Reg64, + WHvX64RegisterR15 => registers.r15 = value.0.Reg64, + WHvX64RegisterRip => registers.rip = value.0.Reg64, + WHvX64RegisterRflags => registers.rflags = value.0.Reg64, + _ => { + // Given unexpected register + return Err(FromWhpRegisterError::InvalidRegister(name_id)); + } + } + } + } + + // Set of all expected register names + let expected_registers: HashSet = + WHP_REGS_NAMES.map(|name| name.0).into_iter().collect(); + + // Technically it should not be possible to have any missing registers at this point + // since we are guaranteed to have WHP_REGS_NAMES_LEN (18) non-duplicate registers that have passed the match-arm above, but leaving this here for safety anyway + let missing: HashSet<_> = expected_registers + .difference(&seen_registers) + .cloned() + .collect(); + + if !missing.is_empty() { + return Err(FromWhpRegisterError::MissingRegister(missing)); + } + + Ok(registers) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn common_regs() -> CommonRegisters { + CommonRegisters { + rax: 1, + rbx: 2, + rcx: 3, + rdx: 4, + rsi: 5, + rdi: 6, + rsp: 7, + rbp: 8, + r8: 9, + r9: 10, + r10: 11, + r11: 12, + r12: 13, + r13: 14, + r14: 15, + r15: 16, + rip: 17, + rflags: 18, + } + } + #[cfg(kvm)] + #[test] + fn round_trip_kvm_regs() { + let original = common_regs(); + let kvm_regs: kvm_regs = (&original).into(); + let converted: CommonRegisters = (&kvm_regs).into(); + assert_eq!(original, converted); + } + + #[cfg(mshv)] + #[test] + fn round_trip_mshv_regs() { + let original = common_regs(); + let mshv_regs: StandardRegisters = (&original).into(); + let converted: CommonRegisters = (&mshv_regs).into(); + assert_eq!(original, converted); + } + + #[cfg(target_os = "windows")] + #[test] + fn round_trip_whp_regs() { + let original = common_regs(); + let whp_regs: [(WHV_REGISTER_NAME, Align16); WHP_REGS_NAMES_LEN] = + (&original).into(); + let converted: CommonRegisters = whp_regs.as_ref().try_into().unwrap(); + assert_eq!(original, converted); + + // test for duplicate register error handling + let original = common_regs(); + let mut whp_regs: [(WHV_REGISTER_NAME, Align16); WHP_REGS_NAMES_LEN] = + (&original).into(); + whp_regs[0].0 = WHvX64RegisterRbx; + let err = CommonRegisters::try_from(whp_regs.as_ref()).unwrap_err(); + assert_eq!( + err, + FromWhpRegisterError::DuplicateRegister(WHvX64RegisterRbx.0) + ); + + // test for passing non-standard register (e.g. CR8) + let original = common_regs(); + let mut whp_regs: [(WHV_REGISTER_NAME, Align16); WHP_REGS_NAMES_LEN] = + (&original).into(); + whp_regs[0].0 = WHvX64RegisterCr8; + let err = CommonRegisters::try_from(whp_regs.as_ref()).unwrap_err(); + assert_eq!( + err, + FromWhpRegisterError::InvalidRegister(WHvX64RegisterCr8.0) + ); + } +} diff --git a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs index 2212fd433..4f4e82555 100644 --- a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs +++ b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs @@ -24,12 +24,16 @@ use windows::Win32::System::LibraryLoader::*; use windows::core::s; use windows_result::HRESULT; +use super::regs::{ + Align16, CommonFpu, CommonRegisters, CommonSpecialRegisters, WHP_FPU_NAMES_LEN, WHP_REGS_NAMES, + WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES, WHP_SREGS_NAMES_LEN, +}; use super::surrogate_process::SurrogateProcess; #[cfg(crashdump)] use crate::HyperlightError; +use crate::hypervisor::regs::WHP_FPU_NAMES; #[cfg(gdb)] use crate::hypervisor::wrappers::WHvDebugRegisters; -use crate::hypervisor::wrappers::{WHvFPURegisters, WHvGeneralRegisters, WHvSpecialRegisters}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::{Result, new_error}; @@ -303,15 +307,16 @@ impl VMProcessor { part.0 } + /// Helper for setting arbitrary registers. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(super) fn set_registers( &self, - registers: &[(WHV_REGISTER_NAME, WHV_REGISTER_VALUE)], + registers: &[(WHV_REGISTER_NAME, Align16)], ) -> Result<()> { - let partition_handle = self.get_partition_hdl(); let register_count = registers.len(); - let mut register_names: Vec = vec![]; - let mut register_values: Vec = vec![]; + + let mut register_names = Vec::with_capacity(register_count); + let mut register_values = Vec::with_capacity(register_count); for (key, value) in registers.iter() { register_names.push(*key); @@ -320,188 +325,115 @@ impl VMProcessor { unsafe { WHvSetVirtualProcessorRegisters( - partition_handle, + self.get_partition_hdl(), 0, register_names.as_ptr(), register_count as u32, - register_values.as_ptr(), + register_values.as_ptr() as *const WHV_REGISTER_VALUE, )?; } Ok(()) } - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_sregs(&self) -> Result { - const LEN: usize = 17; - - let names: [WHV_REGISTER_NAME; LEN] = [ - WHvX64RegisterCr0, - WHvX64RegisterCr2, - WHvX64RegisterCr3, - WHvX64RegisterCr4, - WHvX64RegisterCr8, - WHvX64RegisterEfer, - WHvX64RegisterApicBase, - WHvX64RegisterCs, - WHvX64RegisterDs, - WHvX64RegisterEs, - WHvX64RegisterFs, - WHvX64RegisterGs, - WHvX64RegisterSs, - WHvX64RegisterTr, - WHvX64RegisterLdtr, - WHvX64RegisterGdtr, - WHvX64RegisterIdtr, - ]; + pub(super) fn regs(&self) -> Result { + let mut whv_regs_values: [Align16; WHP_REGS_NAMES_LEN] = + unsafe { std::mem::zeroed() }; - let mut out: [WHV_REGISTER_VALUE; LEN] = unsafe { std::mem::zeroed() }; unsafe { WHvGetVirtualProcessorRegisters( self.get_partition_hdl(), 0, - names.as_ptr(), - LEN as u32, - out.as_mut_ptr(), + WHP_REGS_NAMES.as_ptr(), + whv_regs_values.len() as u32, + whv_regs_values.as_mut_ptr() as *mut WHV_REGISTER_VALUE, )?; } - let res: WHvSpecialRegisters = WHvSpecialRegisters { - cr0: out[0], - cr2: out[1], - cr3: out[2], - cr4: out[3], - cr8: out[4], - efer: out[5], - apic_base: out[6], - cs: out[7], - ds: out[8], - es: out[9], - fs: out[10], - gs: out[11], - ss: out[12], - tr: out[13], - ldtr: out[14], - gdtr: out[15], - idtr: out[16], - }; - - Ok(res) + WHP_REGS_NAMES + .into_iter() + .zip(whv_regs_values) + .collect::)>>() + .as_slice() + .try_into() + .map_err(|e| { + new_error!( + "Failed to convert WHP registers to CommonRegisters: {:?}", + e + ) + }) } - // Sets the registers for the VMProcessor to the given general purpose registers. - // If you want to set other registers, use `set_registers` instead. - pub(super) fn set_general_purpose_registers(&self, regs: &WHvGeneralRegisters) -> Result<()> { - const LEN: usize = 18; - - let names: [WHV_REGISTER_NAME; LEN] = [ - WHvX64RegisterRax, - WHvX64RegisterRbx, - WHvX64RegisterRcx, - WHvX64RegisterRdx, - WHvX64RegisterRsi, - WHvX64RegisterRdi, - WHvX64RegisterRsp, - WHvX64RegisterRbp, - WHvX64RegisterR8, - WHvX64RegisterR9, - WHvX64RegisterR10, - WHvX64RegisterR11, - WHvX64RegisterR12, - WHvX64RegisterR13, - WHvX64RegisterR14, - WHvX64RegisterR15, - WHvX64RegisterRip, - WHvX64RegisterRflags, - ]; + pub(super) fn set_regs(&self, regs: &CommonRegisters) -> Result<()> { + let whp_regs: [(WHV_REGISTER_NAME, Align16); WHP_REGS_NAMES_LEN] = + regs.into(); + self.set_registers(&whp_regs)?; + Ok(()) + } - let values: [WHV_REGISTER_VALUE; LEN] = [ - WHV_REGISTER_VALUE { Reg64: regs.rax }, - WHV_REGISTER_VALUE { Reg64: regs.rbx }, - WHV_REGISTER_VALUE { Reg64: regs.rcx }, - WHV_REGISTER_VALUE { Reg64: regs.rdx }, - WHV_REGISTER_VALUE { Reg64: regs.rsi }, - WHV_REGISTER_VALUE { Reg64: regs.rdi }, - WHV_REGISTER_VALUE { Reg64: regs.rsp }, - WHV_REGISTER_VALUE { Reg64: regs.rbp }, - WHV_REGISTER_VALUE { Reg64: regs.r8 }, - WHV_REGISTER_VALUE { Reg64: regs.r9 }, - WHV_REGISTER_VALUE { Reg64: regs.r10 }, - WHV_REGISTER_VALUE { Reg64: regs.r11 }, - WHV_REGISTER_VALUE { Reg64: regs.r12 }, - WHV_REGISTER_VALUE { Reg64: regs.r13 }, - WHV_REGISTER_VALUE { Reg64: regs.r14 }, - WHV_REGISTER_VALUE { Reg64: regs.r15 }, - WHV_REGISTER_VALUE { Reg64: regs.rip }, - WHV_REGISTER_VALUE { Reg64: regs.rflags }, - ]; + pub(super) fn sregs(&self) -> Result { + let mut whp_sregs_values: [Align16; WHP_SREGS_NAMES_LEN] = + unsafe { std::mem::zeroed() }; unsafe { - WHvSetVirtualProcessorRegisters( + WHvGetVirtualProcessorRegisters( self.get_partition_hdl(), 0, - names.as_ptr(), - LEN as u32, - values.as_ptr(), + WHP_SREGS_NAMES.as_ptr(), + whp_sregs_values.len() as u32, + whp_sregs_values.as_mut_ptr() as *mut WHV_REGISTER_VALUE, )?; } + + WHP_SREGS_NAMES + .into_iter() + .zip(whp_sregs_values) + .collect::)>>() + .as_slice() + .try_into() + .map_err(|e| { + new_error!( + "Failed to convert WHP registers to CommonSpecialRegisters: {:?}", + e + ) + }) + } + + pub(super) fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()> { + let whp_regs: [(WHV_REGISTER_NAME, Align16); WHP_SREGS_NAMES_LEN] = + sregs.into(); + self.set_registers(&whp_regs)?; Ok(()) } - pub(super) fn get_regs(&self) -> Result { - const LEN: usize = 18; + pub(super) fn fpu(&self) -> Result { + let mut whp_fpu_values: [Align16; WHP_FPU_NAMES_LEN] = + unsafe { std::mem::zeroed() }; - let names: [WHV_REGISTER_NAME; LEN] = [ - WHvX64RegisterRax, - WHvX64RegisterRbx, - WHvX64RegisterRcx, - WHvX64RegisterRdx, - WHvX64RegisterRsi, - WHvX64RegisterRdi, - WHvX64RegisterRsp, - WHvX64RegisterRbp, - WHvX64RegisterR8, - WHvX64RegisterR9, - WHvX64RegisterR10, - WHvX64RegisterR11, - WHvX64RegisterR12, - WHvX64RegisterR13, - WHvX64RegisterR14, - WHvX64RegisterR15, - WHvX64RegisterRip, - WHvX64RegisterRflags, - ]; - - let mut out: [WHV_REGISTER_VALUE; LEN] = unsafe { std::mem::zeroed() }; unsafe { WHvGetVirtualProcessorRegisters( self.get_partition_hdl(), 0, - names.as_ptr(), - LEN as u32, - out.as_mut_ptr(), + WHP_FPU_NAMES.as_ptr(), + whp_fpu_values.len() as u32, + whp_fpu_values.as_mut_ptr() as *mut WHV_REGISTER_VALUE, )?; - Ok(WHvGeneralRegisters { - rax: out[0].Reg64, - rbx: out[1].Reg64, - rcx: out[2].Reg64, - rdx: out[3].Reg64, - rsi: out[4].Reg64, - rdi: out[5].Reg64, - rsp: out[6].Reg64, - rbp: out[7].Reg64, - r8: out[8].Reg64, - r9: out[9].Reg64, - r10: out[10].Reg64, - r11: out[11].Reg64, - r12: out[12].Reg64, - r13: out[13].Reg64, - r14: out[14].Reg64, - r15: out[15].Reg64, - rip: out[16].Reg64, - rflags: out[17].Reg64, - }) } + + WHP_FPU_NAMES + .into_iter() + .zip(whp_fpu_values) + .collect::)>>() + .as_slice() + .try_into() + .map_err(|e| new_error!("Failed to convert WHP registers to CommonFpu: {:?}", e)) + } + + pub(super) fn set_fpu(&self, fpu: &CommonFpu) -> Result<()> { + let whp_fpu: [(WHV_REGISTER_NAME, Align16); WHP_FPU_NAMES_LEN] = + fpu.into(); + self.set_registers(&whp_fpu)?; + Ok(()) } #[cfg(crashdump)] @@ -560,12 +492,30 @@ impl VMProcessor { #[cfg(gdb)] pub(super) fn set_debug_regs(&self, regs: &WHvDebugRegisters) -> Result<()> { let registers = vec![ - (WHvX64RegisterDr0, WHV_REGISTER_VALUE { Reg64: regs.dr0 }), - (WHvX64RegisterDr1, WHV_REGISTER_VALUE { Reg64: regs.dr1 }), - (WHvX64RegisterDr2, WHV_REGISTER_VALUE { Reg64: regs.dr2 }), - (WHvX64RegisterDr3, WHV_REGISTER_VALUE { Reg64: regs.dr3 }), - (WHvX64RegisterDr6, WHV_REGISTER_VALUE { Reg64: regs.dr6 }), - (WHvX64RegisterDr7, WHV_REGISTER_VALUE { Reg64: regs.dr7 }), + ( + WHvX64RegisterDr0, + Align16(WHV_REGISTER_VALUE { Reg64: regs.dr0 }), + ), + ( + WHvX64RegisterDr1, + Align16(WHV_REGISTER_VALUE { Reg64: regs.dr1 }), + ), + ( + WHvX64RegisterDr2, + Align16(WHV_REGISTER_VALUE { Reg64: regs.dr2 }), + ), + ( + WHvX64RegisterDr3, + Align16(WHV_REGISTER_VALUE { Reg64: regs.dr3 }), + ), + ( + WHvX64RegisterDr6, + Align16(WHV_REGISTER_VALUE { Reg64: regs.dr6 }), + ), + ( + WHvX64RegisterDr7, + Align16(WHV_REGISTER_VALUE { Reg64: regs.dr7 }), + ), ]; self.set_registers(®isters) @@ -584,233 +534,22 @@ impl VMProcessor { WHvX64RegisterDr7, ]; - let mut out: [WHV_REGISTER_VALUE; LEN] = unsafe { std::mem::zeroed() }; + let mut out: [Align16; LEN] = unsafe { std::mem::zeroed() }; unsafe { WHvGetVirtualProcessorRegisters( self.get_partition_hdl(), 0, names.as_ptr(), LEN as u32, - out.as_mut_ptr(), + out.as_mut_ptr() as *mut WHV_REGISTER_VALUE, )?; Ok(WHvDebugRegisters { - dr0: out[0].Reg64, - dr1: out[1].Reg64, - dr2: out[2].Reg64, - dr3: out[3].Reg64, - dr6: out[4].Reg64, - dr7: out[5].Reg64, - }) - } - } - - pub(super) fn set_fpu(&self, regs: &WHvFPURegisters) -> Result<()> { - const LEN: usize = 26; - - let names: [WHV_REGISTER_NAME; LEN] = [ - WHvX64RegisterXmm0, - WHvX64RegisterXmm1, - WHvX64RegisterXmm2, - WHvX64RegisterXmm3, - WHvX64RegisterXmm4, - WHvX64RegisterXmm5, - WHvX64RegisterXmm6, - WHvX64RegisterXmm7, - WHvX64RegisterXmm8, - WHvX64RegisterXmm9, - WHvX64RegisterXmm10, - WHvX64RegisterXmm11, - WHvX64RegisterXmm12, - WHvX64RegisterXmm13, - WHvX64RegisterXmm14, - WHvX64RegisterXmm15, - WHvX64RegisterFpMmx0, - WHvX64RegisterFpMmx1, - WHvX64RegisterFpMmx2, - WHvX64RegisterFpMmx3, - WHvX64RegisterFpMmx4, - WHvX64RegisterFpMmx5, - WHvX64RegisterFpMmx6, - WHvX64RegisterFpMmx7, - WHvX64RegisterFpControlStatus, - WHvX64RegisterXmmControlStatus, - ]; - - let xmm_regs = [ - regs.xmm0, regs.xmm1, regs.xmm2, regs.xmm3, regs.xmm4, regs.xmm5, regs.xmm6, regs.xmm7, - regs.xmm8, regs.xmm9, regs.xmm10, regs.xmm11, regs.xmm12, regs.xmm13, regs.xmm14, - regs.xmm15, - ]; - - let mut values: Vec = xmm_regs - .iter() - .map(|®| WHV_REGISTER_VALUE { - Fp: WHV_X64_FP_REGISTER { - AsUINT128: WHV_UINT128 { - Anonymous: WHV_UINT128_0 { - Low64: reg as u64, - High64: (reg >> 64) as u64, - }, - }, - }, - }) - .collect(); - - values.extend_from_slice(&[ - WHV_REGISTER_VALUE { Reg64: regs.mmx0 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx1 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx2 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx3 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx4 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx5 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx6 }, - WHV_REGISTER_VALUE { Reg64: regs.mmx7 }, - WHV_REGISTER_VALUE { - FpControlStatus: WHV_X64_FP_CONTROL_STATUS_REGISTER { - Anonymous: WHV_X64_FP_CONTROL_STATUS_REGISTER_0 { - FpControl: regs.fp_control_word, - FpTag: regs.fp_tag_word, - ..Default::default() - }, - }, - }, - WHV_REGISTER_VALUE { - XmmControlStatus: WHV_X64_XMM_CONTROL_STATUS_REGISTER { - Anonymous: WHV_X64_XMM_CONTROL_STATUS_REGISTER_0 { - XmmStatusControl: regs.mxcsr, - ..Default::default() - }, - }, - }, - ]); - - unsafe { - WHvSetVirtualProcessorRegisters( - self.get_partition_hdl(), - 0, - names.as_ptr(), - LEN as u32, - values.as_ptr(), - )?; - } - Ok(()) - } - - #[cfg(gdb)] - pub(super) fn get_fpu(&self) -> Result { - use windows::Win32::System::Hypervisor::*; - - const LEN: usize = 26; - let names: [WHV_REGISTER_NAME; LEN] = [ - WHvX64RegisterXmm0, - WHvX64RegisterXmm1, - WHvX64RegisterXmm2, - WHvX64RegisterXmm3, - WHvX64RegisterXmm4, - WHvX64RegisterXmm5, - WHvX64RegisterXmm6, - WHvX64RegisterXmm7, - WHvX64RegisterXmm8, - WHvX64RegisterXmm9, - WHvX64RegisterXmm10, - WHvX64RegisterXmm11, - WHvX64RegisterXmm12, - WHvX64RegisterXmm13, - WHvX64RegisterXmm14, - WHvX64RegisterXmm15, - WHvX64RegisterFpMmx0, - WHvX64RegisterFpMmx1, - WHvX64RegisterFpMmx2, - WHvX64RegisterFpMmx3, - WHvX64RegisterFpMmx4, - WHvX64RegisterFpMmx5, - WHvX64RegisterFpMmx6, - WHvX64RegisterFpMmx7, - WHvX64RegisterFpControlStatus, - WHvX64RegisterXmmControlStatus, - ]; - - let mut out: [WHV_REGISTER_VALUE; LEN] = unsafe { std::mem::zeroed() }; - unsafe { - WHvGetVirtualProcessorRegisters( - self.get_partition_hdl(), - 0, - names.as_ptr(), - LEN as u32, - out.as_mut_ptr(), - )?; - - // Helper to read a WHV_UINT128 -> u128 - fn u128_from_whv(fp: WHV_REGISTER_VALUE) -> u128 { - unsafe { - let low = fp.Fp.AsUINT128.Anonymous.Low64 as u128; - let high = fp.Fp.AsUINT128.Anonymous.High64 as u128; - (high << 64) | low - } - } - - let xmm = [ - u128_from_whv(out[0]), - u128_from_whv(out[1]), - u128_from_whv(out[2]), - u128_from_whv(out[3]), - u128_from_whv(out[4]), - u128_from_whv(out[5]), - u128_from_whv(out[6]), - u128_from_whv(out[7]), - u128_from_whv(out[8]), - u128_from_whv(out[9]), - u128_from_whv(out[10]), - u128_from_whv(out[11]), - u128_from_whv(out[12]), - u128_from_whv(out[13]), - u128_from_whv(out[14]), - u128_from_whv(out[15]), - ]; - - let mmx = [ - out[16].Reg64, - out[17].Reg64, - out[18].Reg64, - out[19].Reg64, - out[20].Reg64, - out[21].Reg64, - out[22].Reg64, - out[23].Reg64, - ]; - - let fp_control_word = out[24].FpControlStatus.Anonymous.FpControl; - let fp_tag_word = out[24].FpControlStatus.Anonymous.FpTag; - let mxcsr = out[25].XmmControlStatus.Anonymous.XmmStatusControl; - - Ok(WHvFPURegisters { - xmm0: xmm[0], - xmm1: xmm[1], - xmm2: xmm[2], - xmm3: xmm[3], - xmm4: xmm[4], - xmm5: xmm[5], - xmm6: xmm[6], - xmm7: xmm[7], - xmm8: xmm[8], - xmm9: xmm[9], - xmm10: xmm[10], - xmm11: xmm[11], - xmm12: xmm[12], - xmm13: xmm[13], - xmm14: xmm[14], - xmm15: xmm[15], - mmx0: mmx[0], - mmx1: mmx[1], - mmx2: mmx[2], - mmx3: mmx[3], - mmx4: mmx[4], - mmx5: mmx[5], - mmx6: mmx[6], - mmx7: mmx[7], - fp_control_word, - fp_tag_word, - mxcsr, + dr0: out[0].0.Reg64, + dr1: out[1].0.Reg64, + dr2: out[2].0.Reg64, + dr3: out[3].0.Reg64, + dr6: out[4].0.Reg64, + dr7: out[5].0.Reg64, }) } } diff --git a/src/hyperlight_host/src/hypervisor/wrappers.rs b/src/hyperlight_host/src/hypervisor/wrappers.rs index 4a2a1ff8b..135ae2b82 100644 --- a/src/hyperlight_host/src/hypervisor/wrappers.rs +++ b/src/hyperlight_host/src/hypervisor/wrappers.rs @@ -18,7 +18,6 @@ use std::ffi::CString; use tracing::{Span, instrument}; use windows::Win32::Foundation::{HANDLE, HMODULE}; -use windows::Win32::System::Hypervisor::WHV_REGISTER_VALUE; use windows::core::PSTR; use crate::{HyperlightError, Result}; @@ -57,29 +56,6 @@ impl From<&PSTRWrapper> for PSTR { } } -// only used on windows. mshv and kvm already has this implemented -#[derive(Debug, Default, Copy, Clone, PartialEq)] -pub(super) struct WHvGeneralRegisters { - pub rax: u64, - pub rbx: u64, - pub rcx: u64, - pub rdx: u64, - pub rsi: u64, - pub rdi: u64, - pub rsp: u64, - pub rbp: u64, - pub r8: u64, - pub r9: u64, - pub r10: u64, - pub r11: u64, - pub r12: u64, - pub r13: u64, - pub r14: u64, - pub r15: u64, - pub rip: u64, - pub rflags: u64, -} - /// only used on widos for handling debug registers with the VMProcessor #[cfg(gdb)] #[derive(Debug, Default, Copy, Clone, PartialEq)] @@ -92,61 +68,6 @@ pub(super) struct WHvDebugRegisters { pub dr7: u64, } -#[derive(Debug, Default, Copy, Clone, PartialEq)] -pub(super) struct WHvFPURegisters { - pub xmm0: u128, - pub xmm1: u128, - pub xmm2: u128, - pub xmm3: u128, - pub xmm4: u128, - pub xmm5: u128, - pub xmm6: u128, - pub xmm7: u128, - pub xmm8: u128, - pub xmm9: u128, - pub xmm10: u128, - pub xmm11: u128, - pub xmm12: u128, - pub xmm13: u128, - pub xmm14: u128, - pub xmm15: u128, - - pub mmx0: u64, - pub mmx1: u64, - pub mmx2: u64, - pub mmx3: u64, - pub mmx4: u64, - pub mmx5: u64, - pub mmx6: u64, - pub mmx7: u64, - - pub fp_control_word: u16, - pub fp_tag_word: u8, - - pub mxcsr: u32, -} - -#[derive(Default, Copy, Clone)] -pub(super) struct WHvSpecialRegisters { - pub cr0: WHV_REGISTER_VALUE, - pub cr2: WHV_REGISTER_VALUE, - pub cr3: WHV_REGISTER_VALUE, - pub cr4: WHV_REGISTER_VALUE, - pub cr8: WHV_REGISTER_VALUE, - pub efer: WHV_REGISTER_VALUE, - pub apic_base: WHV_REGISTER_VALUE, - pub cs: WHV_REGISTER_VALUE, - pub ds: WHV_REGISTER_VALUE, - pub es: WHV_REGISTER_VALUE, - pub fs: WHV_REGISTER_VALUE, - pub gs: WHV_REGISTER_VALUE, - pub ss: WHV_REGISTER_VALUE, - pub tr: WHV_REGISTER_VALUE, - pub ldtr: WHV_REGISTER_VALUE, - pub gdtr: WHV_REGISTER_VALUE, - pub idtr: WHV_REGISTER_VALUE, -} - /// Wrapper for HANDLE, required since HANDLE is no longer Send. #[derive(Debug, Copy, Clone)] pub struct HandleWrapper(HANDLE); diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 6e59c5367..038d9d780 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -170,13 +170,10 @@ fn unwind( .unwind_cache .try_lock() .map_err(|e| new_error!("could not lock unwinder cache {}\n", e))?; + let regs = hv.regs()?; let iter = trace_info.unwinder.iter_frames( - hv.read_trace_reg(crate::hypervisor::TraceRegister::RIP)?, - framehop::x86_64::UnwindRegsX86_64::new( - hv.read_trace_reg(crate::hypervisor::TraceRegister::RIP)?, - hv.read_trace_reg(crate::hypervisor::TraceRegister::RSP)?, - hv.read_trace_reg(crate::hypervisor::TraceRegister::RBP)?, - ), + regs.rip, + framehop::x86_64::UnwindRegsX86_64::new(regs.rip, regs.rsp, regs.rbp), &mut *cache, &mut read_stack, ); @@ -315,13 +312,15 @@ pub(crate) fn handle_outb( } #[cfg(feature = "mem_profile")] OutBAction::TraceMemoryAlloc => { + use crate::hypervisor::regs::CommonRegisters; + let Ok(stack) = unwind(_hv, mem_mgr, _hv.trace_info_as_ref()) else { return Ok(()); }; - let Ok(amt) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RAX) else { - return Ok(()); - }; - let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else { + let Ok(CommonRegisters { + rax: amt, rcx: ptr, .. + }) = _hv.regs() + else { return Ok(()); }; record_trace_frame(_hv.trace_info_as_ref(), 2u64, |f| { @@ -332,23 +331,27 @@ pub(crate) fn handle_outb( } #[cfg(feature = "mem_profile")] OutBAction::TraceMemoryFree => { + use crate::hypervisor::regs::CommonRegisters; + let Ok(stack) = unwind(_hv, mem_mgr, _hv.trace_info_as_ref()) else { return Ok(()); }; - let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else { + let Ok(CommonRegisters { rcx, .. }) = _hv.regs() else { return Ok(()); }; record_trace_frame(_hv.trace_info_as_ref(), 3u64, |f| { - let _ = f.write_all(&ptr.to_ne_bytes()); + let _ = f.write_all(&rcx.to_ne_bytes()); write_stack(f, &stack); }) } #[cfg(feature = "trace_guest")] OutBAction::TraceRecord => { - let Ok(len) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RAX) else { - return Ok(()); - }; - let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else { + use crate::hypervisor::regs::CommonRegisters; + + let Ok(CommonRegisters { + rax: len, rcx: ptr, .. + }) = _hv.regs() + else { return Ok(()); }; let mut buffer = vec![0u8; len as usize * std::mem::size_of::()]; diff --git a/typos.toml b/typos.toml index c05ec808d..07ffa9dee 100644 --- a/typos.toml +++ b/typos.toml @@ -8,3 +8,4 @@ extend-exclude = ["**/*.patch", "src/hyperlight_guest_bin/third_party/**/*", "NO # typ is used for field name as type is a reserved keyword typ="typ" mmaped="mmapped" +fpr="fpr"