Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/hyperlight_host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ metrics = "0.24.3"
serde_json = "1.0"
elfcore = "2.0"
uuid = { version = "1.19.0", features = ["v4"] }
displaydoc = "0.2.5"

[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = [
Expand Down
35 changes: 14 additions & 21 deletions src/hyperlight_host/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, Ret
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
use thiserror::Error;

use crate::hypervisor::hyperlight_vm::HyperlightVmError;
#[cfg(target_os = "windows")]
use crate::hypervisor::wrappers::HandleWrapper;
use crate::mem::memory_region::MemoryRegionFlags;
Expand Down Expand Up @@ -111,6 +112,10 @@ pub enum HyperlightError {
#[error("HostFunction {0} was not found")]
HostFunctionNotFound(String),

/// Hyperlight VM error
#[error("Hyperlight VM error: {0}")]
HyperlightVmError(#[from] HyperlightVmError),

/// Reading Writing or Seeking data failed.
#[error("Reading Writing or Seeking data failed {0:?}")]
IOError(#[from] std::io::Error),
Expand All @@ -127,11 +132,6 @@ pub enum HyperlightError {
#[error("Conversion of str data to json failed")]
JsonConversionFailure(#[from] serde_json::Error),

/// KVM Error Occurred
#[error("KVM Error {0:?}")]
#[cfg(kvm)]
KVMError(#[from] kvm_ioctls::Error),

/// An attempt to get a lock from a Mutex failed.
#[error("Unable to lock resource")]
LockAttemptFailed(String),
Expand Down Expand Up @@ -168,11 +168,6 @@ pub enum HyperlightError {
#[error("mprotect failed with os error {0:?}")]
MprotectFailed(Option<i32>),

/// mshv Error Occurred
#[error("mshv Error {0:?}")]
#[cfg(mshv3)]
MSHVError(#[from] mshv_ioctls::MshvError),

/// No Hypervisor was found for Sandbox.
#[error("No Hypervisor was found for Sandbox")]
NoHypervisorFound(),
Expand Down Expand Up @@ -242,11 +237,6 @@ pub enum HyperlightError {
#[error("SystemTimeError {0:?}")]
SystemTimeError(#[from] SystemTimeError),

/// Error occurred when translating guest address
#[error("An error occurred when translating guest address: {0:?}")]
#[cfg(gdb)]
TranslateGuestAddress(u64),

/// Error occurred converting a slice to an array
#[error("TryFromSliceError {0:?}")]
TryFromSliceError(#[from] TryFromSliceError),
Expand Down Expand Up @@ -334,6 +324,11 @@ impl HyperlightError {
| HyperlightError::SnapshotSizeMismatch(_, _)
| HyperlightError::MemoryRegionSizeMismatch(_, _, _) => true,

// HyperlightVmError::DispatchGuestCall may poison the sandbox
HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(e)) => {
e.is_poison_error()
}

// All other errors do not poison the sandbox.
HyperlightError::AnyhowError(_)
| HyperlightError::BoundsCheckFailed(_, _)
Expand All @@ -348,6 +343,10 @@ impl HyperlightError {
| HyperlightError::GuestInterfaceUnsupportedType(_)
| HyperlightError::GuestOffsetIsInvalid(_)
| HyperlightError::HostFunctionNotFound(_)
| HyperlightError::HyperlightVmError(HyperlightVmError::Create(_))
| HyperlightError::HyperlightVmError(HyperlightVmError::Initialize(_))
| HyperlightError::HyperlightVmError(HyperlightVmError::MapRegion(_))
| HyperlightError::HyperlightVmError(HyperlightVmError::UnmapRegion(_))
| HyperlightError::IOError(_)
| HyperlightError::IntConversionFailure(_)
| HyperlightError::InvalidFlatBuffer(_)
Expand Down Expand Up @@ -384,12 +383,6 @@ impl HyperlightError {
HyperlightError::WindowsAPIError(_) => false,
#[cfg(target_os = "linux")]
HyperlightError::VmmSysError(_) => false,
#[cfg(kvm)]
HyperlightError::KVMError(_) => false,
#[cfg(mshv3)]
HyperlightError::MSHVError(_) => false,
#[cfg(gdb)]
HyperlightError::TranslateGuestAddress(_) => false,
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/hyperlight_host/src/hypervisor/gdb/arch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ limitations under the License.

//! This file contains architecture specific code for the x86_64

use super::{DebuggableVm, VcpuStopReason};
use crate::Result;
use super::{DebugError, DebuggableVm, VcpuStopReason};
use crate::hypervisor::regs::CommonRegisters;
use crate::hypervisor::virtual_machine::RegisterError;

/// Errors that can occur when determining the vCPU stop reason
#[derive(Debug, thiserror::Error, displaydoc::Display)]
Copy link
Member

@andreiltd andreiltd Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use displaydoc and not display from thiserror? At first glance this looks redundant as one could just write:

pub enum VcpuStopReasonError {
    #[error(Failed to get registers: {0})]
    GetRegs(#[from] RegisterError),
    ...
}

Copy link
Contributor Author

@ludfjig ludfjig Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, however each variant also requires a rustdoc comment, otherwise we get error: missing documentation for a variant. Because the comment was generally the same as what's passed to #[error()], I thought that this makes it a bit cleaner

pub enum VcpuStopReasonError {
/// Failed to get registers: {0}
GetRegs(#[from] RegisterError),
/// Failed to remove hardware breakpoint: {0}
RemoveHwBreakpoint(#[from] DebugError),
}

// Described in Table 6-1. Exceptions and Interrupts at Page 6-13 Vol. 1
// of Intel 64 and IA-32 Architectures Software Developer's Manual
Expand Down Expand Up @@ -56,7 +65,7 @@ pub(crate) fn vcpu_stop_reason(
dr6: u64,
entrypoint: u64,
exception: u32,
) -> Result<VcpuStopReason> {
) -> std::result::Result<VcpuStopReason, VcpuStopReasonError> {
let CommonRegisters { rip, .. } = vm.regs()?;
if DB_EX_ID == exception {
// If the BS flag in DR6 register is set, it means a single step
Expand Down
95 changes: 71 additions & 24 deletions src/hyperlight_host/src/hypervisor/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ use x86_64_target::HyperlightSandboxTarget;

use super::InterruptHandle;
use super::regs::CommonRegisters;
use crate::HyperlightError;
use crate::hypervisor::regs::CommonFpu;
use crate::hypervisor::virtual_machine::VirtualMachine;
use crate::hypervisor::virtual_machine::{HypervisorImplError, RegisterError, VirtualMachine};
use crate::mem::layout::SandboxMemoryLayout;
use crate::mem::memory_region::MemoryRegion;
use crate::mem::mgr::SandboxMemoryManager;
use crate::mem::shared_mem::HostSharedMemory;
use crate::{HyperlightError, new_error};

#[derive(Debug, Error)]
pub(crate) enum GdbTargetError {
pub enum GdbTargetError {
#[error("Error encountered while binding to address and port")]
CannotBind,
#[error("Error encountered while listening for connections")]
Expand Down Expand Up @@ -86,6 +86,17 @@ pub(crate) struct DebugMemoryAccess {
pub(crate) guest_mmap_regions: Vec<MemoryRegion>,
}

/// Errors that can occur during debug memory access operations
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum DebugMemoryAccessError {
/// Failed to translate guest address {0:#x}
TranslateGuestAddress(u64),
/// Failed to acquire lock at {0}:{1} - {2}
LockFailed(&'static str, u32, String),
/// Failed to copy memory: {0}
CopyFailed(Box<HyperlightError>),
}

impl DebugMemoryAccess {
/// Reads memory from the guest's address space with a maximum length of a PAGE_SIZE
///
Expand All @@ -94,8 +105,12 @@ impl DebugMemoryAccess {
/// * `gpa` - Guest physical address to read from.
/// This address is shall be translated before calling this function
/// # Returns
/// * `Result<(), HyperlightError>` - Ok if successful, Err otherwise
pub(crate) fn read(&self, data: &mut [u8], gpa: u64) -> crate::Result<()> {
/// * `Result<(), DebugMemoryAccessError>` - Ok if successful, Err otherwise
pub(crate) fn read(
&self,
data: &mut [u8],
gpa: u64,
) -> std::result::Result<(), DebugMemoryAccessError> {
let read_len = data.len();

let mem_offset = (gpa as usize)
Expand All @@ -107,7 +122,7 @@ impl DebugMemoryAccess {
gpa,
SandboxMemoryLayout::BASE_ADDRESS
);
HyperlightError::TranslateGuestAddress(gpa)
DebugMemoryAccessError::TranslateGuestAddress(gpa)
})?;

// First check the mapped memory regions to see if the address is within any of them
Expand All @@ -123,7 +138,7 @@ impl DebugMemoryAccess {
mem_offset,
reg.guest_region.start,
);
HyperlightError::TranslateGuestAddress(mem_offset as u64)
DebugMemoryAccessError::TranslateGuestAddress(mem_offset as u64)
})?;

let bytes: &[u8] = unsafe {
Expand All @@ -144,9 +159,10 @@ impl DebugMemoryAccess {

self.dbg_mem_access_fn
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?
.get_shared_mem_mut()
.copy_to_slice(&mut data[..read_len], mem_offset)?;
.copy_to_slice(&mut data[..read_len], mem_offset)
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))?;
}

Ok(())
Expand All @@ -159,8 +175,12 @@ impl DebugMemoryAccess {
/// * `gpa` - Guest physical address to write to.
/// This address is shall be translated before calling this function
/// # Returns
/// * `Result<(), HyperlightError>` - Ok if successful, Err otherwise
pub(crate) fn write(&self, data: &[u8], gpa: u64) -> crate::Result<()> {
/// * `Result<(), DebugMemoryAccessError>` - Ok if successful, Err otherwise
pub(crate) fn write(
&self,
data: &[u8],
gpa: u64,
) -> std::result::Result<(), DebugMemoryAccessError> {
let write_len = data.len();

let mem_offset = (gpa as usize)
Expand All @@ -172,7 +192,7 @@ impl DebugMemoryAccess {
gpa,
SandboxMemoryLayout::BASE_ADDRESS
);
HyperlightError::TranslateGuestAddress(gpa)
DebugMemoryAccessError::TranslateGuestAddress(gpa)
})?;

// First check the mapped memory regions to see if the address is within any of them
Expand All @@ -188,7 +208,7 @@ impl DebugMemoryAccess {
mem_offset,
reg.guest_region.start,
);
HyperlightError::TranslateGuestAddress(mem_offset as u64)
DebugMemoryAccessError::TranslateGuestAddress(mem_offset as u64)
})?;

let bytes: &mut [u8] = unsafe {
Expand All @@ -213,9 +233,10 @@ impl DebugMemoryAccess {

self.dbg_mem_access_fn
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?
.get_shared_mem_mut()
.copy_from_slice(&data[..write_len], mem_offset)?;
.copy_from_slice(&data[..write_len], mem_offset)
.map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))?;
}

Ok(())
Expand Down Expand Up @@ -275,24 +296,42 @@ pub(crate) enum DebugResponse {
WriteRegisters,
}

/// Errors that can occur during debug operations
#[derive(Debug, Clone, thiserror::Error, displaydoc::Display)]
pub enum DebugError {
/// Hardware breakpoint not found at address {0:#x}
HwBreakpointNotFound(u64),
/// Maximum hardware breakpoints ({0}) exceeded
TooManyHwBreakpoints(usize),
/// Register operation failed: {0}
Register(#[from] RegisterError),
/// Translation of guest virtual address failed: {0}
TranslateGva(u64),
/// Failed to enable/disable intercept: {enable}, {inner}
Intercept {
enable: bool,
inner: HypervisorImplError,
},
}

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

/// Enable/disable debugging
fn set_debug(&mut self, enable: bool) -> crate::Result<()>;
fn set_debug(&mut self, enable: bool) -> std::result::Result<(), DebugError>;

/// Enable/disable single stepping
fn set_single_step(&mut self, enable: bool) -> crate::Result<()>;
fn set_single_step(&mut self, enable: bool) -> std::result::Result<(), DebugError>;

/// Add a hardware breakpoint at the given address.
/// Must be idempotent.
fn add_hw_breakpoint(&mut self, addr: u64) -> crate::Result<()>;
fn add_hw_breakpoint(&mut self, addr: u64) -> std::result::Result<(), DebugError>;

/// Remove a hardware breakpoint at the given address
fn remove_hw_breakpoint(&mut self, addr: u64) -> crate::Result<()>;
fn remove_hw_breakpoint(&mut self, addr: u64) -> std::result::Result<(), DebugError>;
}

/// Debug communication channel that is used for sending a request type and
Expand Down Expand Up @@ -513,7 +552,9 @@ mod tests {
}

let mut read_data = [0u8; 1];
mem_access.read(&mut read_data, (BASE_VIRT + offset) as u64)?;
mem_access
.read(&mut read_data, (BASE_VIRT + offset) as u64)
.unwrap();

assert_eq!(read_data[0], 0xAA);

Expand All @@ -536,7 +577,9 @@ mod tests {
}

let mut read_data = [0u8; 16];
mem_access.read(&mut read_data, (BASE_VIRT + offset) as u64)?;
mem_access
.read(&mut read_data, (BASE_VIRT + offset) as u64)
.unwrap();

assert_eq!(
read_data,
Expand All @@ -556,7 +599,9 @@ mod tests {
}

let write_data = [0xCCu8; 1];
mem_access.write(&write_data, (BASE_VIRT + offset) as u64)?;
mem_access
.write(&write_data, (BASE_VIRT + offset) as u64)
.unwrap();

let slice = unsafe { get_mmap_slice(&mut mem_access) };
assert_eq!(slice[offset], write_data[0]);
Expand All @@ -577,7 +622,9 @@ mod tests {
}

let write_data = [0xAAu8; 16];
mem_access.write(&write_data, (BASE_VIRT + offset) as u64)?;
mem_access
.write(&write_data, (BASE_VIRT + offset) as u64)
.unwrap();

let slice = unsafe { get_mmap_slice(&mut mem_access) };
assert_eq!(slice[offset..offset + 16], write_data);
Expand Down
Loading
Loading