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
5 changes: 5 additions & 0 deletions .github/workflows/dep_build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ jobs:
# with only one driver enabled (kvm/mshv3 features are unix-only, no-op on Windows)
just test ${{ inputs.config }} ${{ inputs.hypervisor == 'mshv3' && 'mshv3' || 'kvm' }}
- name: Run Rust tests with hw-interrupts
run: |
# with hw-interrupts feature enabled (+ explicit driver on Linux)
just test ${{ inputs.config }} ${{ runner.os == 'Linux' && (inputs.hypervisor == 'mshv3' && 'mshv3,hw-interrupts' || 'kvm,hw-interrupts') || 'hw-interrupts' }}
- name: Run Rust Gdb tests
env:
RUST_LOG: debug
Expand Down
7 changes: 7 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ test-like-ci config=default-target hypervisor="kvm":
@# with only one driver enabled + build-metadata + init-paging
just test {{config}} build-metadata,init-paging,{{ if hypervisor == "mshv3" {"mshv3"} else {"kvm"} }}

@# with hw-interrupts enabled (+ explicit driver on Linux)
{{ if os() == "linux" { if hypervisor == "mshv3" { "just test " + config + " mshv3,hw-interrupts" } else { "just test " + config + " kvm,hw-interrupts" } } else { "just test " + config + " hw-interrupts" } }}

@# make sure certain cargo features compile
just check

Expand Down Expand Up @@ -151,6 +154,9 @@ build-test-like-ci config=default-target hypervisor="kvm":
@# Run Rust tests with single driver
{{ if os() == "linux" { "just test " + config+ " " + if hypervisor == "mshv3" { "mshv3" } else { "kvm" } } else { "" } }}

@# Run Rust tests with hw-interrupts
{{ if os() == "linux" { if hypervisor == "mshv3" { "just test " + config + " mshv3,hw-interrupts" } else { "just test " + config + " kvm,hw-interrupts" } } else { "just test " + config + " hw-interrupts" } }}

@# Run Rust Gdb tests
just test-rust-gdb-debugging {{config}}

Expand Down Expand Up @@ -282,6 +288,7 @@ check:
{{ cargo-cmd }} check -p hyperlight-host --features print_debug {{ target-triple-flag }}
{{ cargo-cmd }} check -p hyperlight-host --features gdb {{ target-triple-flag }}
{{ cargo-cmd }} check -p hyperlight-host --features trace_guest,mem_profile {{ target-triple-flag }}
{{ cargo-cmd }} check -p hyperlight-host --features hw-interrupts {{ target-triple-flag }}

fmt-check:
rustup +nightly component list | grep -q "rustfmt.*installed" || rustup component add rustfmt --toolchain nightly
Expand Down
11 changes: 11 additions & 0 deletions src/hyperlight_common/src/outb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ pub enum OutBAction {
TraceMemoryAlloc = 105,
#[cfg(feature = "mem_profile")]
TraceMemoryFree = 106,
/// IO port for PV timer configuration. The guest writes a 32-bit
/// LE value representing the desired timer period in microseconds.
/// A value of 0 disables the timer.
PvTimerConfig = 107,
/// IO port the guest writes to signal "I'm done" to the host.
/// This replaces the `hlt` instruction for halt signaling so that
/// KVM's in-kernel LAPIC (which absorbs HLT exits) does not interfere
/// with hyperlight's halt-based guest-host protocol.
Halt = 108,
}

impl TryFrom<u16> for OutBAction {
Expand All @@ -120,6 +129,8 @@ impl TryFrom<u16> for OutBAction {
105 => Ok(OutBAction::TraceMemoryAlloc),
#[cfg(feature = "mem_profile")]
106 => Ok(OutBAction::TraceMemoryFree),
107 => Ok(OutBAction::PvTimerConfig),
108 => Ok(OutBAction::Halt),
_ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)),
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/hyperlight_guest/src/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@ use core::arch::asm;
use core::ffi::{CStr, c_char};

use hyperlight_common::outb::OutBAction;
use tracing::instrument;

/// Halt the execution of the guest and returns control to the host.
Copy link
Member

