#[cfg(gdb)]
pub(crate) mod gdb;
pub(crate) mod regs;
pub(crate) mod virtual_machine;
#[cfg(target_os = "windows")]
pub(crate) mod surrogate_process;
#[cfg(target_os = "windows")]
pub(crate) mod surrogate_process_manager;
#[cfg(target_os = "windows")]
pub mod wrappers;
#[cfg(crashdump)]
pub(crate) mod crashdump;
pub(crate) mod hyperlight_vm;
use std::fmt::Debug;
#[cfg(any(kvm, mshv3))]
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
#[cfg(target_os = "windows")]
use std::sync::atomic::{AtomicU8, Ordering};
#[cfg(any(kvm, mshv3))]
use std::time::Duration;
pub(crate) trait InterruptHandleImpl: InterruptHandle {
#[cfg(any(kvm, mshv3))]
fn set_tid(&self);
fn set_running(&self);
fn clear_running(&self);
fn set_dropped(&self);
fn is_cancelled(&self) -> bool;
fn clear_cancel(&self);
fn is_debug_interrupted(&self) -> bool;
#[cfg(gdb)]
fn clear_debug_interrupt(&self);
}
pub trait InterruptHandle: Send + Sync + Debug {
fn kill(&self) -> bool;
#[cfg(gdb)]
fn kill_from_debugger(&self) -> bool;
fn dropped(&self) -> bool;
}
#[cfg(any(kvm, mshv3))]
#[derive(Debug)]
pub(super) struct LinuxInterruptHandle {
state: AtomicU8,
tid: AtomicU64,
dropped: AtomicBool,
retry_delay: Duration,
sig_rt_min_offset: u8,
}
#[cfg(any(kvm, mshv3))]
impl LinuxInterruptHandle {
const RUNNING_BIT: u8 = 1 << 1;
const CANCEL_BIT: u8 = 1 << 0;
#[cfg(gdb)]
const DEBUG_INTERRUPT_BIT: u8 = 1 << 2;
fn get_running_cancel_debug(&self) -> (bool, bool, bool) {
let state = self.state.load(Ordering::Acquire);
let running = state & Self::RUNNING_BIT != 0;
let cancel = state & Self::CANCEL_BIT != 0;
#[cfg(gdb)]
let debug = state & Self::DEBUG_INTERRUPT_BIT != 0;
#[cfg(not(gdb))]
let debug = false;
(running, cancel, debug)
}
fn send_signal(&self) -> bool {
let signal_number = libc::SIGRTMIN() + self.sig_rt_min_offset as libc::c_int;
let mut sent_signal = false;
loop {
let (running, cancel, debug) = self.get_running_cancel_debug();
let should_continue = running && (cancel || debug);
if !should_continue {
break;
}
tracing::info!("Sending signal to kill vcpu thread...");
sent_signal = true;
unsafe {
libc::pthread_kill(self.tid.load(Ordering::Acquire) as _, signal_number);
}
std::thread::sleep(self.retry_delay);
}
sent_signal
}
}
#[cfg(any(kvm, mshv3))]
impl InterruptHandleImpl for LinuxInterruptHandle {
fn set_tid(&self) {
self.tid
.store(unsafe { libc::pthread_self() as u64 }, Ordering::Release);
}
fn set_running(&self) {
self.state.fetch_or(Self::RUNNING_BIT, Ordering::Release);
}
fn is_cancelled(&self) -> bool {
self.state.load(Ordering::Acquire) & Self::CANCEL_BIT != 0
}
fn clear_cancel(&self) {
self.state.fetch_and(!Self::CANCEL_BIT, Ordering::Release);
}
fn clear_running(&self) {
self.state.fetch_and(!Self::RUNNING_BIT, Ordering::Release);
}
fn is_debug_interrupted(&self) -> bool {
#[cfg(gdb)]
{
self.state.load(Ordering::Acquire) & Self::DEBUG_INTERRUPT_BIT != 0
}
#[cfg(not(gdb))]
{
false
}
}
#[cfg(gdb)]
fn clear_debug_interrupt(&self) {
self.state
.fetch_and(!Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
}
fn set_dropped(&self) {
self.dropped.store(true, Ordering::Release);
}
}
#[cfg(any(kvm, mshv3))]
impl InterruptHandle for LinuxInterruptHandle {
fn kill(&self) -> bool {
self.state.fetch_or(Self::CANCEL_BIT, Ordering::Release);
self.send_signal()
}
#[cfg(gdb)]
fn kill_from_debugger(&self) -> bool {
self.state
.fetch_or(Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
self.send_signal()
}
fn dropped(&self) -> bool {
self.dropped.load(Ordering::Acquire)
}
}
#[cfg(target_os = "windows")]
#[derive(Debug)]
pub(super) struct WindowsInterruptHandle {
state: AtomicU8,
partition_state: std::sync::RwLock<PartitionState>,
}
#[cfg(target_os = "windows")]
#[derive(Debug)]
pub(super) struct PartitionState {
pub(super) handle: windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE,
pub(super) dropped: bool,
}
#[cfg(target_os = "windows")]
impl WindowsInterruptHandle {
const RUNNING_BIT: u8 = 1 << 1;
const CANCEL_BIT: u8 = 1 << 0;
#[cfg(gdb)]
const DEBUG_INTERRUPT_BIT: u8 = 1 << 2;
}
#[cfg(target_os = "windows")]
impl InterruptHandleImpl for WindowsInterruptHandle {
fn set_running(&self) {
self.state.fetch_or(Self::RUNNING_BIT, Ordering::Release);
}
fn is_cancelled(&self) -> bool {
self.state.load(Ordering::Acquire) & Self::CANCEL_BIT != 0
}
fn clear_cancel(&self) {
self.state.fetch_and(!Self::CANCEL_BIT, Ordering::Release);
}
fn clear_running(&self) {
self.state.fetch_and(!Self::RUNNING_BIT, Ordering::Release);
}
fn is_debug_interrupted(&self) -> bool {
#[cfg(gdb)]
{
self.state.load(Ordering::Acquire) & Self::DEBUG_INTERRUPT_BIT != 0
}
#[cfg(not(gdb))]
{
false
}
}
#[cfg(gdb)]
fn clear_debug_interrupt(&self) {
self.state
.fetch_and(!Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
}
fn set_dropped(&self) {
match self.partition_state.write() {
Ok(mut guard) => {
guard.dropped = true;
}
Err(e) => {
tracing::error!("Failed to acquire partition_state write lock: {}", e);
}
}
}
}
#[cfg(target_os = "windows")]
impl InterruptHandle for WindowsInterruptHandle {
fn kill(&self) -> bool {
use windows::Win32::System::Hypervisor::WHvCancelRunVirtualProcessor;
self.state.fetch_or(Self::CANCEL_BIT, Ordering::Release);
let state = self.state.load(Ordering::Acquire);
if state & Self::RUNNING_BIT == 0 {
return false;
}
let guard = match self.partition_state.read() {
Ok(guard) => guard,
Err(e) => {
tracing::error!("Failed to acquire partition_state read lock: {}", e);
return false;
}
};
if guard.dropped {
return false;
}
unsafe { WHvCancelRunVirtualProcessor(guard.handle, 0, 0).is_ok() }
}
#[cfg(gdb)]
fn kill_from_debugger(&self) -> bool {
use windows::Win32::System::Hypervisor::WHvCancelRunVirtualProcessor;
self.state
.fetch_or(Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
let state = self.state.load(Ordering::Acquire);
if state & Self::RUNNING_BIT == 0 {
return false;
}
let guard = match self.partition_state.read() {
Ok(guard) => guard,
Err(e) => {
tracing::error!("Failed to acquire partition_state read lock: {}", e);
return false;
}
};
if guard.dropped {
return false;
}
unsafe { WHvCancelRunVirtualProcessor(guard.handle, 0, 0).is_ok() }
}
fn dropped(&self) -> bool {
match self.partition_state.read() {
Ok(guard) => guard.dropped,
Err(e) => {
tracing::error!("Failed to acquire partition_state read lock: {}", e);
true }
}
}
}
#[cfg(all(test, any(target_os = "windows", kvm)))]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};
use hyperlight_testing::dummy_guest_as_string;
use crate::sandbox::uninitialized::GuestBinary;
#[cfg(any(crashdump, gdb))]
use crate::sandbox::uninitialized::SandboxRuntimeConfig;
use crate::sandbox::uninitialized_evolve::set_up_hypervisor_partition;
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() {
return Ok(());
}
use crate::mem::ptr::RawPtr;
use crate::sandbox::host_funcs::FunctionRegistry;
let filename = dummy_guest_as_string().map_err(|e| new_error!("{}", e))?;
let config: SandboxConfiguration = Default::default();
#[cfg(any(crashdump, gdb))]
let rt_cfg: SandboxRuntimeConfig = Default::default();
let sandbox =
UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), Some(config))?;
let (mut mem_mgr, gshm) = sandbox.mgr.build().unwrap();
let exn_stack_top_gva = hyperlight_common::layout::MAX_GVA as u64
- hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET
+ 1;
let mut vm = set_up_hypervisor_partition(
gshm,
&config,
exn_stack_top_gva,
page_size::get(),
#[cfg(any(crashdump, gdb))]
rt_cfg,
sandbox.load_info,
)?;
let peb_addr = RawPtr::from(0x1000u64); let seed = 12345u64; let page_size = 4096u32; let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default()));
let guest_max_log_level = Some(tracing_core::LevelFilter::ERROR);
#[cfg(gdb)]
let dbg_mem_access_fn = Arc::new(Mutex::new(mem_mgr.clone()));
vm.initialise(
peb_addr,
seed,
page_size,
&mut mem_mgr,
&host_funcs,
guest_max_log_level,
#[cfg(gdb)]
dbg_mem_access_fn,
)
.unwrap();
Ok(())
}
}