use core::ffi::c_void;
use std::arch::x86_64::CpuidResult;
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::mem::size_of;
use std::mem::size_of_val;
use std::sync::Arc;
use base::Error;
use base::Result;
use libc::EINVAL;
use libc::EIO;
use libc::ENOENT;
use libc::ENXIO;
use snapshot::AnySnapshot;
use vm_memory::GuestAddress;
use winapi::shared::winerror::E_UNEXPECTED;
use windows::Win32::Foundation::WHV_E_INSUFFICIENT_BUFFER;
use super::types::*;
use super::*;
use crate::CpuId;
use crate::CpuIdEntry;
use crate::DebugRegs;
use crate::Fpu;
use crate::IoOperation;
use crate::IoParams;
use crate::Regs;
use crate::Sregs;
use crate::Vcpu;
use crate::VcpuExit;
use crate::VcpuX86_64;
use crate::Xsave;
const WHPX_EXIT_DIRECTION_MMIO_READ: u8 = 0;
const WHPX_EXIT_DIRECTION_MMIO_WRITE: u8 = 1;
const WHPX_EXIT_DIRECTION_PIO_IN: u8 = 0;
const WHPX_EXIT_DIRECTION_PIO_OUT: u8 = 1;
struct SafeInstructionEmulator {
handle: WHV_EMULATOR_HANDLE,
}
impl SafeInstructionEmulator {
fn new() -> Result<SafeInstructionEmulator> {
const EMULATOR_CALLBACKS: WHV_EMULATOR_CALLBACKS = WHV_EMULATOR_CALLBACKS {
Size: size_of::<WHV_EMULATOR_CALLBACKS>() as u32,
Reserved: 0,
WHvEmulatorIoPortCallback: Some(SafeInstructionEmulator::io_port_cb),
WHvEmulatorMemoryCallback: Some(SafeInstructionEmulator::memory_cb),
WHvEmulatorGetVirtualProcessorRegisters: Some(
SafeInstructionEmulator::get_virtual_processor_registers_cb,
),
WHvEmulatorSetVirtualProcessorRegisters: Some(
SafeInstructionEmulator::set_virtual_processor_registers_cb,
),
WHvEmulatorTranslateGvaPage: Some(SafeInstructionEmulator::translate_gva_page_cb),
};
let mut handle: WHV_EMULATOR_HANDLE = std::ptr::null_mut();
check_whpx!(unsafe { WHvEmulatorCreateEmulator(&EMULATOR_CALLBACKS, &mut handle) })?;
Ok(SafeInstructionEmulator { handle })
}
}
trait InstructionEmulatorCallbacks {
extern "stdcall" fn io_port_cb(
context: *mut ::std::os::raw::c_void,
io_access: *mut WHV_EMULATOR_IO_ACCESS_INFO,
) -> HRESULT;
extern "stdcall" fn memory_cb(
context: *mut ::std::os::raw::c_void,
memory_access: *mut WHV_EMULATOR_MEMORY_ACCESS_INFO,
) -> HRESULT;
extern "stdcall" fn get_virtual_processor_registers_cb(
context: *mut ::std::os::raw::c_void,
register_names: *const WHV_REGISTER_NAME,
register_count: UINT32,
register_values: *mut WHV_REGISTER_VALUE,
) -> HRESULT;
extern "stdcall" fn set_virtual_processor_registers_cb(
context: *mut ::std::os::raw::c_void,
register_names: *const WHV_REGISTER_NAME,
register_count: UINT32,
register_values: *const WHV_REGISTER_VALUE,
) -> HRESULT;
extern "stdcall" fn translate_gva_page_cb(
context: *mut ::std::os::raw::c_void,
gva: WHV_GUEST_VIRTUAL_ADDRESS,
translate_flags: WHV_TRANSLATE_GVA_FLAGS,
translation_result: *mut WHV_TRANSLATE_GVA_RESULT_CODE,
gpa: *mut WHV_GUEST_PHYSICAL_ADDRESS,
) -> HRESULT;
}
struct InstructionEmulatorContext<'a> {
vm_partition: Arc<SafePartition>,
index: u32,
handle_mmio: Option<&'a mut dyn FnMut(IoParams) -> Result<()>>,
handle_io: Option<&'a mut dyn FnMut(IoParams)>,
}
impl InstructionEmulatorCallbacks for SafeInstructionEmulator {
extern "stdcall" fn io_port_cb(
context: *mut ::std::os::raw::c_void,
io_access: *mut WHV_EMULATOR_IO_ACCESS_INFO,
) -> HRESULT {
let ctx = unsafe { &mut *(context as *mut InstructionEmulatorContext) };
let Some(handle_io) = &mut ctx.handle_io else {
return E_UNEXPECTED;
};
let io_access_info = unsafe { &mut *io_access };
let address = io_access_info.Port.into();
let size = io_access_info.AccessSize as usize;
let data: &mut [u8] = unsafe {
assert!(size <= size_of_val(&io_access_info.Data));
std::slice::from_raw_parts_mut(&mut io_access_info.Data as *mut u32 as *mut u8, size)
};
match io_access_info.Direction {
WHPX_EXIT_DIRECTION_PIO_IN => {
handle_io(IoParams {
address,
operation: IoOperation::Read(data),
});
S_OK
}
WHPX_EXIT_DIRECTION_PIO_OUT => {
handle_io(IoParams {
address,
operation: IoOperation::Write(data),
});
S_OK
}
_ => E_UNEXPECTED,
}
}
extern "stdcall" fn memory_cb(
context: *mut ::std::os::raw::c_void,
memory_access: *mut WHV_EMULATOR_MEMORY_ACCESS_INFO,
) -> HRESULT {
let ctx = unsafe { &mut *(context as *mut InstructionEmulatorContext) };
let Some(handle_mmio) = &mut ctx.handle_mmio else {
return E_UNEXPECTED;
};
let memory_access_info = unsafe { &mut *memory_access };
let address = memory_access_info.GpaAddress;
let size = memory_access_info.AccessSize as usize;
let data = &mut memory_access_info.Data[..size];
match memory_access_info.Direction {
WHPX_EXIT_DIRECTION_MMIO_READ => {
if let Err(e) = handle_mmio(IoParams {
address,
operation: IoOperation::Read(data),
}) {
error!("handle_mmio failed with {e}");
E_UNEXPECTED
} else {
S_OK
}
}
WHPX_EXIT_DIRECTION_MMIO_WRITE => {
if let Err(e) = handle_mmio(IoParams {
address,
operation: IoOperation::Write(data),
}) {
error!("handle_mmio write with {e}");
E_UNEXPECTED
} else {
S_OK
}
}
_ => E_UNEXPECTED,
}
}
extern "stdcall" fn get_virtual_processor_registers_cb(
context: *mut ::std::os::raw::c_void,
register_names: *const WHV_REGISTER_NAME,
register_count: UINT32,
register_values: *mut WHV_REGISTER_VALUE,
) -> HRESULT {
let ctx = unsafe { &*(context as *const InstructionEmulatorContext) };
unsafe {
WHvGetVirtualProcessorRegisters(
ctx.vm_partition.partition,
ctx.index,
register_names,
register_count,
register_values,
)
}
}
extern "stdcall" fn set_virtual_processor_registers_cb(
context: *mut ::std::os::raw::c_void,
register_names: *const WHV_REGISTER_NAME,
register_count: UINT32,
register_values: *const WHV_REGISTER_VALUE,
) -> HRESULT {
let ctx = unsafe { &*(context as *const InstructionEmulatorContext) };
unsafe {
WHvSetVirtualProcessorRegisters(
ctx.vm_partition.partition,
ctx.index,
register_names,
register_count,
register_values,
)
}
}
extern "stdcall" fn translate_gva_page_cb(
context: *mut ::std::os::raw::c_void,
gva: WHV_GUEST_VIRTUAL_ADDRESS,
translate_flags: WHV_TRANSLATE_GVA_FLAGS,
translation_result_code: *mut WHV_TRANSLATE_GVA_RESULT_CODE,
gpa: *mut WHV_GUEST_PHYSICAL_ADDRESS,
) -> HRESULT {
let ctx = unsafe { &*(context as *const InstructionEmulatorContext) };
let mut translation_result: WHV_TRANSLATE_GVA_RESULT = Default::default();
let ret = unsafe {
WHvTranslateGva(
ctx.vm_partition.partition,
ctx.index,
gva,
translate_flags,
&mut translation_result,
gpa,
)
};
if ret == S_OK {
unsafe {
*translation_result_code = translation_result.ResultCode;
}
}
ret
}
}
impl Drop for SafeInstructionEmulator {
fn drop(&mut self) {
check_whpx!(unsafe { WHvEmulatorDestroyEmulator(self.handle) }).unwrap();
}
}
unsafe impl Send for SafeInstructionEmulator {}
unsafe impl Sync for SafeInstructionEmulator {}
struct SafeVirtualProcessor {
vm_partition: Arc<SafePartition>,
index: u32,
}
impl SafeVirtualProcessor {
fn new(vm_partition: Arc<SafePartition>, index: u32) -> Result<SafeVirtualProcessor> {
check_whpx!(unsafe { WHvCreateVirtualProcessor(vm_partition.partition, index, 0) })?;
Ok(SafeVirtualProcessor {
vm_partition,
index,
})
}
}
impl Drop for SafeVirtualProcessor {
fn drop(&mut self) {
check_whpx!(unsafe { WHvDeleteVirtualProcessor(self.vm_partition.partition, self.index,) })
.unwrap();
}
}
pub struct WhpxVcpu {
index: u32,
safe_virtual_processor: Arc<SafeVirtualProcessor>,
vm_partition: Arc<SafePartition>,
last_exit_context: Arc<WHV_RUN_VP_EXIT_CONTEXT>,
instruction_emulator: Arc<SafeInstructionEmulator>,
tsc_frequency: Option<u64>,
apic_frequency: Option<u32>,
}
impl WhpxVcpu {
pub(super) fn new(vm_partition: Arc<SafePartition>, index: u32) -> Result<WhpxVcpu> {
let safe_virtual_processor = SafeVirtualProcessor::new(vm_partition.clone(), index)?;
let instruction_emulator = SafeInstructionEmulator::new()?;
Ok(WhpxVcpu {
index,
safe_virtual_processor: Arc::new(safe_virtual_processor),
vm_partition,
last_exit_context: Arc::new(Default::default()),
instruction_emulator: Arc::new(instruction_emulator),
tsc_frequency: None,
apic_frequency: None,
})
}
pub fn set_frequencies(&mut self, tsc_frequency: Option<u64>, lapic_frequency: u32) {
self.tsc_frequency = tsc_frequency;
self.apic_frequency = Some(lapic_frequency);
}
fn handle_msr_read(&mut self, id: u32) -> Result<()> {
if self.last_exit_context.ExitReason
!= WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64MsrAccess
{
return Err(Error::new(EINVAL));
}
let value = match id {
HV_X64_MSR_TSC_FREQUENCY => Some(self.tsc_frequency.unwrap_or(0)),
HV_X64_MSR_APIC_FREQUENCY => Some(self.apic_frequency.unwrap_or(0) as u64),
_ => None,
};
if let Some(value) = value {
let rip = self.last_exit_context.VpContext.Rip
+ self.last_exit_context.VpContext.InstructionLength() as u64;
const REG_NAMES: [WHV_REGISTER_NAME; 3] = [
WHV_REGISTER_NAME_WHvX64RegisterRip,
WHV_REGISTER_NAME_WHvX64RegisterRax,
WHV_REGISTER_NAME_WHvX64RegisterRdx,
];
let values = vec![
WHV_REGISTER_VALUE { Reg64: rip },
WHV_REGISTER_VALUE {
Reg64: (value & 0xffffffff),
},
WHV_REGISTER_VALUE {
Reg64: (value >> 32),
},
];
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
values.as_ptr() as *const WHV_REGISTER_VALUE,
)
})
} else {
self.inject_gp_fault()
}
}
fn handle_msr_write(&mut self, id: u32, _value: u64) -> Result<()> {
if self.last_exit_context.ExitReason
!= WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64MsrAccess
{
return Err(Error::new(EINVAL));
}
let success = matches!(id, HV_X64_MSR_TSC_INVARIANT_CONTROL);
if !success {
return self.inject_gp_fault();
}
let rip = self.last_exit_context.VpContext.Rip
+ self.last_exit_context.VpContext.InstructionLength() as u64;
const REG_NAMES: [WHV_REGISTER_NAME; 1] = [WHV_REGISTER_NAME_WHvX64RegisterRip];
let values = vec![WHV_REGISTER_VALUE { Reg64: rip }];
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
values.as_ptr() as *const WHV_REGISTER_VALUE,
)
})
}
fn inject_gp_fault(&self) -> Result<()> {
const REG_NAMES: [WHV_REGISTER_NAME; 1] = [WHV_REGISTER_NAME_WHvRegisterPendingEvent];
let mut event = WHV_REGISTER_VALUE {
ExceptionEvent: WHV_X64_PENDING_EXCEPTION_EVENT {
__bindgen_anon_1: Default::default(),
},
};
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
&mut event as *mut WHV_REGISTER_VALUE,
)
})?;
if unsafe { event.ExceptionEvent.__bindgen_anon_1.EventPending() } != 0 {
error!("Unable to inject gp fault because pending exception exists");
return Err(Error::new(EINVAL));
}
let mut pending_exception = unsafe { event.ExceptionEvent.__bindgen_anon_1 };
pending_exception.set_EventPending(1);
pending_exception.set_DeliverErrorCode(1);
pending_exception.ErrorCode = 0;
pending_exception
.set_EventType(WHV_X64_PENDING_EVENT_TYPE_WHvX64PendingEventException as u32);
const GP_VECTOR: u32 = 13;
pending_exception.set_Vector(GP_VECTOR);
let event = WHV_REGISTER_VALUE {
ExceptionEvent: WHV_X64_PENDING_EXCEPTION_EVENT {
__bindgen_anon_1: pending_exception,
},
};
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
&event as *const WHV_REGISTER_VALUE,
)
})
}
}
impl Vcpu for WhpxVcpu {
fn try_clone(&self) -> Result<Self> {
Ok(WhpxVcpu {
index: self.index,
safe_virtual_processor: self.safe_virtual_processor.clone(),
vm_partition: self.vm_partition.clone(),
last_exit_context: self.last_exit_context.clone(),
instruction_emulator: self.instruction_emulator.clone(),
tsc_frequency: self.tsc_frequency,
apic_frequency: self.apic_frequency,
})
}
fn as_vcpu(&self) -> &dyn Vcpu {
self
}
fn id(&self) -> usize {
self.index.try_into().unwrap()
}
fn set_immediate_exit(&self, exit: bool) {
if exit {
unsafe {
WHvCancelRunVirtualProcessor(self.vm_partition.partition, self.index, 0);
}
}
}
fn on_suspend(&self) -> Result<()> {
Ok(())
}
unsafe fn enable_raw_capability(&self, _cap: u32, _args: &[u64; 4]) -> Result<()> {
Err(Error::new(ENXIO))
}
fn handle_mmio(&self, handle_fn: &mut dyn FnMut(IoParams) -> Result<()>) -> Result<()> {
let mut status: WHV_EMULATOR_STATUS = Default::default();
let mut ctx = InstructionEmulatorContext {
vm_partition: self.vm_partition.clone(),
index: self.index,
handle_mmio: Some(handle_fn),
handle_io: None,
};
check_whpx!(unsafe {
WHvEmulatorTryMmioEmulation(
self.instruction_emulator.handle,
&mut ctx as *mut _ as *mut c_void,
&self.last_exit_context.VpContext,
&self.last_exit_context.__bindgen_anon_1.MemoryAccess,
&mut status,
)
})?;
let success = unsafe { status.__bindgen_anon_1.EmulationSuccessful() > 0 };
if success {
Ok(())
} else {
self.inject_gp_fault()?;
Err(Error::new(unsafe { status.AsUINT32 }))
}
}
fn handle_io(&self, handle_fn: &mut dyn FnMut(IoParams)) -> Result<()> {
let mut status: WHV_EMULATOR_STATUS = Default::default();
let mut ctx = InstructionEmulatorContext {
vm_partition: self.vm_partition.clone(),
index: self.index,
handle_mmio: None,
handle_io: Some(handle_fn),
};
check_whpx!(unsafe {
WHvEmulatorTryIoEmulation(
self.instruction_emulator.handle,
&mut ctx as *mut _ as *mut c_void,
&self.last_exit_context.VpContext,
&self.last_exit_context.__bindgen_anon_1.IoPortAccess,
&mut status,
)
})?;
let success = unsafe { status.__bindgen_anon_1.EmulationSuccessful() > 0 };
if success {
Ok(())
} else {
Err(Error::new(unsafe { status.AsUINT32 }))
}
}
#[allow(non_upper_case_globals)]
fn run(&mut self) -> Result<VcpuExit> {
let exit_context_ptr = Arc::as_ptr(&self.last_exit_context);
check_whpx!(unsafe {
WHvRunVirtualProcessor(
self.vm_partition.partition,
self.index,
exit_context_ptr as *mut WHV_RUN_VP_EXIT_CONTEXT as *mut c_void,
size_of::<WHV_RUN_VP_EXIT_CONTEXT>() as u32,
)
})?;
match self.last_exit_context.ExitReason {
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonMemoryAccess => Ok(VcpuExit::Mmio),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64IoPortAccess => Ok(VcpuExit::Io),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonUnrecoverableException => {
Ok(VcpuExit::UnrecoverableException)
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonInvalidVpRegisterValue => {
Ok(VcpuExit::InvalidVpRegister)
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonUnsupportedFeature => {
Ok(VcpuExit::UnsupportedFeature)
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64InterruptWindow => {
Ok(VcpuExit::IrqWindowOpen)
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64Halt => Ok(VcpuExit::Hlt),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64ApicEoi => {
let vector = unsafe {
self.last_exit_context
.__bindgen_anon_1
.ApicEoi
.InterruptVector as u8
};
Ok(VcpuExit::IoapicEoi { vector })
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64MsrAccess => {
let id = unsafe { self.last_exit_context.__bindgen_anon_1.MsrAccess.MsrNumber };
let is_write = unsafe {
self.last_exit_context
.__bindgen_anon_1
.MsrAccess
.AccessInfo
.__bindgen_anon_1
.IsWrite()
== 1
};
if is_write {
let value = unsafe {
(self.last_exit_context.__bindgen_anon_1.MsrAccess.Rdx << 32)
| (self.last_exit_context.__bindgen_anon_1.MsrAccess.Rax & 0xffffffff)
};
self.handle_msr_write(id, value)?;
} else {
self.handle_msr_read(id)?;
}
Ok(VcpuExit::MsrAccess)
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64Cpuid => {
let entry = unsafe {
CpuIdEntry {
function: self.last_exit_context.__bindgen_anon_1.CpuidAccess.Rax as u32,
index: self.last_exit_context.__bindgen_anon_1.CpuidAccess.Rcx as u32,
flags: 0,
cpuid: CpuidResult {
eax: self
.last_exit_context
.__bindgen_anon_1
.CpuidAccess
.DefaultResultRax as u32,
ebx: self
.last_exit_context
.__bindgen_anon_1
.CpuidAccess
.DefaultResultRbx as u32,
ecx: self
.last_exit_context
.__bindgen_anon_1
.CpuidAccess
.DefaultResultRcx as u32,
edx: self
.last_exit_context
.__bindgen_anon_1
.CpuidAccess
.DefaultResultRdx as u32,
},
}
};
Ok(VcpuExit::Cpuid { entry })
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonException => Ok(VcpuExit::Exception),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64Rdtsc => Ok(VcpuExit::RdTsc),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64ApicSmiTrap => Ok(VcpuExit::ApicSmiTrap),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonHypercall => Ok(VcpuExit::Hypercall),
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64ApicInitSipiTrap => {
Ok(VcpuExit::ApicInitSipiTrap)
}
WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonCanceled => Ok(VcpuExit::Canceled),
r => panic!("unknown exit reason: {}", r),
}
}
}
impl VcpuX86_64 for WhpxVcpu {
fn set_interrupt_window_requested(&self, requested: bool) {
const REG_NAMES: [WHV_REGISTER_NAME; 1] =
[WHV_REGISTER_NAME_WHvX64RegisterDeliverabilityNotifications];
let mut notifications: WHV_X64_DELIVERABILITY_NOTIFICATIONS_REGISTER__bindgen_ty_1 =
Default::default();
notifications.set_InterruptNotification(if requested { 1 } else { 0 });
let notify_register = WHV_REGISTER_VALUE {
DeliverabilityNotifications: WHV_X64_DELIVERABILITY_NOTIFICATIONS_REGISTER {
__bindgen_anon_1: notifications,
},
};
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
¬ify_register as *const WHV_REGISTER_VALUE,
)
})
.unwrap();
}
fn ready_for_interrupt(&self) -> bool {
let pending = unsafe {
self.last_exit_context
.VpContext
.ExecutionState
.__bindgen_anon_1
.InterruptionPending()
};
let shadow = unsafe {
self.last_exit_context
.VpContext
.ExecutionState
.__bindgen_anon_1
.InterruptShadow()
};
let eflags = self.last_exit_context.VpContext.Rflags;
const IF_MASK: u64 = 0x00000200;
shadow == 0 && pending == 0 && (eflags & IF_MASK) != 0
}
fn interrupt(&self, irq: u8) -> Result<()> {
const REG_NAMES: [WHV_REGISTER_NAME; 1] =
[WHV_REGISTER_NAME_WHvRegisterPendingInterruption];
let mut pending_interrupt: WHV_X64_PENDING_INTERRUPTION_REGISTER__bindgen_ty_1 =
Default::default();
pending_interrupt.set_InterruptionPending(1);
pending_interrupt
.set_InterruptionType(WHV_X64_PENDING_INTERRUPTION_TYPE_WHvX64PendingInterrupt as u32);
pending_interrupt.set_InterruptionVector(irq.into());
let interrupt = WHV_REGISTER_VALUE {
PendingInterruption: WHV_X64_PENDING_INTERRUPTION_REGISTER {
__bindgen_anon_1: pending_interrupt,
},
};
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
&interrupt as *const WHV_REGISTER_VALUE,
)
})
}
fn inject_nmi(&self) -> Result<()> {
const REG_NAMES: [WHV_REGISTER_NAME; 1] =
[WHV_REGISTER_NAME_WHvRegisterPendingInterruption];
let mut pending_interrupt: WHV_X64_PENDING_INTERRUPTION_REGISTER__bindgen_ty_1 =
Default::default();
pending_interrupt.set_InterruptionPending(1);
pending_interrupt
.set_InterruptionType(WHV_X64_PENDING_INTERRUPTION_TYPE_WHvX64PendingNmi as u32);
const NMI_VECTOR: u32 = 2;
pending_interrupt.set_InterruptionVector(NMI_VECTOR);
let interrupt = WHV_REGISTER_VALUE {
PendingInterruption: WHV_X64_PENDING_INTERRUPTION_REGISTER {
__bindgen_anon_1: pending_interrupt,
},
};
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
&interrupt as *const WHV_REGISTER_VALUE,
)
})
}
fn get_regs(&self) -> Result<Regs> {
let mut whpx_regs: WhpxRegs = Default::default();
let reg_names = WhpxRegs::get_register_names();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_regs.as_mut_ptr(),
)
})?;
Ok(Regs::from(&whpx_regs))
}
fn set_regs(&self, regs: &Regs) -> Result<()> {
let whpx_regs = WhpxRegs::from(regs);
let reg_names = WhpxRegs::get_register_names();
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_regs.as_ptr(),
)
})
}
fn get_sregs(&self) -> Result<Sregs> {
let mut whpx_sregs: WhpxSregs = Default::default();
let reg_names = WhpxSregs::get_register_names();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_sregs.as_mut_ptr(),
)
})?;
Ok(Sregs::from(&whpx_sregs))
}
fn set_sregs(&self, sregs: &Sregs) -> Result<()> {
let whpx_sregs = WhpxSregs::from(sregs);
let reg_names = WhpxSregs::get_register_names();
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_sregs.as_ptr(),
)
})
}
fn get_fpu(&self) -> Result<Fpu> {
let mut whpx_fpu: WhpxFpu = Default::default();
let reg_names = WhpxFpu::get_register_names();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_fpu.as_mut_ptr(),
)
})?;
Ok(Fpu::from(&whpx_fpu))
}
fn set_fpu(&self, fpu: &Fpu) -> Result<()> {
let whpx_fpu = WhpxFpu::from(fpu);
let reg_names = WhpxFpu::get_register_names();
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_fpu.as_ptr(),
)
})
}
fn get_xsave(&self) -> Result<Xsave> {
let mut empty_buffer = [0u8; 1];
let mut needed_buf_size: u32 = 0;
let res = unsafe {
WHvGetVirtualProcessorXsaveState(
self.vm_partition.partition,
self.index,
empty_buffer.as_mut_ptr() as *mut _,
0,
&mut needed_buf_size,
)
};
if res != WHV_E_INSUFFICIENT_BUFFER.0 {
error!("failed to get size of vcpu xsave");
return Err(Error::new(EIO));
}
let mut xsave = Xsave::new(needed_buf_size as usize);
check_whpx!(unsafe {
WHvGetVirtualProcessorXsaveState(
self.vm_partition.partition,
self.index,
xsave.as_mut_ptr(),
xsave.len() as u32,
&mut needed_buf_size,
)
})?;
Ok(xsave)
}
fn set_xsave(&self, xsave: &Xsave) -> Result<()> {
check_whpx!(unsafe {
WHvSetVirtualProcessorXsaveState(
self.vm_partition.partition,
self.index,
xsave.as_ptr(),
xsave.len() as u32,
)
})
}
fn get_hypervisor_specific_state(&self) -> Result<AnySnapshot> {
let mut whpx_interrupt_regs: WhpxInterruptRegs = Default::default();
let reg_names = WhpxInterruptRegs::get_register_names();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_interrupt_regs.as_mut_ptr(),
)
})?;
AnySnapshot::to_any(whpx_interrupt_regs.into_serializable()).map_err(|e| {
error!("failed to serialize interrupt state: {:?}", e);
Error::new(EIO)
})
}
fn set_hypervisor_specific_state(&self, data: AnySnapshot) -> Result<()> {
let whpx_interrupt_regs =
WhpxInterruptRegs::from_serializable(AnySnapshot::from_any(data).map_err(|e| {
error!("failed to serialize interrupt state: {:?}", e);
Error::new(EIO)
})?);
let reg_names = WhpxInterruptRegs::get_register_names();
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_interrupt_regs.as_ptr(),
)
})
}
fn get_debugregs(&self) -> Result<DebugRegs> {
let mut whpx_debugregs: WhpxDebugRegs = Default::default();
let reg_names = WhpxDebugRegs::get_register_names();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_debugregs.as_mut_ptr(),
)
})?;
Ok(DebugRegs::from(&whpx_debugregs))
}
fn set_debugregs(&self, debugregs: &DebugRegs) -> Result<()> {
let whpx_debugregs = WhpxDebugRegs::from(debugregs);
let reg_names = WhpxDebugRegs::get_register_names();
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
reg_names as *const WHV_REGISTER_NAME,
reg_names.len() as u32,
whpx_debugregs.as_ptr(),
)
})
}
fn get_xcrs(&self) -> Result<BTreeMap<u32, u64>> {
const REG_NAME: WHV_REGISTER_NAME = WHV_REGISTER_NAME_WHvX64RegisterXCr0;
let mut reg_value = WHV_REGISTER_VALUE::default();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAME,
1,
&mut reg_value,
)
})?;
let xcr0 = unsafe { reg_value.Reg64 };
let xcrs = BTreeMap::from([(0, xcr0)]);
Ok(xcrs)
}
fn set_xcr(&self, xcr_index: u32, value: u64) -> Result<()> {
if xcr_index != 0 {
return Err(Error::new(EINVAL));
}
const REG_NAME: WHV_REGISTER_NAME = WHV_REGISTER_NAME_WHvX64RegisterXCr0;
let reg_value = WHV_REGISTER_VALUE { Reg64: value };
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAME,
1,
®_value,
)
})
}
fn get_msr(&self, msr_index: u32) -> Result<u64> {
let msr_name = get_msr_name(msr_index).ok_or(Error::new(libc::ENOENT))?;
let mut msr_value = WHV_REGISTER_VALUE::default();
check_whpx!(unsafe {
WHvGetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
&msr_name,
1,
&mut msr_value,
)
})?;
let value = unsafe { msr_value.Reg64 };
Ok(value)
}
fn get_all_msrs(&self) -> Result<BTreeMap<u32, u64>> {
const MSRS_TO_SAVE: &[u32] = &[
MSR_EFER,
MSR_KERNEL_GS_BASE,
MSR_APIC_BASE,
MSR_SYSENTER_CS,
MSR_SYSENTER_EIP,
MSR_SYSENTER_ESP,
MSR_STAR,
MSR_LSTAR,
MSR_CSTAR,
MSR_SFMASK,
];
let registers = MSRS_TO_SAVE
.iter()
.map(|msr_index| {
let value = self.get_msr(*msr_index)?;
Ok((*msr_index, value))
})
.collect::<Result<BTreeMap<u32, u64>>>()?;
Ok(registers)
}
fn set_msr(&self, msr_index: u32, value: u64) -> Result<()> {
match get_msr_name(msr_index) {
Some(msr_name) => {
let msr_value = WHV_REGISTER_VALUE { Reg64: value };
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
&msr_name,
1,
&msr_value,
)
})
}
None => {
warn!("msr 0x{msr_index:X} write unsupported by WHPX, dropping");
Ok(())
}
}
}
fn set_cpuid(&self, _cpuid: &CpuId) -> Result<()> {
Err(Error::new(ENXIO))
}
fn handle_cpuid(&mut self, entry: &CpuIdEntry) -> Result<()> {
if self.last_exit_context.ExitReason != WHV_RUN_VP_EXIT_REASON_WHvRunVpExitReasonX64Cpuid {
return Err(Error::new(EINVAL));
}
let rip = self.last_exit_context.VpContext.Rip
+ self.last_exit_context.VpContext.InstructionLength() as u64;
const REG_NAMES: [WHV_REGISTER_NAME; 5] = [
WHV_REGISTER_NAME_WHvX64RegisterRip,
WHV_REGISTER_NAME_WHvX64RegisterRax,
WHV_REGISTER_NAME_WHvX64RegisterRbx,
WHV_REGISTER_NAME_WHvX64RegisterRcx,
WHV_REGISTER_NAME_WHvX64RegisterRdx,
];
let values = vec![
WHV_REGISTER_VALUE { Reg64: rip },
WHV_REGISTER_VALUE {
Reg64: entry.cpuid.eax as u64,
},
WHV_REGISTER_VALUE {
Reg64: entry.cpuid.ebx as u64,
},
WHV_REGISTER_VALUE {
Reg64: entry.cpuid.ecx as u64,
},
WHV_REGISTER_VALUE {
Reg64: entry.cpuid.edx as u64,
},
];
check_whpx!(unsafe {
WHvSetVirtualProcessorRegisters(
self.vm_partition.partition,
self.index,
®_NAMES as *const WHV_REGISTER_NAME,
REG_NAMES.len() as u32,
values.as_ptr() as *const WHV_REGISTER_VALUE,
)
})
}
fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> {
Err(Error::new(ENOENT))
}
fn restore_timekeeping(&self, host_tsc_reference_moment: u64, tsc_offset: u64) -> Result<()> {
self.set_tsc_value(host_tsc_reference_moment.wrapping_add(tsc_offset))
}
}
fn get_msr_name(msr_index: u32) -> Option<WHV_REGISTER_NAME> {
VALID_MSRS.get(&msr_index).copied()
}
#[cfg(test)]
mod tests {
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
use super::*;
use crate::VmX86_64;
fn new_vm(cpu_count: usize, mem: GuestMemory) -> WhpxVm {
let whpx = Whpx::new().expect("failed to instantiate whpx");
let local_apic_supported = Whpx::check_whpx_feature(WhpxFeature::LocalApicEmulation)
.expect("failed to get whpx features");
WhpxVm::new(
&whpx,
cpu_count,
mem,
CpuId::new(0),
local_apic_supported,
None,
)
.expect("failed to create whpx vm")
}
#[test]
fn try_clone() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let vcpu: &WhpxVcpu = vcpu.downcast_ref().expect("Expected a WhpxVcpu");
let _vcpu_clone = vcpu.try_clone().expect("failed to clone whpx vcpu");
}
#[test]
fn index() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 2;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let mut vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let vcpu0: &WhpxVcpu = vcpu.downcast_ref().expect("Expected a WhpxVcpu");
assert_eq!(vcpu0.index, 0);
vcpu = vm.create_vcpu(1).expect("failed to create vcpu");
let vcpu1: &WhpxVcpu = vcpu.downcast_ref().expect("Expected a WhpxVcpu");
assert_eq!(vcpu1.index, 1);
}
#[test]
fn get_regs() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
vcpu.get_regs().expect("failed to get regs");
}
#[test]
fn set_regs() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let mut regs = vcpu.get_regs().expect("failed to get regs");
let new_val = regs.rax + 2;
regs.rax = new_val;
vcpu.set_regs(®s).expect("failed to set regs");
let new_regs = vcpu.get_regs().expect("failed to get regs");
assert_eq!(new_regs.rax, new_val);
}
#[test]
fn debugregs() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let mut dregs = vcpu.get_debugregs().unwrap();
dregs.dr7 += 13;
vcpu.set_debugregs(&dregs).unwrap();
let dregs2 = vcpu.get_debugregs().unwrap();
assert_eq!(dregs.dr7, dregs2.dr7);
}
#[test]
fn sregs() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let mut sregs = vcpu.get_sregs().unwrap();
sregs.cs.base += 7;
vcpu.set_sregs(&sregs).unwrap();
let sregs2 = vcpu.get_sregs().unwrap();
assert_eq!(sregs.cs.base, sregs2.cs.base);
}
#[test]
fn fpu() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let mut fpu = vcpu.get_fpu().unwrap();
fpu.fpr[0].significand += 3;
vcpu.set_fpu(&fpu).unwrap();
let fpu2 = vcpu.get_fpu().unwrap();
assert_eq!(fpu.fpr, fpu2.fpr);
}
#[test]
fn xcrs() {
if !Whpx::is_enabled() {
return;
}
let whpx = Whpx::new().expect("failed to instantiate whpx");
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
if !whpx.check_capability(HypervisorCap::Xcrs) {
return;
}
vcpu.set_xcr(0, 1).unwrap();
let xcrs = vcpu.get_xcrs().unwrap();
let xcr0 = xcrs.get(&0).unwrap();
assert_eq!(*xcr0, 1);
}
#[test]
fn set_msr() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
vcpu.set_msr(MSR_KERNEL_GS_BASE, 42).unwrap();
let gs_base = vcpu.get_msr(MSR_KERNEL_GS_BASE).unwrap();
assert_eq!(gs_base, 42);
}
#[test]
fn get_msr() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let _value = vcpu.get_msr(MSR_TSC).unwrap();
vcpu.get_msr(MSR_TSC + 1)
.expect_err("invalid MSR index should fail");
}
#[test]
fn set_efer() {
if !Whpx::is_enabled() {
return;
}
const EFER_SCE: u64 = 0x00000001;
const EFER_LME: u64 = 0x00000100;
const EFER_LMA: u64 = 0x00000400;
const X86_CR0_PE: u64 = 0x1;
const X86_CR0_PG: u64 = 0x80000000;
const X86_CR4_PAE: u64 = 0x20;
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let mut sregs = vcpu.get_sregs().expect("failed to get sregs");
assert_eq!(sregs.efer, 0);
sregs.cr0 |= X86_CR0_PE;
sregs.cr0 |= X86_CR0_PG;
sregs.cr4 |= X86_CR4_PAE;
sregs.efer = EFER_LMA | EFER_LME;
vcpu.set_sregs(&sregs).expect("failed to set sregs");
let sregs = vcpu.get_sregs().expect("failed to get sregs");
assert_eq!(sregs.efer, EFER_LMA | EFER_LME);
assert_eq!(sregs.cr0 & X86_CR0_PE, X86_CR0_PE);
assert_eq!(sregs.cr0 & X86_CR0_PG, X86_CR0_PG);
assert_eq!(sregs.cr4 & X86_CR4_PAE, X86_CR4_PAE);
let efer = vcpu.get_msr(MSR_EFER).expect("failed to get msr");
assert_eq!(efer, EFER_LMA | EFER_LME);
vcpu.set_msr(MSR_EFER, efer | EFER_SCE)
.expect("failed to set msr");
let sregs = vcpu.get_sregs().expect("failed to get sregs");
assert_eq!(sregs.efer, EFER_SCE | EFER_LME | EFER_LMA);
let new_efer = vcpu.get_msr(MSR_EFER).expect("failed to get msr");
assert_eq!(new_efer, EFER_SCE | EFER_LME | EFER_LMA);
}
#[test]
fn get_and_set_xsave_smoke() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let xsave = vcpu.get_xsave().unwrap();
vcpu.set_xsave(&xsave).unwrap();
}
#[test]
fn get_and_set_hypervisor_specific_state_smoke() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let interrupt_state = vcpu.get_hypervisor_specific_state().unwrap();
vcpu.set_hypervisor_specific_state(interrupt_state).unwrap();
}
#[test]
fn get_all_msrs() {
if !Whpx::is_enabled() {
return;
}
let cpu_count = 1;
let mem =
GuestMemory::new(&[(GuestAddress(0), 0x1000)]).expect("failed to create guest memory");
let vm = new_vm(cpu_count, mem);
let vcpu = vm.create_vcpu(0).expect("failed to create vcpu");
let all_msrs = vcpu.get_all_msrs().unwrap();
let apic_base = all_msrs.get(&MSR_APIC_BASE).unwrap();
assert_ne!(*apic_base, 0);
}
}