Choose a reason for hiding this comment

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

Comment (and maybe name) (and commit message) should clarify that this is meant to be used for wfi rather than actually ending execution as we've been using hlt for in the past?

/// Halt is generally called for a successful completion of the guest's work,
/// this means we can instrument it as a trace point because the trace state
/// shall not be locked at this point (we are not in an exception context).
#[inline(never)]
#[instrument(skip_all, level = "Trace")]
pub fn halt() {
#[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
{
// Send data before halting
// If there is no data, this doesn't do anything
// The reason we do this here instead of in `hlt` asm function
// is to avoid allocating before halting, which leaks memory
// because the guest is not expected to resume execution after halting.
hyperlight_guest_tracing::flush();
}

// Signal "I'm done" to the host via an IO port write. This causes a
// VM exit on all backends (KVM, MSHV, WHP). The subsequent cli+hlt
// is dead code—hyperlight never re-enters the guest after seeing
// the Halt port—but serves as a safety fallback.
unsafe {
out32(OutBAction::Halt as u16, 0);
asm!("cli", "hlt", options(nostack));
}
}

/// Exits the VM with an Abort OUT action and code 0.
#[unsafe(no_mangle)]
Expand Down
3 changes: 3 additions & 0 deletions src/hyperlight_guest_bin/src/arch/amd64/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,8 @@ core::arch::global_asm!("
mov cr4, rdi
flush_done:
call {internal_dispatch_function}\n
mov dx, 108\n
out dx, al\n
cli\n
Copy link
Member

Choose a reason for hiding this comment

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

Is there any use for this wfi fallback at the end here? If the guest does resume execution on interrupt delivery from a hlt here, something has gone very wrong.

hlt\n
", internal_dispatch_function = sym crate::guest_function::call::internal_dispatch_function);
26 changes: 26 additions & 0 deletions src/hyperlight_guest_bin/src/arch/amd64/exception/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,24 @@ global_asm!(
hl_exception_handler = sym super::handle::hl_exception_handler,
);

// Default no-op IRQ handler for hardware interrupts (vectors 0x20-0x2F).
Copy link
Member

Choose a reason for hiding this comment

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

AFAIUI this is code that should be running in target_arch = i686 only right now, so perhaps ought not be in amd64?

// Sends a non-specific EOI to the master PIC and returns.
// This prevents unhandled-interrupt faults when the in-kernel PIT fires
Copy link
Member

Choose a reason for hiding this comment

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

This race condition is still present since this is only being installed in init_idt after the guest has already been running instructions for some time?

If this is a serious concern, surely we need the host to control the vm state a bit better---either only enabling interrupts after initialize() has finished and the guest kernel is up, or presetting the idt state before entering the guest for the first time (although I'm unsure if there is API for that)

// before a guest has installed its own IRQ handler.
global_asm!(
".globl _default_irq_handler",
"_default_irq_handler:",
"push rax",
"mov al, 0x20",
"out 0x20, al", // PIC EOI
"pop rax",
"iretq",
);

unsafe extern "C" {
fn _default_irq_handler();
}

pub(in super::super) fn init_idt(pc: *mut ProcCtrl) {
let idt = unsafe { &raw mut (*pc).idt };
let set_idt_entry = |idx, handler: unsafe extern "C" fn()| {
Expand Down Expand Up @@ -203,6 +221,14 @@ pub(in super::super) fn init_idt(pc: *mut ProcCtrl) {
set_idt_entry(Exception::VirtualizationException, _do_excp20); // Virtualization Exception
set_idt_entry(Exception::SecurityException, _do_excp30); // Security Exception

// Install a default no-op IRQ handler at vector 0x20 (IRQ0 after PIC remapping).
// This ensures HLT can wake when an in-kernel PIT is active, even before the
// guest installs its own IRQ handler.
let irq_handler_addr = _default_irq_handler as *const () as u64;
unsafe {
(&raw mut (*idt).entries[0x20]).write_volatile(IdtEntry::new(irq_handler_addr));
}

let idtr = IdtPointer {
limit: (core::mem::size_of::<IDT>() - 1) as u16,
base: idt as u64,
Expand Down
3 changes: 3 additions & 0 deletions src/hyperlight_guest_bin/src/arch/amd64/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,8 @@ core::arch::global_asm!("
pivot_stack:\n
mov rsp, r8\n
call {generic_init}\n
mov dx, 108\n
out dx, al\n
cli\n
hlt\n
", generic_init = sym crate::generic_init);
1 change: 1 addition & 0 deletions src/hyperlight_host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ trace_guest = ["dep:opentelemetry", "dep:tracing-opentelemetry", "dep:hyperlight
mem_profile = [ "trace_guest", "dep:framehop", "dep:fallible-iterator", "hyperlight-common/mem_profile" ]
kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"]
mshv3 = ["dep:mshv-bindings", "dep:mshv-ioctls"]
hw-interrupts = []
# This enables easy debug in the guest
gdb = ["dep:gdbstub", "dep:gdbstub_arch"]
fuzzing = ["hyperlight-common/fuzzing"]
Expand Down
12 changes: 12 additions & 0 deletions src/hyperlight_host/src/hypervisor/hyperlight_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,12 @@ impl HyperlightVm {
vm.set_sregs(&CommonSpecialRegisters::standard_real_mode_defaults())
.map_err(VmError::Register)?;

// Initialize XSAVE state with proper FPU defaults (FCW, MXCSR).
// MSHV (unlike KVM) does not provide sane defaults for the XSAVE
// area on a freshly created vCPU, and will reject the VP state
// on the first run if it is left uninitialized.
vm.reset_xsave().map_err(VmError::Register)?;

#[cfg(any(kvm, mshv3))]
let interrupt_handle: Arc<dyn InterruptHandleImpl> = Arc::new(LinuxInterruptHandle {
state: AtomicU8::new(0),
Expand Down Expand Up @@ -2241,6 +2247,7 @@ mod tests {
// Tests
// ==========================================================================

#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn reset_vcpu_simple() {
// push rax; hlt - aligns stack to 16 bytes
Expand Down Expand Up @@ -2386,6 +2393,7 @@ mod tests {

use super::*;

#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn reset_vcpu_regs() {
let mut a = CodeAssembler::new(64).unwrap();
Expand Down Expand Up @@ -2445,6 +2453,7 @@ mod tests {
assert_regs_reset(hyperlight_vm.vm.as_ref());
}

#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn reset_vcpu_fpu() {
#[cfg(kvm)]
Expand Down Expand Up @@ -2576,6 +2585,7 @@ mod tests {
}
}

#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn reset_vcpu_debug_regs() {
let mut a = CodeAssembler::new(64).unwrap();
Expand Down Expand Up @@ -2618,6 +2628,7 @@ mod tests {
assert_debug_regs_reset(hyperlight_vm.vm.as_ref());
}

#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn reset_vcpu_sregs() {
// Build code that modifies special registers and halts
Expand Down Expand Up @@ -2671,6 +2682,7 @@ mod tests {

/// Verifies guest-visible FPU state (via FXSAVE) is properly reset.
/// Unlike tests using hypervisor API, this runs actual guest code with FXSAVE.
#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn reset_vcpu_fpu_guest_visible_state() {
let mut ctx = hyperlight_vm_with_mem_mgr_fxsave();
Expand Down
4 changes: 4 additions & 0 deletions src/hyperlight_host/src/hypervisor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub(crate) mod crashdump;

pub(crate) mod hyperlight_vm;

#[cfg(all(feature = "hw-interrupts", any(mshv3, target_os = "windows")))]
pub(crate) mod pic;

use std::fmt::Debug;
#[cfg(any(kvm, mshv3))]
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
Expand Down Expand Up @@ -469,6 +472,7 @@ pub(crate) mod tests {
use crate::sandbox::{SandboxConfiguration, UninitializedSandbox};
use crate::{Result, is_hypervisor_present, new_error};

#[cfg_attr(feature = "hw-interrupts", ignore)]
#[test]
fn test_initialise() -> Result<()> {
if !is_hypervisor_present() {
Expand Down
Loading
Loading