hyperlight-host 0.17.0

A lightweight Virtual Machine Manager that can be hosted in an application to safely run untrusted or code within a VM partition with very low latency and overhead.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 The Hyperlight Authors.

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

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)]
pub enum VcpuStopReasonError {
    #[error("Failed to get registers: {0}")]
    GetRegs(#[from] RegisterError),
    #[error("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
/// Exception id for #DB
pub(crate) const DB_EX_ID: u32 = 1;
/// Exception id for #BP - triggered by the INT3 instruction
pub(crate) const BP_EX_ID: u32 = 3;

/// Software Breakpoint size in memory
pub(crate) const SW_BP_SIZE: usize = 1;
/// Software Breakpoint opcode - INT3
/// Check page 7-28 Vol. 3A of Intel 64 and IA-32
/// Architectures Software Developer's Manual
pub(crate) const SW_BP_OP: u8 = 0xCC;
/// Software Breakpoint written to memory
pub(crate) const SW_BP: [u8; SW_BP_SIZE] = [SW_BP_OP];
/// Maximum number of supported hardware breakpoints
pub(crate) const MAX_NO_OF_HW_BP: usize = 4;

/// Check page 19-4 Vol. 3B of Intel 64 and IA-32
/// Architectures Software Developer's Manual
/// Bit position of BS flag in DR6 debug register
pub(crate) const DR6_BS_FLAG_POS: usize = 14;
/// Bit mask of BS flag in DR6 debug register
pub(crate) const DR6_BS_FLAG_MASK: u64 = 1 << DR6_BS_FLAG_POS;
/// Bit position of HW breakpoints status in DR6 debug register
pub(crate) const DR6_HW_BP_FLAGS_POS: usize = 0;
/// Bit mask of HW breakpoints status in DR6 debug register
pub(crate) const DR6_HW_BP_FLAGS_MASK: u64 = 0x0F << DR6_HW_BP_FLAGS_POS;

/// Determine the reason the vCPU stopped
/// This is done by checking the DR6 register and the exception id
pub(crate) fn vcpu_stop_reason(
    vm: &dyn DebuggableVm,
    dr6: u64,
    exception: u32,
) -> 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
        // instruction triggered the exit
        // Check page 19-4 Vol. 3B of Intel 64 and IA-32
        // Architectures Software Developer's Manual
        if dr6 & DR6_BS_FLAG_MASK != 0 {
            return Ok(VcpuStopReason::DoneStep);
        }

        // If any of the B0-B3 flags in DR6 register is set, it means a
        // hardware breakpoint triggered the exit
        // Check page 19-4 Vol. 3B of Intel 64 and IA-32
        // Architectures Software Developer's Manual
        if DR6_HW_BP_FLAGS_MASK & dr6 != 0 {
            return Ok(VcpuStopReason::HwBp);
        }
    }

    if BP_EX_ID == exception {
        return Ok(VcpuStopReason::SwBp);
    }

    // Log an error and provide internal debugging info
    tracing::error!(
        r"The vCPU exited because of an unknown reason:
        rip: {:?}
        dr6: {:?}
        exception: {:?}
        ",
        rip,
        dr6,
        exception,
    );

    Ok(VcpuStopReason::Unknown)
}