diff --git a/Justfile b/Justfile index 8440b36b5..634341f0e 100644 --- a/Justfile +++ b/Justfile @@ -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 diff --git a/src/hyperlight_common/src/mem.rs b/src/hyperlight_common/src/mem.rs index f9fcd8f04..03b886094 100644 --- a/src/hyperlight_common/src/mem.rs +++ b/src/hyperlight_common/src/mem.rs @@ -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 { diff --git a/src/hyperlight_guest/src/entrypoint.rs b/src/hyperlight_guest/src/entrypoint.rs index 5f1e47741..e87a3b738 100644 --- a/src/hyperlight_guest/src/entrypoint.rs +++ b/src/hyperlight_guest/src/entrypoint.rs @@ -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, @@ -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!() } @@ -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!() } diff --git a/src/hyperlight_guest/src/host_function_call.rs b/src/hyperlight_guest/src/host_function_call.rs index feed39710..84de2f091 100644 --- a/src/hyperlight_guest/src/host_function_call.rs +++ b/src/hyperlight_guest/src/host_function_call.rs @@ -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::{ @@ -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 => { @@ -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> { +/// 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> { if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() { call_host_function( "HostPrint", @@ -135,20 +144,7 @@ pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result ! { - 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 diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index e91a4f834..c79d09ed2 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -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 = [] @@ -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" diff --git a/src/hyperlight_host/build.rs b/src/hyperlight_host/build.rs index 7818c1257..4710d3cbd 100644 --- a/src/hyperlight_host/build.rs +++ b/src/hyperlight_host/build.rs @@ -15,6 +15,7 @@ limitations under the License. */ use anyhow::Result; +#[cfg(feature = "build-metadata")] use built::write_built_file; fn main() -> Result<()> { @@ -103,6 +104,7 @@ fn main() -> Result<()> { mshv3: { all(feature = "mshv3", target_os = "linux") }, } + #[cfg(feature = "build-metadata")] write_built_file()?; Ok(()) diff --git a/src/hyperlight_host/src/hypervisor/handlers.rs b/src/hyperlight_host/src/hypervisor/handlers.rs index 4cc3bcb51..9dbb948af 100644 --- a/src/hyperlight_host/src/hypervisor/handlers.rs +++ b/src/hyperlight_host/src/hypervisor/handlers.rs @@ -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) -> Result<()>; + fn call(&mut self, port: u16, payload: u32) -> Result<()>; } /// A convenient type representing a common way `OutBHandler` implementations @@ -36,7 +36,7 @@ pub trait OutBHandlerCaller: Sync + Send { /// a &mut self). pub type OutBHandlerWrapper = Arc>; -pub(crate) type OutBHandlerFunction = Box) -> Result<()> + Send>; +pub(crate) type OutBHandlerFunction = Box Result<()> + Send>; /// A `OutBHandler` implementation using a `OutBHandlerFunction` /// @@ -52,7 +52,7 @@ impl From for OutBHandler { impl OutBHandlerCaller for OutBHandler { #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn call(&mut self, port: u16, payload: Vec) -> Result<()> { + fn call(&mut self, port: u16, payload: u32) -> Result<()> { let mut func = self .0 .try_lock() diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index ce0f7d1e5..75b5cd2ec 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -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 { diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 44256d1fe..03ca86198 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -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; @@ -505,7 +510,7 @@ pub mod tests { #[serial] fn test_init() { let outb_handler = { - let func: Box) -> Result<()> + Send> = + let func: Box Result<()> + Send> = Box::new(|_, _| -> Result<()> { Ok(()) }); Arc::new(Mutex::new(OutBHandler::from(func))) }; diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 903c078ca..f39fb99bd 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -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(()) @@ -664,7 +669,7 @@ mod tests { } let outb_handler: Arc> = { - let func: Box) -> Result<()> + Send> = + let func: Box Result<()> + Send> = Box::new(|_, _| -> Result<()> { Ok(()) }); Arc::new(Mutex::new(OutBHandler::from(func))) }; diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 63d9b1c06..f0a98a0d8 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -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. @@ -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); diff --git a/src/hyperlight_host/src/sandbox/mem_mgr.rs b/src/hyperlight_host/src/sandbox/mem_mgr.rs index 84780db77..aadaa5091 100644 --- a/src/hyperlight_host/src/sandbox/mem_mgr.rs +++ b/src/hyperlight_host/src/sandbox/mem_mgr.rs @@ -29,27 +29,39 @@ pub type StackCookie = [u8; STACK_COOKIE_LEN]; /// A container with methods for accessing `SandboxMemoryManager` and other /// related objects #[derive(Clone)] -pub(crate) struct MemMgrWrapper(SandboxMemoryManager, StackCookie); +pub(crate) struct MemMgrWrapper { + mgr: SandboxMemoryManager, + stack_cookie: StackCookie, + abort_buffer: Vec, +} impl MemMgrWrapper { #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(super) fn new(mgr: SandboxMemoryManager, stack_cookie: StackCookie) -> Self { - Self(mgr, stack_cookie) + Self { + mgr, + stack_cookie, + abort_buffer: Vec::new(), + } } #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn unwrap_mgr(&self) -> &SandboxMemoryManager { - &self.0 + &self.mgr } #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn unwrap_mgr_mut(&mut self) -> &mut SandboxMemoryManager { - &mut self.0 + &mut self.mgr } #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(super) fn get_stack_cookie(&self) -> &StackCookie { - &self.1 + &self.stack_cookie + } + + pub fn get_abort_buffer_mut(&mut self) -> &mut Vec { + &mut self.abort_buffer } } @@ -74,8 +86,8 @@ impl MemMgrWrapper { MemMgrWrapper, SandboxMemoryManager, ) { - let (hshm, gshm) = self.0.build(); - (MemMgrWrapper(hshm, self.1), gshm) + let (hshm, gshm) = self.mgr.build(); + (MemMgrWrapper::new(hshm, self.stack_cookie), gshm) } #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 452533c76..4dc91207f 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -93,13 +93,64 @@ pub(super) fn outb_log(mgr: &mut SandboxMemoryManager) -> Resu Ok(()) } +const ABORT_TERMINATOR: u8 = 0xFF; +const MAX_ABORT_BUFFER_LEN: usize = 1024; + +fn outb_abort(mem_mgr: &mut MemMgrWrapper, data: u32) -> Result<()> { + let buffer = mem_mgr.get_abort_buffer_mut(); + + let bytes = data.to_le_bytes(); // [len, b1, b2, b3] + let len = bytes[0].min(3); + + for &b in &bytes[1..=len as usize] { + if b == ABORT_TERMINATOR { + let guest_error_code = *buffer.first().unwrap_or(&0); + let guest_error = ErrorCode::from(guest_error_code as u64); + + let result = match guest_error { + ErrorCode::StackOverflow => Err(HyperlightError::StackOverflow()), + _ => { + let message = if let Some(&maybe_exception_code) = buffer.get(1) { + match Exception::try_from(maybe_exception_code) { + Ok(exception) => { + let extra_msg = String::from_utf8_lossy(&buffer[2..]); + format!("Exception: {:?} | {}", exception, extra_msg) + } + Err(_) => String::from_utf8_lossy(&buffer[1..]).into(), + } + } else { + String::new() + }; + + Err(HyperlightError::GuestAborted(guest_error_code, message)) + } + }; + + buffer.clear(); + return result; + } + + if buffer.len() >= MAX_ABORT_BUFFER_LEN { + buffer.clear(); + return Err(HyperlightError::GuestAborted( + 0, + "Guest abort buffer overflowed".into(), + )); + } + + buffer.push(b); + } + + Ok(()) +} + /// Handles OutB operations from the guest. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] fn handle_outb_impl( mem_mgr: &mut MemMgrWrapper, host_funcs: Arc>, port: u16, - data: Vec, + data: u32, ) -> Result<()> { match port.try_into()? { OutBAction::Log => outb_log(mem_mgr.as_mut()), @@ -117,30 +168,16 @@ fn handle_outb_impl( Ok(()) } - OutBAction::Abort => { - let byte = u64::from(data[0]); - let guest_error = ErrorCode::from(byte); - - match guest_error { - ErrorCode::StackOverflow => Err(HyperlightError::StackOverflow()), - _ => { - let message = match data.get(1) { - Some(&exception_code) => match Exception::try_from(exception_code) { - Ok(exception) => format!("Exception: {:?}", exception), - Err(e) => { - format!("Unknown exception code: {:#x} ({})", exception_code, e) - } - }, - None => "See stderr for panic context".into(), - }; - - Err(HyperlightError::GuestAborted(byte as u8, message)) - } - } - } + OutBAction::Abort => outb_abort(mem_mgr, data), OutBAction::DebugPrint => { - let s = String::from_utf8_lossy(&data); - eprint!("{}", s); + let ch: char = match char::from_u32(data) { + Some(c) => c, + None => { + return Err(new_error!("Invalid character for logging: {}", data)); + } + }; + + eprint!("{}", ch); Ok(()) } } diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index a923cf6ae..caf22b38b 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -30,13 +30,15 @@ use super::mem_mgr::MemMgrWrapper; use super::run_options::SandboxRunOptions; use super::uninitialized_evolve::evolve_impl_multi_use; use crate::func::host_functions::{HostFunction, IntoHostFunction}; +#[cfg(feature = "build-metadata")] +use crate::log_build_details; use crate::mem::exe::ExeInfo; use crate::mem::mgr::{SandboxMemoryManager, STACK_COOKIE_LEN}; use crate::mem::shared_mem::ExclusiveSharedMemory; use crate::sandbox::SandboxConfiguration; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; -use crate::{log_build_details, log_then_return, new_error, MultiUseSandbox, Result}; +use crate::{log_then_return, new_error, MultiUseSandbox, Result}; #[cfg(all(target_os = "linux", feature = "seccomp"))] const EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC: &[super::ExtraAllowedSyscall] = &[ @@ -142,6 +144,7 @@ impl UninitializedSandbox { cfg: Option, sandbox_run_options: Option, ) -> Result { + #[cfg(feature = "build-metadata")] log_build_details(); // hyperlight is only supported on Windows 11 and Windows Server 2022 and later @@ -336,7 +339,6 @@ fn check_windows_version() -> Result<()> { #[cfg(test)] mod tests { - use std::path::PathBuf; use std::sync::mpsc::channel; use std::sync::Arc; use std::time::Duration; @@ -344,21 +346,12 @@ mod tests { use crossbeam_queue::ArrayQueue; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue}; - use hyperlight_testing::logger::{Logger as TestLogger, LOGGER as TEST_LOGGER}; use hyperlight_testing::simple_guest_as_string; - use hyperlight_testing::tracing_subscriber::TracingSubscriber as TestSubscriber; - use log::Level; - use serde_json::{Map, Value}; - use tracing::Level as tracing_level; - use tracing_core::callsite::rebuild_interest_cache; - use tracing_core::Subscriber; - use uuid::Uuid; use crate::sandbox::uninitialized::GuestBinary; use crate::sandbox::SandboxConfiguration; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; - use crate::testing::log_values::{test_value_as_str, try_to_strings}; use crate::{new_error, MultiUseSandbox, Result, UninitializedSandbox}; #[test] @@ -804,7 +797,19 @@ mod tests { // from their workstation to be successful without needed to know about test interdependencies // this test will be run explicitly as a part of the CI pipeline #[ignore] + #[cfg(feature = "build-metadata")] fn test_trace_trace() { + use hyperlight_testing::logger::Logger as TestLogger; + use hyperlight_testing::tracing_subscriber::TracingSubscriber as TestSubscriber; + use serde_json::{Map, Value}; + use tracing::Level as tracing_level; + use tracing_core::callsite::rebuild_interest_cache; + use tracing_core::Subscriber; + use uuid::Uuid; + + use crate::testing::log_values::build_metadata_testing::try_to_strings; + use crate::testing::log_values::test_value_as_str; + TestLogger::initialize_log_tracer(); rebuild_interest_cache(); let subscriber = TestSubscriber::new(tracing_level::TRACE); @@ -902,7 +907,14 @@ mod tests { #[ignore] // Tests that traces are emitted as log records when there is no trace // subscriber configured. + #[cfg(feature = "build-metadata")] fn test_log_trace() { + use std::path::PathBuf; + + use hyperlight_testing::logger::{Logger as TestLogger, LOGGER as TEST_LOGGER}; + use log::Level; + use tracing_core::callsite::rebuild_interest_cache; + { TestLogger::initialize_test_logger(); TEST_LOGGER.set_max_level(log::LevelFilter::Trace); diff --git a/src/hyperlight_host/src/testing/log_values.rs b/src/hyperlight_host/src/testing/log_values.rs index 9017d394e..25995f403 100644 --- a/src/hyperlight_host/src/testing/log_values.rs +++ b/src/hyperlight_host/src/testing/log_values.rs @@ -61,61 +61,66 @@ fn try_to_string<'a>(values: &'a Map, key: &'a str) -> Result<&'a } } -/// A single value in the parameter list for the `try_to_strings` -/// function. -pub(crate) type MapLookup<'a> = (&'a Map, &'a str); +#[cfg(feature = "build-metadata")] +pub(crate) mod build_metadata_testing { + use super::*; -/// Given a constant-size slice of `MapLookup`s, attempt to look up the -/// string value in each `MapLookup`'s map (the first tuple element) for -/// that `MapLookup`'s key (the second tuple element). If the lookup -/// succeeded, attempt to convert the resulting value to a string. Return -/// `Ok` with all the successfully looked-up string values, or `Err` -/// if any one or more lookups or string conversions failed. -pub(crate) fn try_to_strings<'a, const NUM: usize>( - lookups: [MapLookup<'a>; NUM], -) -> Result<[&'a str; NUM]> { - // Note (from arschles) about this code: - // - // In theory, there's a way to write this function in the functional - // programming (FP) style -- e.g. with a fold, map, flat_map, or - // something similar -- and without any mutability. - // - // In practice, however, since we're taking in a statically-sized slice, - // and we are expected to return a statically-sized slice of the same - // size, we are more limited in what we can do. There is a way to design - // a fold or flat_map to iterate over the lookups parameter and attempt to - // transform each MapLookup into the string value at that key. - // - // I wrote that code, which I'll called the "FP code" hereafter, and - // noticed two things: - // - // - It required several places where I had to explicitly deal with long - // and complex (in my opinion) types - // - It wasn't much more succinct or shorter than the code herein - // - // The FP code is functionally "pure" and maybe fun to write (if you like - // Rust or you love FP), but not fun to read. In fact, because of all the - // explicit type ceremony, I bet it'd make even the most hardcore Haskell - // programmer blush. - // - // So, I've decided to use a little bit of mutability to implement this - // function in a way I think most programmers would agree is easier to - // reason about and understand quickly. - // - // Final performance note: - // - // It's likely, but not certain, that the FP code is probably not - // significantly more memory efficient than this, since the compiler knows - // the size of both the input and output slices. Plus, this is test code, - // so even if this were 2x slower, I'd still argue the ease of - // understanding is more valuable than the (relatively small) memory - // savings. - let mut ret_slc: [&'a str; NUM] = [""; NUM]; - for (idx, lookup) in lookups.iter().enumerate() { - let map = lookup.0; - let key = lookup.1; - let val = try_to_string(map, key)?; - ret_slc[idx] = val + /// A single value in the parameter list for the `try_to_strings` + /// function. + pub(crate) type MapLookup<'a> = (&'a Map, &'a str); + + /// Given a constant-size slice of `MapLookup`s, attempt to look up the + /// string value in each `MapLookup`'s map (the first tuple element) for + /// that `MapLookup`'s key (the second tuple element). If the lookup + /// succeeded, attempt to convert the resulting value to a string. Return + /// `Ok` with all the successfully looked-up string values, or `Err` + /// if any one or more lookups or string conversions failed. + pub(crate) fn try_to_strings<'a, const NUM: usize>( + lookups: [MapLookup<'a>; NUM], + ) -> Result<[&'a str; NUM]> { + // Note (from arschles) about this code: + // + // In theory, there's a way to write this function in the functional + // programming (FP) style -- e.g. with a fold, map, flat_map, or + // something similar -- and without any mutability. + // + // In practice, however, since we're taking in a statically-sized slice, + // and we are expected to return a statically-sized slice of the same + // size, we are more limited in what we can do. There is a way to design + // a fold or flat_map to iterate over the lookups parameter and attempt to + // transform each MapLookup into the string value at that key. + // + // I wrote that code, which I'll called the "FP code" hereafter, and + // noticed two things: + // + // - It required several places where I had to explicitly deal with long + // and complex (in my opinion) types + // - It wasn't much more succinct or shorter than the code herein + // + // The FP code is functionally "pure" and maybe fun to write (if you like + // Rust or you love FP), but not fun to read. In fact, because of all the + // explicit type ceremony, I bet it'd make even the most hardcore Haskell + // programmer blush. + // + // So, I've decided to use a little bit of mutability to implement this + // function in a way I think most programmers would agree is easier to + // reason about and understand quickly. + // + // Final performance note: + // + // It's likely, but not certain, that the FP code is probably not + // significantly more memory efficient than this, since the compiler knows + // the size of both the input and output slices. Plus, this is test code, + // so even if this were 2x slower, I'd still argue the ease of + // understanding is more valuable than the (relatively small) memory + // savings. + let mut ret_slc: [&'a str; NUM] = [""; NUM]; + for (idx, lookup) in lookups.iter().enumerate() { + let map = lookup.0; + let key = lookup.1; + let val = try_to_string(map, key)?; + ret_slc[idx] = val + } + Ok(ret_slc) } - Ok(ret_slc) } diff --git a/src/hyperlight_host/tests/integration_test.rs b/src/hyperlight_host/tests/integration_test.rs index 320d0bf10..03c26386a 100644 --- a/src/hyperlight_host/tests/integration_test.rs +++ b/src/hyperlight_host/tests/integration_test.rs @@ -63,7 +63,7 @@ fn guest_abort() { .unwrap_err(); println!("{:?}", res); assert!( - matches!(res, HyperlightError::GuestAborted(code, message) if (code == error_code && message.contains("NoException")) ) + matches!(res, HyperlightError::GuestAborted(code, message) if (code == error_code && message.is_empty())) ); } @@ -83,7 +83,7 @@ fn guest_abort_with_context1() { .unwrap_err(); println!("{:?}", res); assert!( - matches!(res, HyperlightError::GuestAborted(code, context) if (code == 25 && context.contains("NoException"))) + matches!(res, HyperlightError::GuestAborted(code, context) if (code == 25 && context == "Oh no")) ); } @@ -135,7 +135,7 @@ fn guest_abort_with_context2() { .unwrap_err(); println!("{:?}", res); assert!( - matches!(res, HyperlightError::GuestAborted(_, context) if context.contains("NoException")) + matches!(res, HyperlightError::GuestAborted(_, context) if context.contains("Guest abort buffer overflowed")) ); } @@ -161,7 +161,7 @@ fn guest_abort_c_guest() { .unwrap_err(); println!("{:?}", res); assert!( - matches!(res, HyperlightError::GuestAborted(code, message) if (code == 75 && message.contains("NoException")) ) + matches!(res, HyperlightError::GuestAborted(code, message) if (code == 75 && message == "This is a test error message") ) ); } @@ -181,7 +181,7 @@ fn guest_panic() { .unwrap_err(); println!("{:?}", res); assert!( - matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("NoException")) + matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("\nError... error...")) ) } @@ -261,7 +261,7 @@ fn guest_malloc_abort() { assert!(matches!( res.unwrap_err(), // OOM memory errors in rust allocator are panics. Our panic handler returns ErrorCode::UnknownError on panic - HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("NoException") + HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("memory allocation of ") )); } diff --git a/src/tests/rust_guests/callbackguest/src/main.rs b/src/tests/rust_guests/callbackguest/src/main.rs index c787599d5..a96b957f5 100644 --- a/src/tests/rust_guests/callbackguest/src/main.rs +++ b/src/tests/rust_guests/callbackguest/src/main.rs @@ -35,7 +35,7 @@ use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest::guest_function_definition::GuestFunctionDefinition; use hyperlight_guest::guest_function_register::register_function; use hyperlight_guest::host_function_call::{ - call_host_function, get_host_return_value, print_output_as_guest_function, + call_host_function, get_host_return_value, print_output_with_host_print, }; use hyperlight_guest::logging::log_message; @@ -167,7 +167,7 @@ pub extern "C" fn hyperlight_main() { "PrintOutput".to_string(), Vec::from(&[ParameterType::String]), ReturnType::Int, - print_output_as_guest_function as usize, + print_output_with_host_print as usize, ); register_function(print_output_def);