Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/hyperlight_host/src/hypervisor/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ use gdbstub::target::TargetError;
use thiserror::Error;
use x86_64_target::HyperlightSandboxTarget;

use super::InterruptHandle;
use super::regs::CommonRegisters;
use super::{Hypervisor, InterruptHandle};
use crate::hypervisor::regs::CommonFpu;
use crate::hypervisor::vm::Vm;
use crate::mem::layout::SandboxMemoryLayout;
use crate::mem::memory_region::MemoryRegion;
use crate::mem::mgr::SandboxMemoryManager;
Expand Down Expand Up @@ -275,8 +276,8 @@ pub(crate) enum DebugResponse {
}

/// Trait for VMs that support debugging capabilities.
/// This extends the base Hypervisor trait with GDB-specific functionality.
pub(crate) trait DebuggableVm: Hypervisor {
/// This extends the base Vm trait with GDB-specific functionality.
pub(crate) trait DebuggableVm: Vm {
/// Translates a guest virtual address to a guest physical address
fn translate_gva(&self, gva: u64) -> crate::Result<u64>;

Expand Down
46 changes: 22 additions & 24 deletions src/hyperlight_host/src/hypervisor/hyperlight_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,30 @@ use super::regs::{CommonFpu, CommonRegisters};
#[cfg(target_os = "windows")]
use super::{PartitionState, WindowsInterruptHandle};
use crate::HyperlightError::{ExecutionCanceledByHost, NoHypervisorFound};
#[cfg(not(gdb))]
use crate::hypervisor::Hypervisor;
#[cfg(any(kvm, mshv3))]
use crate::hypervisor::LinuxInterruptHandle;
#[cfg(crashdump)]
use crate::hypervisor::crashdump;
use crate::hypervisor::regs::CommonSpecialRegisters;
#[cfg(not(gdb))]
use crate::hypervisor::vm::Vm;
#[cfg(kvm)]
use crate::hypervisor::vm::kvm::KvmVm;
#[cfg(mshv3)]
use crate::hypervisor::hyperv_linux::MshvVm;
use crate::hypervisor::vm::mshv::MshvVm;
#[cfg(target_os = "windows")]
use crate::hypervisor::hyperv_windows::WhpVm;
#[cfg(kvm)]
use crate::hypervisor::kvm::KvmVm;
use crate::hypervisor::regs::CommonSpecialRegisters;
use crate::hypervisor::vm::whp::WhpVm;
use crate::hypervisor::vm::{HypervisorType, VmExit, get_available_hypervisor};
#[cfg(target_os = "windows")]
use crate::hypervisor::wrappers::HandleWrapper;
use crate::hypervisor::{HyperlightExit, InterruptHandle, InterruptHandleImpl, get_max_log_level};
use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level};
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
use crate::mem::mgr::SandboxMemoryManager;
use crate::mem::ptr::{GuestPtr, RawPtr};
use crate::mem::shared_mem::HostSharedMemory;
use crate::metrics::{METRIC_ERRONEOUS_VCPU_KICKS, METRIC_GUEST_CANCELLATION};
use crate::sandbox::SandboxConfiguration;
use crate::sandbox::host_funcs::FunctionRegistry;
use crate::sandbox::hypervisor::{HypervisorType, get_available_hypervisor};
use crate::sandbox::outb::handle_outb;
#[cfg(feature = "mem_profile")]
use crate::sandbox::trace::MemTraceInfo;
Expand All @@ -77,7 +77,7 @@ pub(crate) struct HyperlightVm {
#[cfg(gdb)]
vm: Box<dyn DebuggableVm>,
#[cfg(not(gdb))]
vm: Box<dyn Hypervisor>,
vm: Box<dyn Vm>,
page_size: usize,
entrypoint: u64,
orig_rsp: GuestPtr,
Expand Down Expand Up @@ -117,7 +117,7 @@ impl HyperlightVm {
#[cfg(gdb)]
type VmType = Box<dyn DebuggableVm>;
#[cfg(not(gdb))]
type VmType = Box<dyn Hypervisor>;
type VmType = Box<dyn Vm>;

#[cfg_attr(not(gdb), allow(unused_mut))]
let mut vm: VmType = match get_available_hypervisor() {
Expand Down Expand Up @@ -368,7 +368,7 @@ impl HyperlightVm {
let result = loop {
// ===== KILL() TIMING POINT 2: Before set_tid() =====
// If kill() is called and ran to completion BEFORE this line executes:
// - CANCEL_BIT will be set and we will return an early HyperlightExit::Cancelled()
// - CANCEL_BIT will be set and we will return an early VmExit::Cancelled()
// without sending any signals/WHV api calls
#[cfg(any(kvm, mshv3))]
self.interrupt_handle.set_tid();
Expand All @@ -379,7 +379,7 @@ impl HyperlightVm {
let exit_reason = if self.interrupt_handle.is_cancelled()
|| self.interrupt_handle.is_debug_interrupted()
{
Ok(HyperlightExit::Cancelled())
Ok(VmExit::Cancelled())
} else {
#[cfg(feature = "trace_guest")]
tc.setup_guest_trace(Span::current().context());
Expand Down Expand Up @@ -424,7 +424,7 @@ impl HyperlightVm {
// - Signals will not be sent
match exit_reason {
#[cfg(gdb)]
Ok(HyperlightExit::Debug { dr6, exception }) => {
Ok(VmExit::Debug { dr6, exception }) => {
// Handle debug event (breakpoints)
let stop_reason =
arch::vcpu_stop_reason(self.vm.as_mut(), dr6, self.entrypoint, exception)?;
Expand All @@ -433,13 +433,11 @@ impl HyperlightVm {
}
}

Ok(HyperlightExit::Halt()) => {
Ok(VmExit::Halt()) => {
break Ok(());
}
Ok(HyperlightExit::IoOut(port, data)) => {
self.handle_io(mem_mgr, host_funcs, port, data)?
}
Ok(HyperlightExit::MmioRead(addr)) => {
Ok(VmExit::IoOut(port, data)) => self.handle_io(mem_mgr, host_funcs, port, data)?,
Ok(VmExit::MmioRead(addr)) => {
let all_regions = self.sandbox_regions.iter().chain(self.get_mapped_regions());
match get_memory_access_violation(
addr as usize,
Expand All @@ -465,7 +463,7 @@ impl HyperlightVm {
}
}
}
Ok(HyperlightExit::MmioWrite(addr)) => {
Ok(VmExit::MmioWrite(addr)) => {
let all_regions = self.sandbox_regions.iter().chain(self.get_mapped_regions());
match get_memory_access_violation(
addr as usize,
Expand All @@ -491,15 +489,15 @@ impl HyperlightVm {
}
}
}
Ok(HyperlightExit::Cancelled()) => {
Ok(VmExit::Cancelled()) => {
// If cancellation was not requested for this specific guest function call,
// the vcpu was interrupted by a stale cancellation. This can occur when:
// - Linux: A signal from a previous call arrives late
// - Windows: WHvCancelRunVirtualProcessor called right after vcpu exits but RUNNING_BIT is still true
if !cancel_requested && !debug_interrupted {
// Track that an erroneous vCPU kick occurred
metrics::counter!(METRIC_ERRONEOUS_VCPU_KICKS).increment(1);
// treat this the same as a HyperlightExit::Retry, the cancel was not meant for this call
// treat this the same as a VmExit::Retry, the cancel was not meant for this call
continue;
}

Expand All @@ -517,10 +515,10 @@ impl HyperlightVm {
metrics::counter!(METRIC_GUEST_CANCELLATION).increment(1);
break Err(ExecutionCanceledByHost());
}
Ok(HyperlightExit::Unknown(reason)) => {
Ok(VmExit::Unknown(reason)) => {
break Err(new_error!("Unexpected VM Exit: {:?}", reason));
}
Ok(HyperlightExit::Retry()) => continue,
Ok(VmExit::Retry()) => continue,
Err(e) => {
break Err(e);
}
Expand Down
91 changes: 1 addition & 90 deletions src/hyperlight_host/src/hypervisor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,14 @@ limitations under the License.

use log::LevelFilter;

use crate::Result;
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
use crate::mem::memory_region::MemoryRegion;

/// HyperV-on-linux functionality
#[cfg(mshv3)]
pub(crate) mod hyperv_linux;
#[cfg(target_os = "windows")]
pub(crate) mod hyperv_windows;

/// GDB debugging support
#[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(crate) mod kvm;
pub(crate) mod vm;

#[cfg(target_os = "windows")]
/// Hyperlight Surrogate Process
Expand All @@ -61,83 +49,6 @@ use std::sync::atomic::{AtomicU8, Ordering};
#[cfg(any(kvm, mshv3))]
use std::time::Duration;

pub(crate) enum HyperlightExit {
/// The vCPU has exited due to a debug event (usually breakpoint)
#[cfg(gdb)]
Debug { dr6: u64, exception: u32 },
/// The vCPU has halted
Halt(),
/// The vCPU has issued a write to the given port with the given value
IoOut(u16, Vec<u8>),
/// The vCPU tried to read from the given (unmapped) addr
MmioRead(u64),
/// The vCPU tried to write to the given (unmapped) addr
MmioWrite(u64),
/// The vCPU execution has been cancelled
Cancelled(),
/// The vCPU has exited for a reason that is not handled by Hyperlight
Unknown(String),
/// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN
#[cfg_attr(
target_os = "windows",
expect(
dead_code,
reason = "Retry() is never constructed on Windows, but it is still matched on (which dead_code lint ignores)"
)
)]
Retry(),
}

/// Trait for single-vCPU VMs. Provides a common interface for basic VM operations.
/// Abstracts over differences between KVM, MSHV and WHP implementations.
pub(crate) trait Hypervisor: Debug + Send {
/// Map memory region into this VM
///
/// # Safety
/// The caller must ensure that the memory region is valid and points to valid memory,
/// and lives long enough for the VM to use it.
/// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped
/// memory regions may be overwritten.
/// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries.
unsafe fn map_memory(&mut self, region: (u32, &MemoryRegion)) -> Result<()>;

/// Unmap memory region from this VM that has previously been mapped using `map_memory`.
fn unmap_memory(&mut self, region: (u32, &MemoryRegion)) -> Result<()>;

/// Runs the vCPU until it exits.
/// Note: this function should not emit any traces or spans as it is called after guest span is setup
fn run_vcpu(&mut self) -> Result<HyperlightExit>;

/// Get regs
#[allow(dead_code)]
fn regs(&self) -> Result<CommonRegisters>;
/// Set regs
fn set_regs(&self, regs: &CommonRegisters) -> Result<()>;
/// Get fpu regs
#[allow(dead_code)]
fn fpu(&self) -> Result<CommonFpu>;
/// Set fpu regs
fn set_fpu(&self, fpu: &CommonFpu) -> Result<()>;
/// Get special regs
#[allow(dead_code)]
fn sregs(&self) -> Result<CommonSpecialRegisters>;
/// Set special regs
fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()>;

/// xsave
#[cfg(crashdump)]
fn xsave(&self) -> Result<Vec<u8>>;

/// Get partition handle
#[cfg(target_os = "windows")]
fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;

/// Mark that initial memory setup is complete. After this, map_memory will fail.
/// This is only needed on Windows where dynamic memory mapping is not yet supported.
#[cfg(target_os = "windows")]
fn complete_initial_memory_setup(&mut self);
}

/// Get the logging level to pass to the guest entrypoint
fn get_max_log_level() -> u32 {
// Check to see if the RUST_LOG environment variable is set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use tracing::{Span, instrument};

#[cfg(gdb)]
use crate::hypervisor::gdb::DebuggableVm;
use crate::hypervisor::{HyperlightExit, Hypervisor};
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
use crate::hypervisor::vm::{Vm, VmExit};
use crate::mem::memory_region::MemoryRegion;
use crate::{Result, new_error};

Expand Down Expand Up @@ -84,7 +85,7 @@ impl KvmVm {
}
}

impl Hypervisor for KvmVm {
impl Vm for KvmVm {
unsafe fn map_memory(&mut self, (slot, region): (u32, &MemoryRegion)) -> Result<()> {
let mut kvm_region: kvm_userspace_memory_region = region.into();
kvm_region.slot = slot;
Expand All @@ -103,61 +104,58 @@ impl Hypervisor for KvmVm {
Ok(())
}

fn run_vcpu(&mut self) -> Result<HyperlightExit> {
fn run_vcpu(&mut self) -> Result<VmExit> {
match self.vcpu_fd.run() {
Ok(VcpuExit::Hlt) => Ok(HyperlightExit::Halt()),
Ok(VcpuExit::IoOut(port, data)) => Ok(HyperlightExit::IoOut(port, data.to_vec())),
Ok(VcpuExit::MmioRead(addr, _)) => Ok(HyperlightExit::MmioRead(addr)),
Ok(VcpuExit::MmioWrite(addr, _)) => Ok(HyperlightExit::MmioWrite(addr)),
Ok(VcpuExit::Hlt) => Ok(VmExit::Halt()),
Ok(VcpuExit::IoOut(port, data)) => Ok(VmExit::IoOut(port, data.to_vec())),
Ok(VcpuExit::MmioRead(addr, _)) => Ok(VmExit::MmioRead(addr)),
Ok(VcpuExit::MmioWrite(addr, _)) => Ok(VmExit::MmioWrite(addr)),
#[cfg(gdb)]
Ok(VcpuExit::Debug(debug_exit)) => Ok(HyperlightExit::Debug {
Ok(VcpuExit::Debug(debug_exit)) => Ok(VmExit::Debug {
dr6: debug_exit.dr6,
exception: debug_exit.exception,
}),
Err(e) => match e.errno() {
// InterruptHandle::kill() sends a signal (SIGRTMIN+offset) to interrupt the vcpu, which causes EINTR
libc::EINTR => Ok(HyperlightExit::Cancelled()),
libc::EAGAIN => Ok(HyperlightExit::Retry()),
_ => Ok(HyperlightExit::Unknown(format!(
"Unknown KVM VCPU error: {}",
e
))),
libc::EINTR => Ok(VmExit::Cancelled()),
libc::EAGAIN => Ok(VmExit::Retry()),
_ => Ok(VmExit::Unknown(format!("Unknown KVM VCPU error: {}", e))),
},
Ok(other) => Ok(HyperlightExit::Unknown(format!(
Ok(other) => Ok(VmExit::Unknown(format!(
"Unknown KVM VCPU exit: {:?}",
other
))),
}
}

fn regs(&self) -> Result<super::regs::CommonRegisters> {
fn regs(&self) -> Result<CommonRegisters> {
let kvm_regs = self.vcpu_fd.get_regs()?;
Ok((&kvm_regs).into())
}

fn set_regs(&self, regs: &super::regs::CommonRegisters) -> Result<()> {
fn set_regs(&self, regs: &CommonRegisters) -> Result<()> {
let kvm_regs: kvm_regs = regs.into();
self.vcpu_fd.set_regs(&kvm_regs)?;
Ok(())
}

fn fpu(&self) -> Result<super::regs::CommonFpu> {
fn fpu(&self) -> Result<CommonFpu> {
let kvm_fpu = self.vcpu_fd.get_fpu()?;
Ok((&kvm_fpu).into())
}

fn set_fpu(&self, fpu: &super::regs::CommonFpu) -> Result<()> {
fn set_fpu(&self, fpu: &CommonFpu) -> Result<()> {
let kvm_fpu: kvm_fpu = fpu.into();
self.vcpu_fd.set_fpu(&kvm_fpu)?;
Ok(())
}

fn sregs(&self) -> Result<super::regs::CommonSpecialRegisters> {
fn sregs(&self) -> Result<CommonSpecialRegisters> {
let kvm_sregs = self.vcpu_fd.get_sregs()?;
Ok((&kvm_sregs).into())
}

fn set_sregs(&self, sregs: &super::regs::CommonSpecialRegisters) -> Result<()> {
fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> Result<()> {
let kvm_sregs: kvm_sregs = sregs.into();
self.vcpu_fd.set_sregs(&kvm_sregs)?;
Ok(())
Expand Down
Loading
Loading