use std::fmt::Debug;
use std::sync::OnceLock;
use tracing::{Span, instrument};
#[cfg(gdb)]
use crate::hypervisor::gdb::DebugError;
use crate::hypervisor::regs::{
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
};
use crate::mem::memory_region::MemoryRegion;
#[cfg(feature = "trace_guest")]
use crate::sandbox::trace::TraceContext as SandboxTraceContext;
#[cfg(kvm)]
pub(crate) mod kvm;
#[cfg(mshv3)]
pub(crate) mod mshv;
#[cfg(target_os = "windows")]
pub(crate) mod whp;
#[cfg(feature = "hw-interrupts")]
pub(crate) mod x86_64;
static AVAILABLE_HYPERVISOR: OnceLock<Option<HypervisorType>> = OnceLock::new();
pub fn get_available_hypervisor() -> &'static Option<HypervisorType> {
AVAILABLE_HYPERVISOR.get_or_init(|| {
cfg_if::cfg_if! {
if #[cfg(all(kvm, mshv3))] {
if mshv::is_hypervisor_present() {
Some(HypervisorType::Mshv)
} else if kvm::is_hypervisor_present() {
Some(HypervisorType::Kvm)
} else {
None
}
} else if #[cfg(kvm)] {
if kvm::is_hypervisor_present() {
Some(HypervisorType::Kvm)
} else {
None
}
} else if #[cfg(mshv3)] {
if mshv::is_hypervisor_present() {
Some(HypervisorType::Mshv)
} else {
None
}
} else if #[cfg(target_os = "windows")] {
if whp::is_hypervisor_present() {
Some(HypervisorType::Whp)
} else {
None
}
} else {
None
}
}
})
}
#[instrument(skip_all, parent = Span::current())]
pub fn is_hypervisor_present() -> bool {
get_available_hypervisor().is_some()
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub(crate) enum HypervisorType {
#[cfg(kvm)]
Kvm,
#[cfg(mshv3)]
Mshv,
#[cfg(target_os = "windows")]
Whp,
}
#[cfg(any(mshv3, target_os = "windows"))]
pub(crate) const XSAVE_MIN_SIZE: usize = 576;
#[cfg(all(any(kvm, mshv3), test, not(feature = "nanvix-unstable")))]
pub(crate) const XSAVE_BUFFER_SIZE: usize = 4096;
#[cfg(not(any(kvm, mshv3, target_os = "windows", target_arch = "aarch64")))]
compile_error!(
"No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature."
);
pub(crate) enum VmExit {
#[cfg(gdb)]
Debug {
#[cfg(target_arch = "x86_64")]
dr6: u64,
#[cfg(target_arch = "x86_64")]
exception: u32,
},
Halt(),
IoOut(u16, Vec<u8>),
MmioRead(u64),
MmioWrite(u64),
Cancelled(),
Unknown(String),
#[cfg_attr(
any(target_os = "windows", feature = "hw-interrupts"),
expect(
dead_code,
reason = "Retry() is never constructed on Windows or with hw-interrupts (EAGAIN causes continue instead)"
)
)]
Retry(),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum VmError {
#[error("Failed to create vm: {0}")]
CreateVm(#[from] CreateVmError),
#[cfg(gdb)]
#[error("Debug operation failed: {0}")]
Debug(#[from] DebugError),
#[error("Map memory operation failed: {0}")]
MapMemory(#[from] MapMemoryError),
#[error("Register operation failed: {0}")]
Register(#[from] RegisterError),
#[error("Failed to run vcpu: {0}")]
RunVcpu(#[from] RunVcpuError),
#[error("Unmap memory operation failed: {0}")]
UnmapMemory(#[from] UnmapMemoryError),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum CreateVmError {
#[error("VCPU creation failed: {0}")]
CreateVcpuFd(HypervisorError),
#[error("VM creation failed: {0}")]
CreateVmFd(HypervisorError),
#[error("Hypervisor is not available: {0}")]
HypervisorNotAvailable(HypervisorError),
#[error("Initialize VM failed: {0}")]
InitializeVm(HypervisorError),
#[error("Set Partition Property failed: {0}")]
SetPartitionProperty(HypervisorError),
#[cfg(target_os = "windows")]
#[error("Surrogate process creation failed: {0}")]
SurrogateProcess(String),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum RunVcpuError {
#[error("Failed to decode message type: {0}")]
DecodeIOMessage(u32),
#[cfg(gdb)]
#[error("Failed to get DR6 debug register: {0}")]
GetDr6(HypervisorError),
#[error("Increment RIP failed: {0}")]
IncrementRip(HypervisorError),
#[error("Parse GPA access info failed")]
ParseGpaAccessInfo,
#[error("Unknown error: {0}")]
Unknown(HypervisorError),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum RegisterError {
#[error("Failed to get registers: {0}")]
GetRegs(HypervisorError),
#[error("Failed to set registers: {0}")]
SetRegs(HypervisorError),
#[error("Failed to get FPU registers: {0}")]
GetFpu(HypervisorError),
#[error("Failed to set FPU registers: {0}")]
SetFpu(HypervisorError),
#[error("Failed to get special registers: {0}")]
GetSregs(HypervisorError),
#[error("Failed to set special registers: {0}")]
SetSregs(HypervisorError),
#[error("Failed to get debug registers: {0}")]
GetDebugRegs(HypervisorError),
#[error("Failed to set debug registers: {0}")]
SetDebugRegs(HypervisorError),
#[error("Failed to get xsave: {0}")]
GetXsave(HypervisorError),
#[error("Failed to set xsave: {0}")]
SetXsave(HypervisorError),
#[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
XsaveSizeMismatch {
expected: u32,
actual: u32,
},
#[error("Invalid xsave alignment")]
InvalidXsaveAlignment,
#[cfg(target_os = "windows")]
#[error("Failed to get xsave size: {0}")]
GetXsaveSize(#[from] HypervisorError),
#[cfg(target_os = "windows")]
#[error("Failed to convert WHP registers: {0}")]
ConversionFailed(String),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum MapMemoryError {
#[cfg(target_os = "windows")]
#[error("Address conversion failed: {0}")]
AddressConversion(std::num::TryFromIntError),
#[error("Hypervisor error: {0}")]
Hypervisor(HypervisorError),
#[cfg(target_os = "windows")]
#[error("Invalid memory region flags: {0}")]
InvalidFlags(String),
#[cfg(target_os = "windows")]
#[error("Failed to load API '{api_name}': {source}")]
LoadApi {
api_name: &'static str,
source: windows_result::Error,
},
#[cfg(target_os = "windows")]
#[error("Operation not supported: {0}")]
NotSupported(String),
#[cfg(target_os = "windows")]
#[error("Surrogate process creation failed: {0}")]
SurrogateProcess(String),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum UnmapMemoryError {
#[error("Hypervisor error: {0}")]
Hypervisor(HypervisorError),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum HypervisorError {
#[cfg(kvm)]
#[error("KVM error: {0}")]
KvmError(#[from] kvm_ioctls::Error),
#[cfg(mshv3)]
#[error("MSHV error: {0}")]
MshvError(#[from] mshv_ioctls::MshvError),
#[cfg(target_os = "windows")]
#[error("Windows error: {0}")]
WindowsError(#[from] windows_result::Error),
}
pub(crate) trait VirtualMachine: Debug + Send {
unsafe fn map_memory(
&mut self,
region: (u32, &MemoryRegion),
) -> std::result::Result<(), MapMemoryError>;
fn unmap_memory(
&mut self,
region: (u32, &MemoryRegion),
) -> std::result::Result<(), UnmapMemoryError>;
fn run_vcpu(
&mut self,
#[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
) -> std::result::Result<VmExit, RunVcpuError>;
#[allow(dead_code)]
fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
#[allow(dead_code)]
fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
#[allow(dead_code)]
fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
#[allow(dead_code)]
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
#[allow(dead_code)]
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
#[cfg(test)]
#[cfg(not(feature = "nanvix-unstable"))]
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
#[cfg(target_os = "windows")]
fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(target_os = "linux")]
fn is_hypervisor_present() {
use std::path::Path;
cfg_if::cfg_if! {
if #[cfg(all(kvm, mshv3))] {
assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
} else if #[cfg(kvm)] {
assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
} else if #[cfg(mshv3)] {
assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
} else {
assert!(!super::is_hypervisor_present());
}
}
}
}