Skip to content

Bring back panic info to abort #489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 14, 2025
Merged
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
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test-like-ci config=default-target hypervisor="kvm":
just test {{config}} {{ if hypervisor == "mshv3" {"mshv3"} else {""} }}

@# with only one driver enabled + seccomp
just test {{config}} seccomp,{{ if hypervisor == "mshv" {"mshv2"} else if hypervisor == "mshv3" {"mshv3"} else {"kvm"} }}
just test {{config}} seccomp,build-metadata,{{ if hypervisor == "mshv" {"mshv2"} else if hypervisor == "mshv3" {"mshv3"} else {"kvm"} }}

@# make sure certain cargo features compile
cargo check -p hyperlight-host --features crashdump
Expand Down
12 changes: 0 additions & 12 deletions src/hyperlight_common/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ pub const PAGE_SIZE_USIZE: usize = 1 << 12;

use core::ffi::{c_char, c_void};

#[repr(C)]
pub struct HostFunctionDefinitions {
pub fbHostFunctionDetailsSize: u64,
pub fbHostFunctionDetails: *mut c_void,
}

#[repr(C)]
pub struct GuestErrorData {
pub guestErrorSize: u64,
pub guestErrorBuffer: *mut c_void,
}

#[repr(u64)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RunMode {
Expand Down
23 changes: 15 additions & 8 deletions src/hyperlight_guest/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use spin::Once;
use crate::gdt::load_gdt;
use crate::guest_function_call::dispatch_function;
use crate::guest_logger::init_logger;
use crate::host_function_call::{debug_print, outb};
use crate::host_function_call::outb;
use crate::idtr::load_idt;
use crate::{
__security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS, OS_PAGE_SIZE, OUTB_PTR,
Expand All @@ -43,11 +43,12 @@ pub fn halt() {

#[no_mangle]
pub extern "C" fn abort() -> ! {
abort_with_code(&[0])
abort_with_code(&[0, 0xFF])
}

pub fn abort_with_code(code: &[u8]) -> ! {
outb(OutBAction::Abort as u16, code);
outb(OutBAction::Abort as u16, &[0xFF]); // send abort terminator (if not included in code)
unreachable!()
}

Expand All @@ -56,13 +57,19 @@ pub fn abort_with_code(code: &[u8]) -> ! {
/// # Safety
/// This function is unsafe because it dereferences a raw pointer.
pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_char) -> ! {
debug_print(
CStr::from_ptr(message_ptr)
.to_str()
.expect("Invalid UTF-8 string"),
);

// Step 1: Send abort code (typically 1 byte, but `code` allows flexibility)
outb(OutBAction::Abort as u16, code);

// Step 2: Convert the C string to bytes
let message_bytes = CStr::from_ptr(message_ptr).to_bytes(); // excludes null terminator

// Step 3: Send the message itself in chunks
outb(OutBAction::Abort as u16, message_bytes);

// Step 4: Send abort terminator to signal completion (e.g., 0xFF)
outb(OutBAction::Abort as u16, &[0xFF]);

// This function never returns
unreachable!()
}

Expand Down
62 changes: 29 additions & 33 deletions src/hyperlight_guest/src/host_function_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::arch::global_asm;
use core::arch;

use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
use hyperlight_common::flatbuffer_wrappers::function_types::{
Expand Down Expand Up @@ -79,21 +79,16 @@ pub fn outb(port: u16, data: &[u8]) {
unsafe {
match RUNNING_MODE {
RunMode::Hypervisor => {
for chunk in data.chunks(4) {
// Process the data in chunks of 4 bytes. If a chunk has fewer than 4 bytes,
// pad it with 0x7F to ensure it can be converted into a 4-byte array.
// The choice of 0x7F as the padding value is arbitrary and does not carry
// any special meaning; it simply ensures consistent chunk size.
let val = match chunk {
[a, b, c, d] => u32::from_le_bytes([*a, *b, *c, *d]),
[a, b, c] => u32::from_le_bytes([*a, *b, *c, 0x7F]),
[a, b] => u32::from_le_bytes([*a, *b, 0x7F, 0x7F]),
[a] => u32::from_le_bytes([*a, 0x7F, 0x7F, 0x7F]),
[] => break,
_ => unreachable!(),
};

hloutd(val, port);
let mut i = 0;
while i < data.len() {
let remaining = data.len() - i;
let chunk_len = remaining.min(3);
let mut chunk = [0u8; 4];
chunk[0] = chunk_len as u8;
chunk[1..1 + chunk_len].copy_from_slice(&data[i..i + chunk_len]);
let val = u32::from_le_bytes(chunk);
out32(port, val);
i += chunk_len;
}
}
RunMode::InProcessLinux | RunMode::InProcessWindows => {
Expand All @@ -119,11 +114,25 @@ pub fn outb(port: u16, data: &[u8]) {
}
}

extern "win64" {
fn hloutd(value: u32, port: u16);
pub(crate) unsafe fn out32(port: u16, val: u32) {
arch::asm!("out dx, eax", in("dx") port, in("eax") val, options(preserves_flags, nomem, nostack));
}

pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result<Vec<u8>> {
/// Prints a message using `OutBAction::DebugPrint`. It transmits bytes of a message
/// through several VMExists and, with such, it is slower than
/// `print_output_with_host_print`.
///
/// This function should be used in debug mode only. This function does not
/// require memory to be setup to be used.
pub fn debug_print(msg: &str) {
outb(OutBAction::DebugPrint as u16, msg.as_bytes());
}

/// Print a message using the host's print function.
///
/// This function requires memory to be setup to be used. In particular, the
/// existence of the input and output memory regions.
pub fn print_output_with_host_print(function_call: &FunctionCall) -> Result<Vec<u8>> {
if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() {
call_host_function(
"HostPrint",
Expand All @@ -135,20 +144,7 @@ pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result<Ve
} else {
Err(HyperlightGuestError::new(
ErrorCode::GuestError,
"Wrong Parameters passed to print_output_as_guest_function".to_string(),
"Wrong Parameters passed to print_output_with_host_print".to_string(),
))
}
}

pub fn debug_print(msg: &str) {
outb(OutBAction::DebugPrint as u16, msg.as_bytes());
}

global_asm!(
".global hloutd
hloutd:
mov eax, ecx
mov dx, dx
out dx, eax
ret"
);
5 changes: 2 additions & 3 deletions src/hyperlight_guest/src/interrupt_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ pub extern "sysv64" fn hl_exception_handler(
) {
let exception = Exception::try_from(exception_number as u8).expect("Invalid exception number");
let msg = format!(
"EXCEPTION: {:#?}\n\
Page Fault Address: {:#x}\n\
"Page Fault Address: {:#x}\n\
Stack Pointer: {:#x}",
exception, page_fault_address, stack_pointer
page_fault_address, stack_pointer
);

unsafe {
Expand Down
13 changes: 6 additions & 7 deletions src/hyperlight_guest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ limitations under the License.
#![no_std]
// Deps
use alloc::string::ToString;
use core::hint::unreachable_unchecked;

use buddy_system_allocator::LockedHeap;
use guest_function_register::GuestFunctionRegister;
use host_function_call::debug_print;
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
use hyperlight_common::mem::{HyperlightPEB, RunMode};
use hyperlight_common::outb::OutBAction;

use crate::host_function_call::outb;
use crate::entrypoint::abort_with_code_and_message;
extern crate alloc;

// Modules
Expand Down Expand Up @@ -73,9 +70,11 @@ pub(crate) static _fltused: i32 = 0;
// to satisfy the clippy when cfg == test
#[allow(dead_code)]
fn panic(info: &core::panic::PanicInfo) -> ! {
debug_print(info.to_string().as_str());
outb(OutBAction::Abort as u16, &[ErrorCode::UnknownError as u8]);
unsafe { unreachable_unchecked() }
let msg = info.to_string();
let c_string = alloc::ffi::CString::new(msg)
.unwrap_or_else(|_| alloc::ffi::CString::new("panic (invalid utf8)").unwrap());

unsafe { abort_with_code_and_message(&[ErrorCode::UnknownError as u8], c_string.as_ptr()) }
}

// Globals
Expand Down
5 changes: 3 additions & 2 deletions src/hyperlight_host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ proc-maps = "0.4.0"
[build-dependencies]
anyhow = { version = "1.0.98" }
cfg_aliases = "0.2.1"
built = { version = "0.8.0", features = ["chrono", "git2"] }
built = { version = "0.8.0", optional = true, features = ["chrono", "git2"] }

[features]
default = ["kvm", "mshv2", "seccomp"]
default = ["kvm", "mshv2", "seccomp", "build-metadata"]
seccomp = ["dep:seccompiler"]
function_call_metrics = []
executable_heap = []
Expand All @@ -129,6 +129,7 @@ mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"]
# This enables easy debug in the guest
gdb = ["dep:gdbstub", "dep:gdbstub_arch"]
fuzzing = ["hyperlight-common/fuzzing"]
build-metadata = ["dep:built"]

[[bench]]
name = "benchmarks"
Expand Down
2 changes: 2 additions & 0 deletions src/hyperlight_host/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

use anyhow::Result;
#[cfg(feature = "build-metadata")]
use built::write_built_file;

fn main() -> Result<()> {
Expand Down Expand Up @@ -103,6 +104,7 @@ fn main() -> Result<()> {
mshv3: { all(feature = "mshv3", target_os = "linux") },
}

#[cfg(feature = "build-metadata")]
write_built_file()?;

Ok(())
Expand Down
6 changes: 3 additions & 3 deletions src/hyperlight_host/src/hypervisor/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{new_error, Result};
/// has initiated an outb operation.
pub trait OutBHandlerCaller: Sync + Send {
/// Function that gets called when an outb operation has occurred.
fn call(&mut self, port: u16, payload: Vec<u8>) -> Result<()>;
fn call(&mut self, port: u16, payload: u32) -> Result<()>;
}

/// A convenient type representing a common way `OutBHandler` implementations
Expand All @@ -36,7 +36,7 @@ pub trait OutBHandlerCaller: Sync + Send {
/// a &mut self).
pub type OutBHandlerWrapper = Arc<Mutex<dyn OutBHandlerCaller>>;

pub(crate) type OutBHandlerFunction = Box<dyn FnMut(u16, Vec<u8>) -> Result<()> + Send>;
pub(crate) type OutBHandlerFunction = Box<dyn FnMut(u16, u32) -> Result<()> + Send>;

/// A `OutBHandler` implementation using a `OutBHandlerFunction`
///
Expand All @@ -52,7 +52,7 @@ impl From<OutBHandlerFunction> for OutBHandler {

impl OutBHandlerCaller for OutBHandler {
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
fn call(&mut self, port: u16, payload: Vec<u8>) -> Result<()> {
fn call(&mut self, port: u16, payload: u32) -> Result<()> {
let mut func = self
.0
.try_lock()
Expand Down
7 changes: 6 additions & 1 deletion src/hyperlight_host/src/hypervisor/hyperv_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,10 +546,15 @@ impl Hypervisor for HypervLinuxDriver {
instruction_length: u64,
outb_handle_fn: OutBHandlerWrapper,
) -> Result<()> {
let mut padded = [0u8; 4];
let copy_len = data.len().min(4);
padded[..copy_len].copy_from_slice(&data[..copy_len]);
let val = u32::from_le_bytes(padded);

outb_handle_fn
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.call(port, data)?;
.call(port, val)?;

// update rip
self.vcpu_fd.set_reg(&[hv_register_assoc {
Expand Down
9 changes: 7 additions & 2 deletions src/hyperlight_host/src/hypervisor/hyperv_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,15 @@ impl Hypervisor for HypervWindowsDriver {
instruction_length: u64,
outb_handle_fn: OutBHandlerWrapper,
) -> Result<()> {
let mut padded = [0u8; 4];
let copy_len = data.len().min(4);
padded[..copy_len].copy_from_slice(&data[..copy_len]);
let val = u32::from_le_bytes(padded);

outb_handle_fn
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.call(port, data)?;
.call(port, val)?;

let mut regs = self.processor.get_regs()?;
regs.rip = rip + instruction_length;
Expand Down Expand Up @@ -505,7 +510,7 @@ pub mod tests {
#[serial]
fn test_init() {
let outb_handler = {
let func: Box<dyn FnMut(u16, Vec<u8>) -> Result<()> + Send> =
let func: Box<dyn FnMut(u16, u32) -> Result<()> + Send> =
Box::new(|_, _| -> Result<()> { Ok(()) });
Arc::new(Mutex::new(OutBHandler::from(func)))
};
Expand Down
9 changes: 7 additions & 2 deletions src/hyperlight_host/src/hypervisor/kvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,15 @@ impl Hypervisor for KVMDriver {
if data.is_empty() {
log_then_return!("no data was given in IO interrupt");
} else {
let mut padded = [0u8; 4];
let copy_len = data.len().min(4);
padded[..copy_len].copy_from_slice(&data[..copy_len]);
let value = u32::from_le_bytes(padded);

outb_handle_fn
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.call(port, data)?;
.call(port, value)?;
}

Ok(())
Expand Down Expand Up @@ -664,7 +669,7 @@ mod tests {
}

let outb_handler: Arc<Mutex<OutBHandler>> = {
let func: Box<dyn FnMut(u16, Vec<u8>) -> Result<()> + Send> =
let func: Box<dyn FnMut(u16, u32) -> Result<()> + Send> =
Box::new(|_, _| -> Result<()> { Ok(()) });
Arc::new(Mutex::new(OutBHandler::from(func)))
};
Expand Down
6 changes: 5 additions & 1 deletion src/hyperlight_host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ limitations under the License.
#![cfg_attr(not(any(test, debug_assertions)), warn(clippy::unwrap_used))]
#![cfg_attr(any(test, debug_assertions), allow(clippy::disallowed_macros))]

#[cfg(feature = "build-metadata")]
use std::sync::Once;

use log::info;
#[cfg(feature = "build-metadata")]
/// The `built` crate is used to generate a `built.rs` file that contains
/// information about the build environment. This information is used to
/// populate the `built_info` module, which is re-exported here.
Expand Down Expand Up @@ -149,9 +150,12 @@ macro_rules! debug {
}

// LOG_ONCE is used to log information about the crate version once
#[cfg(feature = "build-metadata")]
static LOG_ONCE: Once = Once::new();

#[cfg(feature = "build-metadata")]
pub(crate) fn log_build_details() {
use log::info;
LOG_ONCE.call_once(|| {
info!("Package name: {}", built_info::PKG_NAME);
info!("Package version: {}", built_info::PKG_VERSION);
Expand Down
Loading