From 1a702dec3c99ec2ed0e6dd38852bc368761aa254 Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Sat, 19 Oct 2024 03:42:19 +0100 Subject: [PATCH 01/21] [hyperlight_host] Restrict OutBHandler{Caller,Wrapper} to pub(crate) In the future, the outb handler will need to take a Hypervisor instance in order to be able to access register and memory state of the VM, so it doesn't make sense for these interfaces to be more public than the `Hypervisor` trait. Nobody outside of Hyperlight seems to use these at the moment, so it's probably simplest to restrict these to `pub(crate)`. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> --- src/hyperlight_host/src/hypervisor/handlers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/handlers.rs b/src/hyperlight_host/src/hypervisor/handlers.rs index 9a091b2b1..3fa6c3db2 100644 --- a/src/hyperlight_host/src/hypervisor/handlers.rs +++ b/src/hyperlight_host/src/hypervisor/handlers.rs @@ -23,7 +23,7 @@ use crate::{Result, new_error}; /// The trait representing custom logic to handle the case when /// a Hypervisor's virtual CPU (vCPU) informs Hyperlight the guest /// has initiated an outb operation. -pub trait OutBHandlerCaller: Sync + Send { +pub(crate) trait OutBHandlerCaller: Sync + Send { /// Function that gets called when an outb operation has occurred. fn call(&mut self, port: u16, payload: u32) -> Result<()>; } @@ -34,7 +34,7 @@ pub trait OutBHandlerCaller: Sync + Send { /// Note: This needs to be wrapped in a Mutex to be able to grab a mutable /// reference to the underlying data (i.e., handle_outb in `Sandbox` takes /// a &mut self). -pub type OutBHandlerWrapper = Arc>; +pub(crate) type OutBHandlerWrapper = Arc>; pub(crate) type OutBHandlerFunction = Box Result<()> + Send>; From c3d65fc833da5997c3406becb68a37b846ccfac0 Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Sat, 19 Oct 2024 03:50:09 +0100 Subject: [PATCH 02/21] [hyperlight_host] Plumb a trace file descriptor around MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds (unused) support for creating trace files for sandboxes and passing them around to relevant sandbox event handler code. This will be used for collecting debug trace and profiling information. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> Signed-off-by: Doru Blânzeanu --- Cargo.lock | 10 ++++++ src/hyperlight_host/Cargo.toml | 3 ++ .../src/hypervisor/handlers.rs | 27 ++++++++++++++-- .../src/hypervisor/hyperv_linux.rs | 19 ++++++++++- .../src/hypervisor/hyperv_windows.rs | 16 +++++++++- src/hyperlight_host/src/hypervisor/kvm.rs | 17 +++++++++- src/hyperlight_host/src/hypervisor/mod.rs | 19 +++++++---- src/hyperlight_host/src/sandbox/mod.rs | 32 +++++++++++++++++++ src/hyperlight_host/src/sandbox/outb.rs | 22 ++++++++----- .../src/sandbox/uninitialized_evolve.rs | 11 +++++++ 10 files changed, 156 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40f25ed72..0706e2281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -616,6 +616,15 @@ dependencies = [ "log", ] +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1079,6 +1088,7 @@ dependencies = [ "crossbeam-queue", "elfcore", "env_logger", + "envy", "flatbuffers", "gdbstub", "gdbstub_arch", diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index ac56a57b0..d1e6d2965 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -44,6 +44,7 @@ anyhow = "1.0" metrics = "0.24.2" serde_json = "1.0" elfcore = "2.0" +uuid = { version = "1.17.0", features = ["v4"] } [target.'cfg(windows)'.dependencies] windows = { version = "0.61", features = [ @@ -79,6 +80,7 @@ mshv-ioctls3 = { package="mshv-ioctls", version = "=0.3.2", optional = true} [dev-dependencies] uuid = { version = "1.17.0", features = ["v4"] } signal-hook-registry = "1.4.5" +envy = { version = "0.4.2" } serde = "1.0" proptest = "1.7.0" tempfile = "3.20.0" @@ -126,6 +128,7 @@ executable_heap = [] print_debug = [] # Dumps the VM state to a file on unexpected errors or crashes. The path of the file will be printed on stdout and logged. crashdump = ["dep:chrono"] +trace_guest = [] kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"] # This feature is deprecated in favor of mshv3 mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] diff --git a/src/hyperlight_host/src/hypervisor/handlers.rs b/src/hyperlight_host/src/hypervisor/handlers.rs index 3fa6c3db2..186c030e2 100644 --- a/src/hyperlight_host/src/hypervisor/handlers.rs +++ b/src/hyperlight_host/src/hypervisor/handlers.rs @@ -18,6 +18,8 @@ use std::sync::{Arc, Mutex}; use tracing::{Span, instrument}; +#[cfg(feature = "trace_guest")] +use super::Hypervisor; use crate::{Result, new_error}; /// The trait representing custom logic to handle the case when @@ -25,7 +27,12 @@ use crate::{Result, new_error}; /// has initiated an outb operation. pub(crate) trait OutBHandlerCaller: Sync + Send { /// Function that gets called when an outb operation has occurred. - fn call(&mut self, port: u16, payload: u32) -> Result<()>; + fn call( + &mut self, + #[cfg(feature = "trace_guest")] hv: &mut dyn Hypervisor, + port: u16, + payload: u32, + ) -> Result<()>; } /// A convenient type representing a common way `OutBHandler` implementations @@ -36,6 +43,10 @@ pub(crate) trait OutBHandlerCaller: Sync + Send { /// a &mut self). pub(crate) type OutBHandlerWrapper = Arc>; +#[cfg(feature = "trace_guest")] +pub(crate) type OutBHandlerFunction = + Box Result<()> + Send>; +#[cfg(not(feature = "trace_guest"))] pub(crate) type OutBHandlerFunction = Box Result<()> + Send>; /// A `OutBHandler` implementation using a `OutBHandlerFunction` @@ -52,12 +63,22 @@ 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: u32) -> Result<()> { + fn call( + &mut self, + #[cfg(feature = "trace_guest")] hv: &mut dyn Hypervisor, + port: u16, + payload: u32, + ) -> Result<()> { let mut func = self .0 .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?; - func(port, payload) + func( + #[cfg(feature = "trace_guest")] + hv, + port, + payload, + ) } } diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index d9160b6f2..dd03bcb57 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -72,6 +72,8 @@ use crate::HyperlightError; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; use crate::sandbox::SandboxConfiguration; +#[cfg(feature = "trace_guest")] +use crate::sandbox::TraceInfo; #[cfg(crashdump)] use crate::sandbox::uninitialized::SandboxRuntimeConfig; use crate::{Result, log_then_return, new_error}; @@ -311,6 +313,9 @@ pub(crate) struct HypervLinuxDriver { gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] + #[allow(dead_code)] + trace_info: TraceInfo, } impl HypervLinuxDriver { @@ -322,6 +327,8 @@ impl HypervLinuxDriver { /// the underlying virtual CPU after this function returns. Call the /// `apply_registers` method to do that, or more likely call /// `initialise` to do it for you. + #[allow(clippy::too_many_arguments)] + // TODO: refactor this function to take fewer arguments. Add trace_info to rt_cfg #[instrument(skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn new( mem_regions: Vec, @@ -331,6 +338,7 @@ impl HypervLinuxDriver { config: &SandboxConfiguration, #[cfg(gdb)] gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] trace_info: TraceInfo, ) -> Result { let mshv = Mshv::new()?; let pr = Default::default(); @@ -438,6 +446,8 @@ impl HypervLinuxDriver { gdb_conn, #[cfg(crashdump)] rt_cfg, + #[cfg(feature = "trace_guest")] + trace_info, }; // Send the interrupt handle to the GDB thread if debugging is enabled @@ -668,7 +678,12 @@ impl Hypervisor for HypervLinuxDriver { outb_handle_fn .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .call(port, val)?; + .call( + #[cfg(feature = "trace_guest")] + self, + port, + val, + )?; // update rip self.vcpu_fd.set_reg(&[hv_register_assoc { @@ -1164,6 +1179,8 @@ mod tests { #[cfg(crashdump)] guest_core_dump: true, }, + #[cfg(feature = "trace_guest")] + TraceInfo::new().unwrap(), ) .unwrap(); } diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index a057c41cc..94204e1e2 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -60,6 +60,8 @@ use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT; use crate::hypervisor::wrappers::WHvGeneralRegisters; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; +#[cfg(feature = "trace_guest")] +use crate::sandbox::TraceInfo; #[cfg(crashdump)] use crate::sandbox::uninitialized::SandboxRuntimeConfig; use crate::{Result, debug, log_then_return, new_error}; @@ -283,6 +285,9 @@ pub(crate) struct HypervWindowsDriver { gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] + #[allow(dead_code)] + trace_info: TraceInfo, } /* This does not automatically impl Send/Sync because the host * address of the shared memory region is a raw pointer, which are @@ -294,6 +299,7 @@ unsafe impl Sync for HypervWindowsDriver {} impl HypervWindowsDriver { #[allow(clippy::too_many_arguments)] + // TODO: refactor this function to take fewer arguments. Add trace_info to rt_cfg #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn new( mem_regions: Vec, @@ -304,6 +310,7 @@ impl HypervWindowsDriver { mmap_file_handle: HandleWrapper, #[cfg(gdb)] gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] trace_info: TraceInfo, ) -> Result { // create and setup hypervisor partition let mut partition = VMPartition::new(1)?; @@ -354,6 +361,8 @@ impl HypervWindowsDriver { gdb_conn, #[cfg(crashdump)] rt_cfg, + #[cfg(feature = "trace_guest")] + trace_info, }; // Send the interrupt handle to the GDB thread if debugging is enabled @@ -683,7 +692,12 @@ impl Hypervisor for HypervWindowsDriver { outb_handle_fn .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .call(port, val)?; + .call( + #[cfg(feature = "trace_guest")] + self, + port, + val, + )?; let mut regs = self.processor.get_regs()?; regs.rip = rip + instruction_length; diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 0802ecb6b..bdc9887cd 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -46,6 +46,8 @@ use crate::HyperlightError; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; use crate::sandbox::SandboxConfiguration; +#[cfg(feature = "trace_guest")] +use crate::sandbox::TraceInfo; #[cfg(crashdump)] use crate::sandbox::uninitialized::SandboxRuntimeConfig; use crate::{Result, log_then_return, new_error}; @@ -298,12 +300,17 @@ pub(crate) struct KVMDriver { gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] + #[allow(dead_code)] + trace_info: TraceInfo, } impl KVMDriver { /// Create a new instance of a `KVMDriver`, with only control registers /// set. Standard registers will not be set, and `initialise` must /// be called to do so. + #[allow(clippy::too_many_arguments)] + // TODO: refactor this function to take fewer arguments. Add trace_info to rt_cfg #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn new( mem_regions: Vec, @@ -313,6 +320,7 @@ impl KVMDriver { config: &SandboxConfiguration, #[cfg(gdb)] gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] trace_info: TraceInfo, ) -> Result { let kvm = Kvm::new()?; @@ -380,6 +388,8 @@ impl KVMDriver { gdb_conn, #[cfg(crashdump)] rt_cfg, + #[cfg(feature = "trace_guest")] + trace_info, }; // Send the interrupt handle to the GDB thread if debugging is enabled @@ -586,7 +596,12 @@ impl Hypervisor for KVMDriver { outb_handle_fn .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .call(port, value)?; + .call( + #[cfg(feature = "trace_guest")] + self, + port, + value, + )?; } Ok(()) diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index ecf6acbc5..bb058a1f1 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -503,10 +503,12 @@ pub(crate) mod tests { use hyperlight_testing::dummy_guest_as_string; - use super::handlers::{MemAccessHandler, OutBHandler}; + use super::handlers::{MemAccessHandler, OutBHandler, OutBHandlerFunction}; #[cfg(gdb)] use crate::hypervisor::DbgMemAccessHandlerCaller; use crate::mem::ptr::RawPtr; + #[cfg(feature = "trace_guest")] + use crate::sandbox::TraceInfo; use crate::sandbox::uninitialized::GuestBinary; #[cfg(any(crashdump, gdb))] use crate::sandbox::uninitialized::SandboxRuntimeConfig; @@ -538,11 +540,6 @@ pub(crate) mod tests { return Ok(()); } - let outb_handler: Arc> = { - let func: Box Result<()> + Send> = - Box::new(|_, _| -> Result<()> { Ok(()) }); - Arc::new(Mutex::new(OutBHandler::from(func))) - }; let mem_access_handler = { let func: Box Result<()> + Send> = Box::new(|| -> Result<()> { Ok(()) }); Arc::new(Mutex::new(MemAccessHandler::from(func))) @@ -563,7 +560,17 @@ pub(crate) mod tests { &config, #[cfg(any(crashdump, gdb))] &rt_cfg, + #[cfg(feature = "trace_guest")] + TraceInfo::new()?, )?; + let outb_handler: Arc> = { + #[cfg(feature = "trace_guest")] + #[allow(clippy::type_complexity)] + let func: OutBHandlerFunction = Box::new(|_, _, _| -> Result<()> { Ok(()) }); + #[cfg(not(feature = "trace_guest"))] + let func: OutBHandlerFunction = Box::new(|_, _| -> Result<()> { Ok(()) }); + Arc::new(Mutex::new(OutBHandler::from(func))) + }; vm.initialise( RawPtr::from(0x230000), 1234567890, diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index 07389c369..209fb167c 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -43,6 +43,9 @@ pub mod snapshot; /// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm mod callable; +#[cfg(feature = "trace_guest")] +use std::sync::{Arc, Mutex}; + /// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm pub use callable::Callable; /// Re-export for `SandboxConfiguration` type @@ -89,6 +92,35 @@ pub fn is_hypervisor_present() -> bool { hypervisor::get_available_hypervisor().is_some() } +#[cfg(feature = "trace_guest")] +#[derive(Clone)] +/// The information that trace collection requires in order to write +/// an accurate trace. +pub(crate) struct TraceInfo { + /// The epoch against which trace events are timed; at least as + /// early as the creation of the sandbox being traced. + #[allow(dead_code)] + pub epoch: std::time::Instant, + /// The file to which the trace is being written + #[allow(dead_code)] + pub file: Arc>, +} +#[cfg(feature = "trace_guest")] +impl TraceInfo { + /// Create a new TraceInfo by saving the current time as the epoch + /// and generating a random filename. + pub fn new() -> crate::Result { + let mut path = std::env::current_dir()?; + path.push("trace"); + path.push(uuid::Uuid::new_v4().to_string()); + path.set_extension("trace"); + Ok(Self { + epoch: std::time::Instant::now(), + file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)), + }) + } +} + pub(crate) trait WrapperGetter { #[allow(dead_code)] fn get_mgr_wrapper(&self) -> &MemMgrWrapper; diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index dcdd96589..8ac8a2e6b 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -26,6 +26,8 @@ use tracing_log::format_trace; use super::host_funcs::FunctionRegistry; use super::mem_mgr::MemMgrWrapper; +#[cfg(feature = "trace_guest")] +use crate::hypervisor::Hypervisor; use crate::hypervisor::handlers::{OutBHandler, OutBHandlerFunction, OutBHandlerWrapper}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; @@ -149,6 +151,7 @@ fn outb_abort(mem_mgr: &mut MemMgrWrapper, data: u32) -> Resul fn handle_outb_impl( mem_mgr: &mut MemMgrWrapper, host_funcs: Arc>, + #[cfg(feature = "trace_guest")] _hv: &mut dyn Hypervisor, port: u16, data: u32, ) -> Result<()> { @@ -192,14 +195,17 @@ pub(crate) fn outb_handler_wrapper( mut mem_mgr_wrapper: MemMgrWrapper, host_funcs_wrapper: Arc>, ) -> OutBHandlerWrapper { - let outb_func: OutBHandlerFunction = Box::new(move |port, payload| { - handle_outb_impl( - &mut mem_mgr_wrapper, - host_funcs_wrapper.clone(), - port, - payload, - ) - }); + let outb_func: OutBHandlerFunction = + Box::new(move |#[cfg(feature = "trace_guest")] hv, port, payload| { + handle_outb_impl( + &mut mem_mgr_wrapper, + host_funcs_wrapper.clone(), + #[cfg(feature = "trace_guest")] + hv, + port, + payload, + ) + }); let outb_hdl = OutBHandler::from(outb_func); Arc::new(Mutex::new(outb_hdl)) } diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index 696eb92a1..07747dd9d 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -35,6 +35,8 @@ use crate::mem::ptr_offset::Offset; use crate::mem::shared_mem::GuestSharedMemory; #[cfg(any(feature = "init-paging", target_os = "windows"))] use crate::mem::shared_mem::SharedMemory; +#[cfg(feature = "trace_guest")] +use crate::sandbox::TraceInfo; #[cfg(gdb)] use crate::sandbox::config::DebugInfo; use crate::sandbox::host_funcs::FunctionRegistry; @@ -77,6 +79,8 @@ where &u_sbox.config, #[cfg(any(crashdump, gdb))] &u_sbox.rt_cfg, + #[cfg(feature = "trace_guest")] + TraceInfo::new()?, )?; let outb_hdl = outb_handler_wrapper(hshm.clone(), u_sbox.host_funcs.clone()); @@ -146,6 +150,7 @@ pub(crate) fn set_up_hypervisor_partition( mgr: &mut SandboxMemoryManager, #[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration, #[cfg(any(crashdump, gdb))] rt_cfg: &SandboxRuntimeConfig, + #[cfg(feature = "trace_guest")] trace_info: TraceInfo, ) -> Result> { #[cfg(feature = "init-paging")] let rsp_ptr = { @@ -217,6 +222,8 @@ pub(crate) fn set_up_hypervisor_partition( gdb_conn, #[cfg(crashdump)] rt_cfg.clone(), + #[cfg(feature = "trace_guest")] + trace_info, )?; Ok(Box::new(hv)) } @@ -233,6 +240,8 @@ pub(crate) fn set_up_hypervisor_partition( gdb_conn, #[cfg(crashdump)] rt_cfg.clone(), + #[cfg(feature = "trace_guest")] + trace_info, )?; Ok(Box::new(hv)) } @@ -255,6 +264,8 @@ pub(crate) fn set_up_hypervisor_partition( gdb_conn, #[cfg(crashdump)] rt_cfg.clone(), + #[cfg(feature = "trace_guest")] + trace_info, )?; Ok(Box::new(hv)) } From 21cf19bd989cd1985878371b8a6f6e837e2a9e2c Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:20:33 +0000 Subject: [PATCH 03/21] [hyperlight_host/exe] Allow load() to consume the exe_info This will be useful in the near future, when it will allow transforming the exe_info into unwind information without an extra copy. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> --- src/hyperlight_host/src/mem/elf.rs | 2 +- src/hyperlight_host/src/mem/exe.rs | 4 ++-- src/hyperlight_host/src/mem/mgr.rs | 6 +++--- src/hyperlight_host/src/sandbox/outb.rs | 15 ++++++--------- src/hyperlight_host/src/sandbox/uninitialized.rs | 4 ++-- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/hyperlight_host/src/mem/elf.rs b/src/hyperlight_host/src/mem/elf.rs index 3efe09b4f..afe7ea4a5 100644 --- a/src/hyperlight_host/src/mem/elf.rs +++ b/src/hyperlight_host/src/mem/elf.rs @@ -73,7 +73,7 @@ impl ElfInfo { .unwrap(); (max_phdr.p_vaddr + max_phdr.p_memsz - self.get_base_va()) as usize } - pub(crate) fn load_at(&self, load_addr: usize, target: &mut [u8]) -> Result<()> { + pub(crate) fn load_at(&mut self, load_addr: usize, target: &mut [u8]) -> Result<()> { let base_va = self.get_base_va(); for phdr in self.phdrs.iter().filter(|phdr| phdr.p_type == PT_LOAD) { let start_va = (phdr.p_vaddr - base_va) as usize; diff --git a/src/hyperlight_host/src/mem/exe.rs b/src/hyperlight_host/src/mem/exe.rs index bf1724317..6f72b7cae 100644 --- a/src/hyperlight_host/src/mem/exe.rs +++ b/src/hyperlight_host/src/mem/exe.rs @@ -71,9 +71,9 @@ impl ExeInfo { // copying into target, but the PE loader chooses to apply // relocations in its owned representation of the PE contents, // which requires it to be &mut. - pub fn load(&mut self, load_addr: usize, target: &mut [u8]) -> Result<()> { + pub fn load(self, load_addr: usize, target: &mut [u8]) -> Result<()> { match self { - ExeInfo::Elf(elf) => { + ExeInfo::Elf(mut elf) => { elf.load_at(load_addr, target)?; } } diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index f6ce32c26..856e9b361 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -311,7 +311,7 @@ impl SandboxMemoryManager { #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn load_guest_binary_into_memory( cfg: SandboxConfiguration, - exe_info: &mut ExeInfo, + exe_info: ExeInfo, guest_blob: Option<&GuestBlob>, ) -> Result { let guest_blob_size = guest_blob.map(|b| b.data.len()).unwrap_or(0); @@ -320,8 +320,8 @@ impl SandboxMemoryManager { let layout = SandboxMemoryLayout::new( cfg, exe_info.loaded_size(), - usize::try_from(cfg.get_stack_size(exe_info))?, - usize::try_from(cfg.get_heap_size(exe_info))?, + usize::try_from(cfg.get_stack_size(&exe_info))?, + usize::try_from(cfg.get_heap_size(&exe_info))?, guest_blob_size, guest_blob_mem_flags, )?; diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 8ac8a2e6b..5b25248ae 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -246,13 +246,10 @@ mod tests { let sandbox_cfg = SandboxConfiguration::default(); let new_mgr = || { - let mut exe_info = simple_guest_exe_info().unwrap(); - let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( - sandbox_cfg, - &mut exe_info, - None, - ) - .unwrap(); + let exe_info = simple_guest_exe_info().unwrap(); + let mut mgr = + SandboxMemoryManager::load_guest_binary_into_memory(sandbox_cfg, exe_info, None) + .unwrap(); let mem_size = mgr.get_shared_mem_mut().mem_size(); let layout = mgr.layout; let shared_mem = mgr.get_shared_mem_mut(); @@ -361,10 +358,10 @@ mod tests { let sandbox_cfg = SandboxConfiguration::default(); tracing::subscriber::with_default(subscriber.clone(), || { let new_mgr = || { - let mut exe_info = simple_guest_exe_info().unwrap(); + let exe_info = simple_guest_exe_info().unwrap(); let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( sandbox_cfg, - &mut exe_info, + exe_info, None, ) .unwrap(); diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index f5149f5cc..b2a69cd29 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -281,12 +281,12 @@ impl UninitializedSandbox { guest_binary: &GuestBinary, guest_blob: Option<&GuestBlob>, ) -> Result> { - let mut exe_info = match guest_binary { + let exe_info = match guest_binary { GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(bin_path_str)?, GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?, }; - SandboxMemoryManager::load_guest_binary_into_memory(cfg, &mut exe_info, guest_blob) + SandboxMemoryManager::load_guest_binary_into_memory(cfg, exe_info, guest_blob) } /// Set the max log level to be used by the guest. From 77c6ce728084ec58cb59ea351b46ae720be76719 Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Sat, 19 Oct 2024 05:06:21 +0100 Subject: [PATCH 04/21] [hyperlight_host/trace] Add HV interface for reading trace registers This adds a new interface which tracing code can use to request the values of registers from the hypervisor supervising a sandbox. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> --- src/hyperlight_host/Cargo.toml | 3 ++ .../src/hypervisor/hyperv_linux.rs | 31 +++++++++++++++++++ .../src/hypervisor/hyperv_windows.rs | 14 +++++++++ src/hyperlight_host/src/hypervisor/kvm.rs | 14 +++++++++ src/hyperlight_host/src/hypervisor/mod.rs | 19 ++++++++++++ 5 files changed, 81 insertions(+) diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index d1e6d2965..a27121d76 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -129,6 +129,9 @@ print_debug = [] # Dumps the VM state to a file on unexpected errors or crashes. The path of the file will be printed on stdout and logged. crashdump = ["dep:chrono"] trace_guest = [] +# This feature enables unwinding the guest stack from the host, in +# order to produce stack traces for debugging or profiling. +unwind_guest = [ "trace_guest" ] kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"] # This feature is deprecated in favor of mshv3 mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index dd03bcb57..5059188dc 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -48,11 +48,18 @@ use mshv_bindings::{ hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, hv_partition_synthetic_processor_features, }; +#[cfg(feature = "unwind_guest")] +use mshv_bindings::{ + hv_register_name, hv_register_name_HV_X64_REGISTER_RAX, hv_register_name_HV_X64_REGISTER_RBP, + hv_register_name_HV_X64_REGISTER_RCX, hv_register_name_HV_X64_REGISTER_RSP, +}; use mshv_ioctls::{Mshv, MshvError, VcpuFd, VmFd}; use tracing::{Span, instrument}; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; +#[cfg(feature = "unwind_guest")] +use super::TraceRegister; use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; #[cfg(gdb)] use super::gdb::{ @@ -547,6 +554,19 @@ impl Debug for HypervLinuxDriver { } } +#[cfg(feature = "unwind_guest")] +impl From for hv_register_name { + fn from(r: TraceRegister) -> Self { + match r { + TraceRegister::RAX => hv_register_name_HV_X64_REGISTER_RAX, + TraceRegister::RCX => hv_register_name_HV_X64_REGISTER_RCX, + TraceRegister::RIP => hv_register_name_HV_X64_REGISTER_RIP, + TraceRegister::RSP => hv_register_name_HV_X64_REGISTER_RSP, + TraceRegister::RBP => hv_register_name_HV_X64_REGISTER_RBP, + } + } +} + impl Hypervisor for HypervLinuxDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn initialise( @@ -1093,6 +1113,17 @@ impl Hypervisor for HypervLinuxDriver { Ok(()) } + + #[cfg(feature = "unwind_guest")] + fn read_trace_reg(&self, reg: TraceRegister) -> Result { + let mut assoc = [hv_register_assoc { + name: reg.into(), + ..Default::default() + }]; + self.vcpu_fd.get_reg(&mut assoc)?; + // safety: all registers that we currently support are 64-bit + unsafe { Ok(assoc[0].value.reg64) } + } } impl Drop for HypervLinuxDriver { diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 94204e1e2..e1263a810 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -44,6 +44,8 @@ use { std::sync::Mutex, }; +#[cfg(feature = "unwind_guest")] +use super::TraceRegister; use super::fpu::{FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper}; use super::surrogate_process::SurrogateProcess; @@ -1043,6 +1045,18 @@ impl Hypervisor for HypervWindowsDriver { Ok(()) } + + #[cfg(feature = "unwind_guest")] + fn read_trace_reg(&self, reg: TraceRegister) -> Result { + let regs = self.processor.get_regs()?; + match reg { + TraceRegister::RAX => Ok(regs.rax), + TraceRegister::RCX => Ok(regs.rcx), + TraceRegister::RIP => Ok(regs.rip), + TraceRegister::RSP => Ok(regs.rsp), + TraceRegister::RBP => Ok(regs.rbp), + } + } } impl Drop for HypervWindowsDriver { diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index bdc9887cd..a9adc1c82 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -29,6 +29,8 @@ use tracing::{Span, instrument}; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; +#[cfg(feature = "unwind_guest")] +use super::TraceRegister; use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; #[cfg(gdb)] use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason}; @@ -962,6 +964,18 @@ impl Hypervisor for KVMDriver { Ok(()) } + + #[cfg(feature = "unwind_guest")] + fn read_trace_reg(&self, reg: TraceRegister) -> Result { + let regs = self.vcpu_fd.get_regs()?; + Ok(match reg { + TraceRegister::RAX => regs.rax, + TraceRegister::RCX => regs.rcx, + TraceRegister::RIP => regs.rip, + TraceRegister::RSP => regs.rsp, + TraceRegister::RBP => regs.rbp, + }) + } } impl Drop for KVMDriver { diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index bb058a1f1..38e0ddfae 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -116,6 +116,21 @@ pub enum HyperlightExit { Retry(), } +/// Registers which may be useful for tracing/stack unwinding +#[cfg(feature = "trace_guest")] +pub enum TraceRegister { + /// RAX + RAX, + /// RCX + RCX, + /// RIP + RIP, + /// RSP + RSP, + /// RBP + RBP, +} + /// A common set of hypervisor functionality pub(crate) trait Hypervisor: Debug + Sync + Send { /// Initialise the internally stored vCPU with the given PEB address and @@ -251,6 +266,10 @@ pub(crate) trait Hypervisor: Debug + Sync + Send { ) -> Result<()> { unimplemented!() } + + /// Read a register for trace/unwind purposes + #[cfg(feature = "unwind_guest")] + fn read_trace_reg(&self, reg: TraceRegister) -> Result; } /// A virtual CPU that can be run until an exit occurs From f5a8084077c87580f265e5d23f3d5ecbc28b3b36 Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:56:17 +0000 Subject: [PATCH 05/21] [hyperlight_host/trace] Support collecting guest stacktraces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> Signed-off-by: Doru Blânzeanu --- Cargo.lock | 607 +++++++++--------- src/hyperlight_common/Cargo.toml | 3 +- src/hyperlight_common/src/outb.rs | 5 + src/hyperlight_host/Cargo.toml | 5 +- .../src/hypervisor/hyperv_linux.rs | 13 +- .../src/hypervisor/hyperv_windows.rs | 5 + src/hyperlight_host/src/hypervisor/kvm.rs | 5 + src/hyperlight_host/src/hypervisor/mod.rs | 13 +- src/hyperlight_host/src/mem/elf.rs | 110 +++- src/hyperlight_host/src/mem/exe.rs | 44 +- src/hyperlight_host/src/mem/mgr.rs | 9 +- src/hyperlight_host/src/sandbox/mod.rs | 43 +- src/hyperlight_host/src/sandbox/outb.rs | 82 ++- .../src/sandbox/uninitialized.rs | 13 +- .../src/sandbox/uninitialized_evolve.rs | 12 +- 15 files changed, 636 insertions(+), 333 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0706e2281..c92a5ed2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,20 +13,20 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy 0.8.26", ] [[package]] @@ -61,9 +61,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -76,36 +76,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -124,6 +124,18 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.88" @@ -137,15 +149,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -209,6 +221,19 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -249,9 +274,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -376,18 +401,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.36" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.36" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -397,15 +422,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation-sys" @@ -500,9 +531,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -633,14 +664,20 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.3.0" @@ -678,6 +715,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "framehop" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a5a3f0acb82df800ca3aa50c0d60d286c5d13d4cfc3114b3a9663f13b032fe" +dependencies = [ + "arrayvec", + "cfg-if", + "fallible-iterator", + "gimli", + "macho-unwind-info", + "pe-unwind-info", +] + [[package]] name = "futures" version = "0.3.31" @@ -793,15 +844,16 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.58.0", + "windows", ] [[package]] @@ -816,20 +868,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -842,12 +894,16 @@ name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] [[package]] name = "git2" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5220b8ba44c68a9a7f7a7659e864dd73692e417ef0211bea133c7b74e031eeb9" +checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ "bitflags 2.9.1", "libc", @@ -898,9 +954,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "foldhash", "serde", @@ -1079,6 +1135,7 @@ version = "0.7.0" dependencies = [ "anyhow", "bitflags 2.9.1", + "blake3", "built", "cfg-if", "cfg_aliases", @@ -1089,7 +1146,9 @@ dependencies = [ "elfcore", "env_logger", "envy", + "fallible-iterator", "flatbuffers", + "framehop", "gdbstub", "gdbstub_arch", "goblin", @@ -1138,8 +1197,8 @@ dependencies = [ "tracing-tracy", "uuid", "vmm-sys-util", - "windows 0.61.3", - "windows-result 0.3.4", + "windows", + "windows-result", "windows-sys 0.60.2", "windows-version", ] @@ -1182,7 +1241,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -1196,21 +1255,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1219,31 +1279,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1251,67 +1291,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -1325,9 +1352,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1335,9 +1362,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -1412,9 +1439,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", @@ -1425,9 +1452,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -1440,7 +1467,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -1499,9 +1526,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.18.1+1.9.0" +version = "0.18.2+1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" +checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" dependencies = [ "cc", "libc", @@ -1511,12 +1538,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] @@ -1532,9 +1559,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", @@ -1560,15 +1587,15 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1595,13 +1622,24 @@ dependencies = [ [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] +[[package]] +name = "macho-unwind-info" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4bdc8b0ce69932332cf76d24af69c3a155242af95c226b2ab6c2e371ed1149" +dependencies = [ + "thiserror 2.0.12", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", +] + [[package]] name = "managed" version = "0.8.0" @@ -1619,9 +1657,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "metrics" @@ -1675,22 +1713,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -1714,7 +1752,7 @@ dependencies = [ "libc", "num_enum", "vmm-sys-util", - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] @@ -1792,18 +1830,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro2", "quote", @@ -1825,6 +1864,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "oorandom" version = "11.1.5" @@ -1943,9 +1988,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1953,9 +1998,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1970,6 +2015,19 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pe-unwind-info" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500fa4cdeacd98997c5865e3d0d1cb8fe7e9d7d75ecc775e07989a433a9a9a59" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "thiserror 2.0.12", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2050,9 +2108,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -2063,13 +2121,22 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy 0.8.26", ] [[package]] @@ -2150,15 +2217,15 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -2180,9 +2247,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radix_trie" @@ -2220,7 +2287,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -2272,9 +2339,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] @@ -2285,7 +2352,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] @@ -2336,9 +2403,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.21" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8cea6b35bcceb099f30173754403d2eba0a5dc18cea3630fccd88251909288" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64", "bytes", @@ -2406,9 +2473,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -2427,9 +2494,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags 2.9.1", "errno", @@ -2440,9 +2507,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -2473,9 +2540,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea091f6cac2595aa38993f04f4ee692ed43757035c36e67c180b6828356385b1" +checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" dependencies = [ "sdd", ] @@ -2567,9 +2634,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -2613,9 +2680,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -2676,24 +2743,21 @@ checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2751,9 +2815,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -2767,7 +2831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2824,19 +2888,18 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -2896,9 +2959,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -2908,26 +2971,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic" version = "0.13.1" @@ -3008,9 +3078,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -3123,9 +3193,9 @@ dependencies = [ [[package]] name = "tracy-client" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db" +checksum = "ef54005d3d760186fd662dad4b7bb27ecd5531cdef54d1573ebd3f20a9205ed7" dependencies = [ "loom", "once_cell", @@ -3134,9 +3204,9 @@ dependencies = [ [[package]] name = "tracy-client-sys" -version = "0.24.3" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" +checksum = "5f9612d9503675b07b244922ea6f6f3cdd88c43add1b3498084613fc88cdf69d" dependencies = [ "cc", "windows-targets 0.52.6", @@ -3177,12 +3247,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3201,7 +3265,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -3265,9 +3329,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -3413,16 +3477,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -3430,7 +3484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link", "windows-numerics", @@ -3442,20 +3496,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -3464,11 +3505,11 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-result", + "windows-strings", ] [[package]] @@ -3477,22 +3518,11 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link", "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.0" @@ -3504,17 +3534,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -3538,19 +3557,10 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -3560,16 +3570,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -3754,9 +3754,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -3770,23 +3770,17 @@ dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -3796,9 +3790,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -3818,11 +3812,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "zerocopy-derive 0.8.24", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3838,9 +3832,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -3868,11 +3862,22 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -3881,9 +3886,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/src/hyperlight_common/Cargo.toml b/src/hyperlight_common/Cargo.toml index 1afea5022..42024e36d 100644 --- a/src/hyperlight_common/Cargo.toml +++ b/src/hyperlight_common/Cargo.toml @@ -25,6 +25,7 @@ spin = "0.10.0" [features] default = ["tracing"] fuzzing = ["dep:arbitrary"] +unwind_guest = [] std = [] [dev-dependencies] @@ -32,4 +33,4 @@ hyperlight-testing = { workspace = true } [lib] bench = false # see https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options -doctest = false # reduce noise in test output \ No newline at end of file +doctest = false # reduce noise in test output diff --git a/src/hyperlight_common/src/outb.rs b/src/hyperlight_common/src/outb.rs index 1d15cfb90..efa42aeba 100644 --- a/src/hyperlight_common/src/outb.rs +++ b/src/hyperlight_common/src/outb.rs @@ -90,11 +90,14 @@ impl TryFrom for Exception { /// - CallFunction: makes a call to a host function, /// - Abort: aborts the execution of the guest, /// - DebugPrint: prints a message to the host +/// - TraceRecordStack: records the stack trace of the guest pub enum OutBAction { Log = 99, CallFunction = 101, Abort = 102, DebugPrint = 103, + #[cfg(feature = "unwind_guest")] + TraceRecordStack = 104, } impl TryFrom for OutBAction { @@ -105,6 +108,8 @@ impl TryFrom for OutBAction { 101 => Ok(OutBAction::CallFunction), 102 => Ok(OutBAction::Abort), 103 => Ok(OutBAction::DebugPrint), + #[cfg(feature = "unwind_guest")] + 104 => Ok(OutBAction::TraceRecordStack), _ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)), } } diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index a27121d76..0a2311588 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -28,6 +28,8 @@ rand = { version = "0.9" } cfg-if = { version = "1.0.1" } libc = { version = "0.2.174" } flatbuffers = "25.2.10" +framehop = { version = "0.13.1", optional = true } +fallible-iterator = { version = "0.3.0", optional = true } page_size = "0.6.0" termcolor = "1.2.0" bitflags = "2.9.1" @@ -45,6 +47,7 @@ metrics = "0.24.2" serde_json = "1.0" elfcore = "2.0" uuid = { version = "1.17.0", features = ["v4"] } +blake3 = "1.8.2" [target.'cfg(windows)'.dependencies] windows = { version = "0.61", features = [ @@ -131,7 +134,7 @@ crashdump = ["dep:chrono"] trace_guest = [] # This feature enables unwinding the guest stack from the host, in # order to produce stack traces for debugging or profiling. -unwind_guest = [ "trace_guest" ] +unwind_guest = [ "trace_guest", "dep:framehop", "dep:fallible-iterator", "hyperlight-common/unwind_guest" ] kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"] # This feature is deprecated in favor of mshv3 mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index 5059188dc..ef56a7044 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -1124,6 +1124,11 @@ impl Hypervisor for HypervLinuxDriver { // safety: all registers that we currently support are 64-bit unsafe { Ok(assoc[0].value.reg64) } } + + #[cfg(feature = "trace_guest")] + fn trace_info_as_ref(&self) -> &TraceInfo { + &self.trace_info + } } impl Drop for HypervLinuxDriver { @@ -1143,6 +1148,8 @@ impl Drop for HypervLinuxDriver { #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "unwind_guest")] + use crate::mem::exe::DummyUnwindInfo; use crate::mem::memory_region::MemoryRegionVecBuilder; use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; @@ -1211,7 +1218,11 @@ mod tests { guest_core_dump: true, }, #[cfg(feature = "trace_guest")] - TraceInfo::new().unwrap(), + TraceInfo::new( + #[cfg(feature = "unwind_guest")] + Arc::new(DummyUnwindInfo {}), + ) + .unwrap(), ) .unwrap(); } diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index e1263a810..55b01e8bb 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -1057,6 +1057,11 @@ impl Hypervisor for HypervWindowsDriver { TraceRegister::RBP => Ok(regs.rbp), } } + + #[cfg(feature = "trace_guest")] + fn trace_info_as_ref(&self) -> &TraceInfo { + &self.trace_info + } } impl Drop for HypervWindowsDriver { diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index a9adc1c82..b63c44eb4 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -976,6 +976,11 @@ impl Hypervisor for KVMDriver { TraceRegister::RBP => regs.rbp, }) } + + #[cfg(feature = "trace_guest")] + fn trace_info_as_ref(&self) -> &TraceInfo { + &self.trace_info + } } impl Drop for KVMDriver { diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index 38e0ddfae..2be3fdf28 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -20,6 +20,8 @@ use tracing::{Span, instrument}; use crate::error::HyperlightError::ExecutionCanceledByHost; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::metrics::METRIC_GUEST_CANCELLATION; +#[cfg(feature = "trace_guest")] +use crate::sandbox::TraceInfo; use crate::{HyperlightError, Result, log_then_return, new_error}; /// Util for handling x87 fpu state @@ -270,6 +272,10 @@ pub(crate) trait Hypervisor: Debug + Sync + Send { /// Read a register for trace/unwind purposes #[cfg(feature = "unwind_guest")] fn read_trace_reg(&self, reg: TraceRegister) -> Result; + + /// Get a reference of the trace info for the guest + #[cfg(feature = "trace_guest")] + fn trace_info_as_ref(&self) -> &TraceInfo; } /// A virtual CPU that can be run until an exit occurs @@ -278,7 +284,7 @@ pub struct VirtualCPU {} impl VirtualCPU { /// Run the given hypervisor until a halt instruction is reached #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] - pub fn run( + pub(crate) fn run( hv: &mut dyn Hypervisor, outb_handle_fn: Arc>, mem_access_fn: Arc>, @@ -526,8 +532,6 @@ pub(crate) mod tests { #[cfg(gdb)] use crate::hypervisor::DbgMemAccessHandlerCaller; use crate::mem::ptr::RawPtr; - #[cfg(feature = "trace_guest")] - use crate::sandbox::TraceInfo; use crate::sandbox::uninitialized::GuestBinary; #[cfg(any(crashdump, gdb))] use crate::sandbox::uninitialized::SandboxRuntimeConfig; @@ -579,8 +583,7 @@ pub(crate) mod tests { &config, #[cfg(any(crashdump, gdb))] &rt_cfg, - #[cfg(feature = "trace_guest")] - TraceInfo::new()?, + sandbox.load_info, )?; let outb_handler: Arc> = { #[cfg(feature = "trace_guest")] diff --git a/src/hyperlight_host/src/mem/elf.rs b/src/hyperlight_host/src/mem/elf.rs index afe7ea4a5..ff83d42c8 100644 --- a/src/hyperlight_host/src/mem/elf.rs +++ b/src/hyperlight_host/src/mem/elf.rs @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +#[cfg(feature = "unwind_guest")] +use std::sync::Arc; + #[cfg(target_arch = "aarch64")] use goblin::elf::reloc::{R_AARCH64_NONE, R_AARCH64_RELATIVE}; #[cfg(target_arch = "x86_64")] @@ -26,13 +29,85 @@ use goblin::elf64::program_header::PT_LOAD; use crate::{Result, log_then_return, new_error}; +#[cfg(feature = "unwind_guest")] +struct ResolvedSectionHeader { + name: String, + addr: u64, + offset: u64, + size: u64, +} + pub(crate) struct ElfInfo { payload: Vec, phdrs: ProgramHeaders, + #[cfg(feature = "unwind_guest")] + shdrs: Vec, entry: u64, relocs: Vec, } +#[cfg(feature = "unwind_guest")] +struct UnwindInfo { + payload: Vec, + load_addr: u64, + va_size: u64, + base_svma: u64, + shdrs: Vec, +} + +#[cfg(feature = "unwind_guest")] +impl super::exe::UnwindInfo for UnwindInfo { + fn as_module(&self) -> framehop::Module> { + framehop::Module::new( + // TODO: plumb through a name from from_file if this + // came from a file + "guest".to_string(), + self.load_addr..self.load_addr + self.va_size, + self.load_addr, + self, + ) + } + fn hash(&self) -> blake3::Hash { + blake3::hash(&self.payload) + } +} + +#[cfg(feature = "unwind_guest")] +impl UnwindInfo { + fn resolved_section_header(&self, name: &[u8]) -> Option<&ResolvedSectionHeader> { + self.shdrs + .iter() + .find(|&sh| sh.name.as_bytes()[0..core::cmp::min(name.len(), sh.name.len())] == *name) + } +} + +#[cfg(feature = "unwind_guest")] +impl framehop::ModuleSectionInfo> for &UnwindInfo { + fn base_svma(&self) -> u64 { + self.base_svma + } + fn section_svma_range(&mut self, name: &[u8]) -> Option> { + let shdr = self.resolved_section_header(name)?; + Some(shdr.addr..shdr.addr + shdr.size) + } + fn section_data(&mut self, name: &[u8]) -> Option> { + if name == b".eh_frame" && self.resolved_section_header(b".debug_frame").is_some() { + /* Rustc does not always emit enough information for stack + * unwinding in .eh_frame, presumably because we use panic = + * abort in the guest. Framehop defaults to ignoring + * .debug_frame if .eh_frame exists, but we want the opposite + * behaviour here, since .debug_frame will actually contain + * frame information whereas .eh_frame often doesn't because + * of the aforementioned behaviour. Consequently, we hack + * around this by pretending that .eh_frame doesn't exist if + * .debug_frame does. */ + return None; + } + let shdr = self.resolved_section_header(name)?; + Some(self.payload[shdr.offset as usize..(shdr.offset + shdr.size) as usize].to_vec()) + } +} + impl ElfInfo { pub(crate) fn new(bytes: &[u8]) -> Result { let elf = Elf::parse(bytes)?; @@ -47,6 +122,19 @@ impl ElfInfo { Ok(ElfInfo { payload: bytes.to_vec(), phdrs: elf.program_headers, + #[cfg(feature = "unwind_guest")] + shdrs: elf + .section_headers + .iter() + .filter_map(|sh| { + Some(ResolvedSectionHeader { + name: elf.shdr_strtab.get_at(sh.sh_name)?.to_string(), + addr: sh.sh_addr, + offset: sh.sh_offset, + size: sh.sh_size, + }) + }) + .collect(), entry: elf.entry, relocs, }) @@ -73,7 +161,11 @@ impl ElfInfo { .unwrap(); (max_phdr.p_vaddr + max_phdr.p_memsz - self.get_base_va()) as usize } - pub(crate) fn load_at(&mut self, load_addr: usize, target: &mut [u8]) -> Result<()> { + pub(crate) fn load_at( + self, + load_addr: usize, + target: &mut [u8], + ) -> Result { let base_va = self.get_base_va(); for phdr in self.phdrs.iter().filter(|phdr| phdr.p_type == PT_LOAD) { let start_va = (phdr.p_vaddr - base_va) as usize; @@ -113,6 +205,20 @@ impl ElfInfo { } } } - Ok(()) + cfg_if::cfg_if! { + if #[cfg(feature = "unwind_guest")] { + let va_size = self.get_va_size() as u64; + let base_svma = self.get_base_va(); + Ok(Arc::new(UnwindInfo { + payload: self.payload, + load_addr: load_addr as u64, + va_size, + base_svma, + shdrs: self.shdrs, + })) + } else { + Ok(()) + } + } } } diff --git a/src/hyperlight_host/src/mem/exe.rs b/src/hyperlight_host/src/mem/exe.rs index 6f72b7cae..064d58cde 100644 --- a/src/hyperlight_host/src/mem/exe.rs +++ b/src/hyperlight_host/src/mem/exe.rs @@ -16,6 +16,8 @@ limitations under the License. use std::fs::File; use std::io::Read; +#[cfg(feature = "unwind_guest")] +use std::sync::Arc; use std::vec::Vec; use super::elf::ElfInfo; @@ -37,6 +39,41 @@ pub enum ExeInfo { const DEFAULT_ELF_STACK_RESERVE: u64 = 65536; const DEFAULT_ELF_HEAP_RESERVE: u64 = 131072; +#[cfg(feature = "unwind_guest")] +pub(crate) trait UnwindInfo: Send + Sync { + fn as_module(&self) -> framehop::Module>; + fn hash(&self) -> blake3::Hash; +} + +#[cfg(feature = "unwind_guest")] +pub(crate) struct DummyUnwindInfo {} +#[cfg(feature = "unwind_guest")] +impl UnwindInfo for DummyUnwindInfo { + fn as_module(&self) -> framehop::Module> { + framehop::Module::new("unsupported".to_string(), 0..0, 0, self) + } + fn hash(&self) -> blake3::Hash { + blake3::Hash::from_bytes([0; 32]) + } +} +#[cfg(feature = "unwind_guest")] +impl framehop::ModuleSectionInfo for &DummyUnwindInfo { + fn base_svma(&self) -> u64 { + 0 + } + fn section_svma_range(&mut self, _name: &[u8]) -> Option> { + None + } + fn section_data(&mut self, _name: &[u8]) -> Option { + None + } +} + +#[cfg(feature = "unwind_guest")] +pub(crate) type LoadInfo = Arc; +#[cfg(not(feature = "unwind_guest"))] +pub(crate) type LoadInfo = (); + impl ExeInfo { pub fn from_file(path: &str) -> Result { let mut file = File::open(path)?; @@ -71,12 +108,9 @@ impl ExeInfo { // copying into target, but the PE loader chooses to apply // relocations in its owned representation of the PE contents, // which requires it to be &mut. - pub fn load(self, load_addr: usize, target: &mut [u8]) -> Result<()> { + pub fn load(self, load_addr: usize, target: &mut [u8]) -> Result { match self { - ExeInfo::Elf(mut elf) => { - elf.load_at(load_addr, target)?; - } + ExeInfo::Elf(elf) => elf.load_at(load_addr, target), } - Ok(()) } } diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 856e9b361..2f268c409 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -313,7 +313,7 @@ impl SandboxMemoryManager { cfg: SandboxConfiguration, exe_info: ExeInfo, guest_blob: Option<&GuestBlob>, - ) -> Result { + ) -> Result<(Self, super::exe::LoadInfo)> { let guest_blob_size = guest_blob.map(|b| b.data.len()).unwrap_or(0); let guest_blob_mem_flags = guest_blob.map(|b| b.permissions); @@ -339,12 +339,15 @@ impl SandboxMemoryManager { shared_mem.write_u64(offset, load_addr_u64)?; } - exe_info.load( + let load_info = exe_info.load( load_addr.clone().try_into()?, &mut shared_mem.as_mut_slice()[layout.get_guest_code_offset()..], )?; - Ok(Self::new(layout, shared_mem, load_addr, entrypoint_offset)) + Ok(( + Self::new(layout, shared_mem, load_addr, entrypoint_offset), + load_info, + )) } /// Writes host function details to memory diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index 209fb167c..5a2f7be71 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -43,6 +43,8 @@ pub mod snapshot; /// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm mod callable; +#[cfg(feature = "unwind_guest")] +use std::io::Write; #[cfg(feature = "trace_guest")] use std::sync::{Arc, Mutex}; @@ -50,6 +52,8 @@ use std::sync::{Arc, Mutex}; pub use callable::Callable; /// Re-export for `SandboxConfiguration` type pub use config::SandboxConfiguration; +#[cfg(feature = "unwind_guest")] +use framehop::Unwinder; /// Re-export for the `MultiUseSandbox` type pub use initialized_multi_use::MultiUseSandbox; use tracing::{Span, instrument}; @@ -104,20 +108,53 @@ pub(crate) struct TraceInfo { /// The file to which the trace is being written #[allow(dead_code)] pub file: Arc>, + /// The unwind information for the current guest + #[cfg(feature = "unwind_guest")] + #[allow(dead_code)] + pub unwind_module: Arc, + /// The framehop unwinder for the current guest + #[cfg(feature = "unwind_guest")] + pub unwinder: framehop::x86_64::UnwinderX86_64>, + /// The framehop cache + #[cfg(feature = "unwind_guest")] + pub unwind_cache: Arc>, } #[cfg(feature = "trace_guest")] impl TraceInfo { /// Create a new TraceInfo by saving the current time as the epoch /// and generating a random filename. - pub fn new() -> crate::Result { + pub fn new( + #[cfg(feature = "unwind_guest")] unwind_module: Arc, + ) -> crate::Result { let mut path = std::env::current_dir()?; path.push("trace"); path.push(uuid::Uuid::new_v4().to_string()); path.set_extension("trace"); - Ok(Self { + #[cfg(feature = "unwind_guest")] + let hash = unwind_module.hash(); + #[cfg(feature = "unwind_guest")] + let (unwinder, unwind_cache) = { + let mut unwinder = framehop::x86_64::UnwinderX86_64::new(); + unwinder.add_module(unwind_module.clone().as_module()); + let cache = framehop::x86_64::CacheX86_64::new(); + (unwinder, Arc::new(Mutex::new(cache))) + }; + let ret = Self { epoch: std::time::Instant::now(), file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)), - }) + #[cfg(feature = "unwind_guest")] + unwind_module, + #[cfg(feature = "unwind_guest")] + unwinder, + #[cfg(feature = "unwind_guest")] + unwind_cache, + }; + /* write a frame identifying the binary */ + #[cfg(feature = "unwind_guest")] + self::outb::record_trace_frame(&ret, 0, |f| { + let _ = f.write_all(hash.as_bytes()); + })?; + Ok(ret) } } diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 5b25248ae..c1b210c44 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -14,8 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +#[cfg(feature = "unwind_guest")] +use std::io::Write; use std::sync::{Arc, Mutex}; +#[cfg(feature = "unwind_guest")] +use fallible_iterator::FallibleIterator; +#[cfg(feature = "unwind_guest")] +use framehop::Unwinder; use hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; @@ -26,12 +32,14 @@ use tracing_log::format_trace; use super::host_funcs::FunctionRegistry; use super::mem_mgr::MemMgrWrapper; -#[cfg(feature = "trace_guest")] -use crate::hypervisor::Hypervisor; use crate::hypervisor::handlers::{OutBHandler, OutBHandlerFunction, OutBHandlerWrapper}; +#[cfg(feature = "unwind_guest")] +use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::{HyperlightError, Result, new_error}; +#[cfg(feature = "trace_guest")] +use crate::{hypervisor::Hypervisor, sandbox::TraceInfo}; #[instrument(err(Debug), skip_all, parent = Span::current(), level="Trace")] pub(super) fn outb_log(mgr: &mut SandboxMemoryManager) -> Result<()> { @@ -142,7 +150,64 @@ fn outb_abort(mem_mgr: &mut MemMgrWrapper, data: u32) -> Resul buffer.push(b); } + Ok(()) +} + +#[cfg(feature = "unwind_guest")] +fn unwind( + hv: &dyn Hypervisor, + mem: &SandboxMemoryManager, + trace_info: &TraceInfo, +) -> Result> { + let mut read_stack = |addr| { + mem.shared_mem + .read::((addr - SandboxMemoryLayout::BASE_ADDRESS as u64) as usize) + .map_err(|_| ()) + }; + let mut cache = trace_info + .unwind_cache + .try_lock() + .map_err(|e| new_error!("could not lock unwinder cache {}\n", e))?; + let iter = trace_info.unwinder.iter_frames( + hv.read_trace_reg(crate::hypervisor::TraceRegister::RIP)?, + framehop::x86_64::UnwindRegsX86_64::new( + hv.read_trace_reg(crate::hypervisor::TraceRegister::RIP)?, + hv.read_trace_reg(crate::hypervisor::TraceRegister::RSP)?, + hv.read_trace_reg(crate::hypervisor::TraceRegister::RBP)?, + ), + &mut *cache, + &mut read_stack, + ); + iter.map(|f| Ok(f.address() - mem.layout.get_guest_code_address() as u64)) + .collect() + .map_err(|e| new_error!("couldn't unwind: {}", e)) +} + +#[cfg(feature = "unwind_guest")] +fn write_stack(out: &mut std::fs::File, stack: &[u64]) { + let _ = out.write_all(&stack.len().to_ne_bytes()); + for frame in stack { + let _ = out.write_all(&frame.to_ne_bytes()); + } +} +#[cfg(feature = "unwind_guest")] +pub(super) fn record_trace_frame( + trace_info: &TraceInfo, + frame_id: u64, + write_frame: F, +) -> Result<()> { + let Ok(mut out) = trace_info.file.lock() else { + return Ok(()); + }; + // frame structure: + // 16 bytes timestamp + let now = std::time::Instant::now().saturating_duration_since(trace_info.epoch); + let _ = out.write_all(&now.as_micros().to_ne_bytes()); + // 8 bytes frame type id + let _ = out.write_all(&frame_id.to_ne_bytes()); + // frame data + write_frame(&mut out); Ok(()) } @@ -183,6 +248,15 @@ fn handle_outb_impl( eprint!("{}", ch); Ok(()) } + #[cfg(feature = "unwind_guest")] + OutBAction::TraceRecordStack => { + let Ok(stack) = unwind(_hv, mem_mgr.as_ref(), _hv.trace_info_as_ref()) else { + return Ok(()); + }; + record_trace_frame(_hv.trace_info_as_ref(), 1u64, |f| { + write_stack(f, &stack); + }) + } } } @@ -247,7 +321,7 @@ mod tests { let new_mgr = || { let exe_info = simple_guest_exe_info().unwrap(); - let mut mgr = + let (mut mgr, _) = SandboxMemoryManager::load_guest_binary_into_memory(sandbox_cfg, exe_info, None) .unwrap(); let mem_size = mgr.get_shared_mem_mut().mem_size(); @@ -359,7 +433,7 @@ mod tests { tracing::subscriber::with_default(subscriber.clone(), || { let new_mgr = || { let exe_info = simple_guest_exe_info().unwrap(); - let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( + let (mut mgr, _) = SandboxMemoryManager::load_guest_binary_into_memory( sandbox_cfg, exe_info, None, diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index b2a69cd29..cfac5979c 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -78,6 +78,7 @@ pub struct UninitializedSandbox { pub(crate) config: SandboxConfiguration, #[cfg(any(crashdump, gdb))] pub(crate) rt_cfg: SandboxRuntimeConfig, + pub(crate) load_info: crate::mem::exe::LoadInfo, } impl Debug for UninitializedSandbox { @@ -222,8 +223,8 @@ impl UninitializedSandbox { } }; - let mut mem_mgr_wrapper = { - let mut mgr = UninitializedSandbox::load_guest_binary( + let (mut mem_mgr_wrapper, load_info) = { + let (mut mgr, load_info) = UninitializedSandbox::load_guest_binary( sandbox_cfg, &guest_binary, guest_blob.as_ref(), @@ -231,7 +232,7 @@ impl UninitializedSandbox { let stack_guard = Self::create_stack_guard(); mgr.set_stack_guard(&stack_guard)?; - MemMgrWrapper::new(mgr, stack_guard) + (MemMgrWrapper::new(mgr, stack_guard), load_info) }; mem_mgr_wrapper.write_memory_layout()?; @@ -250,6 +251,7 @@ impl UninitializedSandbox { config: sandbox_cfg, #[cfg(any(crashdump, gdb))] rt_cfg, + load_info, }; // If we were passed a writer for host print register it otherwise use the default. @@ -280,7 +282,10 @@ impl UninitializedSandbox { cfg: SandboxConfiguration, guest_binary: &GuestBinary, guest_blob: Option<&GuestBlob>, - ) -> Result> { + ) -> Result<( + SandboxMemoryManager, + crate::mem::exe::LoadInfo, + )> { let exe_info = match guest_binary { GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(bin_path_str)?, GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?, diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index 07747dd9d..ca6eb9e35 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -28,6 +28,7 @@ use super::uninitialized::SandboxRuntimeConfig; use crate::HyperlightError::NoHypervisorFound; use crate::hypervisor::Hypervisor; use crate::hypervisor::handlers::{MemAccessHandlerCaller, OutBHandlerCaller}; +use crate::mem::exe::LoadInfo; use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; @@ -79,8 +80,7 @@ where &u_sbox.config, #[cfg(any(crashdump, gdb))] &u_sbox.rt_cfg, - #[cfg(feature = "trace_guest")] - TraceInfo::new()?, + u_sbox.load_info, )?; let outb_hdl = outb_handler_wrapper(hshm.clone(), u_sbox.host_funcs.clone()); @@ -150,7 +150,7 @@ pub(crate) fn set_up_hypervisor_partition( mgr: &mut SandboxMemoryManager, #[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration, #[cfg(any(crashdump, gdb))] rt_cfg: &SandboxRuntimeConfig, - #[cfg(feature = "trace_guest")] trace_info: TraceInfo, + _load_info: LoadInfo, ) -> Result> { #[cfg(feature = "init-paging")] let rsp_ptr = { @@ -209,6 +209,12 @@ pub(crate) fn set_up_hypervisor_partition( None }; + #[cfg(feature = "trace_guest")] + let trace_info = TraceInfo::new( + #[cfg(feature = "unwind_guest")] + _load_info, + )?; + match *get_available_hypervisor() { #[cfg(mshv)] Some(HypervisorType::Mshv) => { From e027582b4824e6ef2927b47e74375e3cd7d8d653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Thu, 3 Jul 2025 12:13:32 +0300 Subject: [PATCH 06/21] allow dead code for unused functions triggered by previous commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- src/hyperlight_host/src/mem/layout.rs | 5 +++++ src/hyperlight_host/src/mem/mgr.rs | 1 + src/hyperlight_host/src/mem/ptr_offset.rs | 2 ++ src/hyperlight_host/src/mem/shared_mem.rs | 2 ++ src/hyperlight_host/src/mem/shared_mem_snapshot.rs | 2 +- src/hyperlight_host/src/sandbox/host_funcs.rs | 1 + 6 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/hyperlight_host/src/mem/layout.rs b/src/hyperlight_host/src/mem/layout.rs index 04edc9bcc..a8a1e5fc9 100644 --- a/src/hyperlight_host/src/mem/layout.rs +++ b/src/hyperlight_host/src/mem/layout.rs @@ -381,6 +381,7 @@ impl SandboxMemoryLayout { /// Get the offset in guest memory to the OutB pointer. #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(super) fn get_outb_pointer_offset(&self) -> usize { // The outb pointer is immediately after the code pointer // in the `CodeAndOutBPointers` struct which is a u64 @@ -389,6 +390,7 @@ impl SandboxMemoryLayout { /// Get the offset in guest memory to the OutB context. #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(super) fn get_outb_context_offset(&self) -> usize { // The outb context is immediately after the outb pointer // in the `CodeAndOutBPointers` struct which is a u64 @@ -416,6 +418,7 @@ impl SandboxMemoryLayout { /// This function exists to accommodate the macro that generates C API /// compatible functions. #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(crate) fn get_output_data_offset(&self) -> usize { self.output_data_buffer_offset } @@ -452,6 +455,7 @@ impl SandboxMemoryLayout { /// Get the offset in guest memory to the PEB address #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(super) fn get_in_process_peb_offset(&self) -> usize { self.peb_offset } @@ -486,6 +490,7 @@ impl SandboxMemoryLayout { /// Get the offset to the guest guard page #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub fn get_guard_page_offset(&self) -> usize { self.guard_page_offset } diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 2f268c409..3abdb11fe 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -285,6 +285,7 @@ where /// `shared_mem` to indicate the address of the outb pointer and context /// for calling outb function #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(crate) fn set_outb_address_and_context(&mut self, addr: u64, context: u64) -> Result<()> { let pointer_offset = self.layout.get_outb_pointer_offset(); let context_offset = self.layout.get_outb_context_offset(); diff --git a/src/hyperlight_host/src/mem/ptr_offset.rs b/src/hyperlight_host/src/mem/ptr_offset.rs index 673767e36..a8105ed70 100644 --- a/src/hyperlight_host/src/mem/ptr_offset.rs +++ b/src/hyperlight_host/src/mem/ptr_offset.rs @@ -32,11 +32,13 @@ pub(crate) struct Offset(u64); impl Offset { /// Get the offset representing `0` #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(super) fn zero() -> Self { Self::default() } /// round up to the nearest multiple of `alignment` + #[allow(dead_code)] pub(super) fn round_up_to(self, alignment: u64) -> Self { let remainder = self.0 % alignment; let multiples = self.0 / alignment; diff --git a/src/hyperlight_host/src/mem/shared_mem.rs b/src/hyperlight_host/src/mem/shared_mem.rs index 50c809f44..23d0b7fcf 100644 --- a/src/hyperlight_host/src/mem/shared_mem.rs +++ b/src/hyperlight_host/src/mem/shared_mem.rs @@ -502,6 +502,7 @@ impl ExclusiveSharedMemory { }) } + #[allow(dead_code)] pub(super) fn make_memory_executable(&self) -> Result<()> { #[cfg(target_os = "windows")] { @@ -616,6 +617,7 @@ impl ExclusiveSharedMemory { /// Return the address of memory at an offset to this `SharedMemory` checking /// that the memory is within the bounds of the `SharedMemory`. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] + #[allow(dead_code)] pub(crate) fn calculate_address(&self, offset: usize) -> Result { bounds_check!(offset, 0, self.mem_size()); Ok(self.base_addr() + offset) diff --git a/src/hyperlight_host/src/mem/shared_mem_snapshot.rs b/src/hyperlight_host/src/mem/shared_mem_snapshot.rs index b7f461716..dfa54430c 100644 --- a/src/hyperlight_host/src/mem/shared_mem_snapshot.rs +++ b/src/hyperlight_host/src/mem/shared_mem_snapshot.rs @@ -44,7 +44,7 @@ impl SharedMemorySnapshot { /// Take another snapshot of the internally-stored `SharedMemory`, /// then store it internally. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - + #[allow(dead_code)] pub(super) fn replace_snapshot(&mut self, shared_mem: &mut S) -> Result<()> { self.snapshot = shared_mem.with_exclusivity(|e| e.copy_all_to_vec())??; Ok(()) diff --git a/src/hyperlight_host/src/sandbox/host_funcs.rs b/src/hyperlight_host/src/sandbox/host_funcs.rs index 96751f391..6d3c8d98a 100644 --- a/src/hyperlight_host/src/sandbox/host_funcs.rs +++ b/src/hyperlight_host/src/sandbox/host_funcs.rs @@ -93,6 +93,7 @@ impl FunctionRegistry { /// Return `Ok` if the function was found and was of the right signature, /// and `Err` otherwise. #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] + #[allow(dead_code)] pub(super) fn host_print(&mut self, msg: String) -> Result { let res = self.call_host_func_impl("HostPrint", vec![ParameterValue::String(msg)])?; res.try_into() From d26ccba949aa22006bd40a09dba6860a76beea06 Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:41:55 +0000 Subject: [PATCH 07/21] Add trace support for recording memory allocations and frees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for producing profiles of memory usage. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> Signed-off-by: Doru Blânzeanu --- src/hyperlight_common/Cargo.toml | 1 + src/hyperlight_common/src/outb.rs | 10 ++++ src/hyperlight_guest_bin/Cargo.toml | 1 + src/hyperlight_guest_bin/src/lib.rs | 68 ++++++++++++++++++++++++- src/hyperlight_host/Cargo.toml | 3 +- src/hyperlight_host/src/sandbox/outb.rs | 30 +++++++++++ 6 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/hyperlight_common/Cargo.toml b/src/hyperlight_common/Cargo.toml index 42024e36d..cf317bd9d 100644 --- a/src/hyperlight_common/Cargo.toml +++ b/src/hyperlight_common/Cargo.toml @@ -26,6 +26,7 @@ spin = "0.10.0" default = ["tracing"] fuzzing = ["dep:arbitrary"] unwind_guest = [] +mem_profile = [] std = [] [dev-dependencies] diff --git a/src/hyperlight_common/src/outb.rs b/src/hyperlight_common/src/outb.rs index efa42aeba..43f07a9cf 100644 --- a/src/hyperlight_common/src/outb.rs +++ b/src/hyperlight_common/src/outb.rs @@ -91,6 +91,8 @@ impl TryFrom for Exception { /// - Abort: aborts the execution of the guest, /// - DebugPrint: prints a message to the host /// - TraceRecordStack: records the stack trace of the guest +/// - TraceMemoryAlloc: records memory allocation events +/// - TraceMemoryFree: records memory deallocation events pub enum OutBAction { Log = 99, CallFunction = 101, @@ -98,6 +100,10 @@ pub enum OutBAction { DebugPrint = 103, #[cfg(feature = "unwind_guest")] TraceRecordStack = 104, + #[cfg(feature = "mem_profile")] + TraceMemoryAlloc, + #[cfg(feature = "mem_profile")] + TraceMemoryFree, } impl TryFrom for OutBAction { @@ -110,6 +116,10 @@ impl TryFrom for OutBAction { 103 => Ok(OutBAction::DebugPrint), #[cfg(feature = "unwind_guest")] 104 => Ok(OutBAction::TraceRecordStack), + #[cfg(feature = "mem_profile")] + 105 => Ok(OutBAction::TraceMemoryAlloc), + #[cfg(feature = "mem_profile")] + 106 => Ok(OutBAction::TraceMemoryFree), _ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)), } } diff --git a/src/hyperlight_guest_bin/Cargo.toml b/src/hyperlight_guest_bin/Cargo.toml index 1b8202f51..a0347f866 100644 --- a/src/hyperlight_guest_bin/Cargo.toml +++ b/src/hyperlight_guest_bin/Cargo.toml @@ -17,6 +17,7 @@ and third-party code used by our C-API needed to build a native hyperlight-guest default = ["libc", "printf"] libc = [] # compile musl libc printf = [] # compile printf +mem_profile = ["hyperlight-common/unwind_guest","hyperlight-common/mem_profile"] [dependencies] hyperlight-guest = { workspace = true, default-features = false } diff --git a/src/hyperlight_guest_bin/src/lib.rs b/src/hyperlight_guest_bin/src/lib.rs index 5f153ebd2..133e282e7 100644 --- a/src/hyperlight_guest_bin/src/lib.rs +++ b/src/hyperlight_guest_bin/src/lib.rs @@ -28,6 +28,8 @@ use guest_function::register::GuestFunctionRegister; use guest_logger::init_logger; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::mem::HyperlightPEB; +#[cfg(feature = "mem_profile")] +use hyperlight_common::outb::OutBAction; use hyperlight_guest::exit::{abort_with_code_and_message, halt}; use hyperlight_guest::guest_handle::handle::GuestHandle; use log::LevelFilter; @@ -54,9 +56,69 @@ pub mod host_comm; pub mod memory; pub mod paging; +// Globals +#[cfg(feature = "mem_profile")] +struct ProfiledLockedHeap(LockedHeap); +#[cfg(feature = "mem_profile")] +unsafe impl alloc::alloc::GlobalAlloc for ProfiledLockedHeap { + unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 { + let addr = unsafe { self.0.alloc(layout) }; + unsafe { + core::arch::asm!("out dx, al", + in("dx") OutBAction::TraceMemoryAlloc as u16, + in("rax") layout.size() as u64, + in("rcx") addr as u64); + } + addr + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) { + unsafe { + core::arch::asm!("out dx, al", + in("dx") OutBAction::TraceMemoryFree as u16, + in("rax") layout.size() as u64, + in("rcx") ptr as u64); + self.0.dealloc(ptr, layout) + } + } + unsafe fn alloc_zeroed(&self, layout: core::alloc::Layout) -> *mut u8 { + let addr = unsafe { self.0.alloc_zeroed(layout) }; + unsafe { + core::arch::asm!("out dx, al", + in("dx") OutBAction::TraceMemoryAlloc as u16, + in("rax") layout.size() as u64, + in("rcx") addr as u64); + } + addr + } + unsafe fn realloc( + &self, + ptr: *mut u8, + layout: core::alloc::Layout, + new_size: usize, + ) -> *mut u8 { + let new_ptr = unsafe { self.0.realloc(ptr, layout, new_size) }; + unsafe { + core::arch::asm!("out dx, al", + in("dx") OutBAction::TraceMemoryFree as u16, + in("rax") layout.size() as u64, + in("rcx") ptr); + core::arch::asm!("out dx, al", + in("dx") OutBAction::TraceMemoryAlloc as u16, + in("rax") new_size as u64, + in("rcx") new_ptr); + } + new_ptr + } +} + // === Globals === +#[cfg(not(feature = "mem_profile"))] #[global_allocator] pub(crate) static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::empty(); +#[cfg(feature = "mem_profile")] +#[global_allocator] +pub(crate) static HEAP_ALLOCATOR: ProfiledLockedHeap<32> = + ProfiledLockedHeap(LockedHeap::<32>::empty()); pub(crate) static mut GUEST_HANDLE: GuestHandle = GuestHandle::new(); pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister = @@ -129,7 +191,11 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve let heap_start = (*peb_ptr).guest_heap.ptr as usize; let heap_size = (*peb_ptr).guest_heap.size as usize; - HEAP_ALLOCATOR + #[cfg(not(feature = "mem_profile"))] + let heap_allocator = &HEAP_ALLOCATOR; + #[cfg(feature = "mem_profile")] + let heap_allocator = &HEAP_ALLOCATOR.0; + heap_allocator .try_lock() .expect("Failed to access HEAP_ALLOCATOR") .init(heap_start, heap_size); diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 0a2311588..76a152b38 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -30,6 +30,7 @@ libc = { version = "0.2.174" } flatbuffers = "25.2.10" framehop = { version = "0.13.1", optional = true } fallible-iterator = { version = "0.3.0", optional = true } +blake3 = "1.8.2" page_size = "0.6.0" termcolor = "1.2.0" bitflags = "2.9.1" @@ -47,7 +48,6 @@ metrics = "0.24.2" serde_json = "1.0" elfcore = "2.0" uuid = { version = "1.17.0", features = ["v4"] } -blake3 = "1.8.2" [target.'cfg(windows)'.dependencies] windows = { version = "0.61", features = [ @@ -135,6 +135,7 @@ trace_guest = [] # This feature enables unwinding the guest stack from the host, in # order to produce stack traces for debugging or profiling. unwind_guest = [ "trace_guest", "dep:framehop", "dep:fallible-iterator", "hyperlight-common/unwind_guest" ] +mem_profile = [ "unwind_guest", "hyperlight-common/mem_profile" ] kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"] # This feature is deprecated in favor of mshv3 mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index c1b210c44..5a559321a 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -257,6 +257,36 @@ fn handle_outb_impl( write_stack(f, &stack); }) } + #[cfg(feature = "mem_profile")] + OutBAction::TraceMemoryAlloc => { + let Ok(stack) = unwind(_hv, mem_mgr.as_ref(), _hv.trace_info_as_ref()) else { + return Ok(()); + }; + let Ok(amt) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RAX) else { + return Ok(()); + }; + let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else { + return Ok(()); + }; + record_trace_frame(_hv.trace_info_as_ref(), 2u64, |f| { + let _ = f.write_all(&ptr.to_ne_bytes()); + let _ = f.write_all(&amt.to_ne_bytes()); + write_stack(f, &stack); + }) + } + #[cfg(feature = "mem_profile")] + OutBAction::TraceMemoryFree => { + let Ok(stack) = unwind(_hv, mem_mgr.as_ref(), _hv.trace_info_as_ref()) else { + return Ok(()); + }; + let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else { + return Ok(()); + }; + record_trace_frame(_hv.trace_info_as_ref(), 3u64, |f| { + let _ = f.write_all(&ptr.to_ne_bytes()); + write_stack(f, &stack); + }) + } } } From 7cc9a7afac0ce74c0eb43be1d405e3a259862ab0 Mon Sep 17 00:00:00 2001 From: Lucy Menon <168595099+syntactically@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:54:05 +0000 Subject: [PATCH 08/21] Add basic utility for dumping logs and memory statistics from traces Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> --- Cargo.lock | 717 ++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/trace_dump/Cargo.toml | 14 + src/trace_dump/main.rs | 831 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1529 insertions(+), 34 deletions(-) create mode 100644 src/trace_dump/Cargo.toml create mode 100644 src/trace_dump/main.rs diff --git a/Cargo.lock b/Cargo.lock index c92a5ed2c..709611f13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,14 @@ version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ + "cpp_demangle", + "fallible-iterator", "gimli", + "memmap2", + "object", + "rustc-demangle", + "smallvec", + "typed-arena", ] [[package]] @@ -136,6 +143,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "associative-cache" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46016233fc1bb55c23b856fe556b7db6ccd05119a0a392e04f0b3b7c79058f16" + [[package]] name = "async-trait" version = "0.1.88" @@ -144,7 +157,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -191,7 +204,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.104", ] [[package]] @@ -290,6 +303,31 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cairo-rs" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "cast" version = "0.3.0" @@ -303,14 +341,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" dependencies = [ "clap", - "heck", + "heck 0.5.0", "indexmap", "log", "proc-macro2", "quote", "serde", "serde_json", - "syn", + "syn 2.0.104", "tempfile", "toml", ] @@ -335,6 +373,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -438,12 +486,67 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "cpp_demangle" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -453,6 +556,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.6.0" @@ -553,7 +665,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -595,7 +707,19 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "dwrote" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", ] [[package]] @@ -684,6 +808,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "flatbuffers" version = "25.2.10" @@ -694,6 +827,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -706,6 +849,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -785,7 +943,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -899,6 +1057,39 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gio" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + [[package]] name = "git2" version = "0.20.2" @@ -912,6 +1103,53 @@ dependencies = [ "url", ] +[[package]] +name = "glib" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16aa2475c9debed5a32832cb5ff2af5a3f9e1ab9e69df58eaadc1ab2004d6eba" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b" +dependencies = [ + "anyhow", + "heck 0.4.1", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "glib-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glob" version = "0.3.2" @@ -931,6 +1169,17 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "gobject-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "goblin" version = "0.10.0" @@ -962,6 +1211,12 @@ dependencies = [ "serde", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1080,7 +1335,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasmparser", ] @@ -1093,7 +1348,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasmparser", ] @@ -1458,7 +1713,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1481,6 +1736,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "kvm-bindings" version = "0.13.0" @@ -1655,12 +1919,27 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "metrics" version = "0.24.2" @@ -1718,6 +1997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1846,7 +2126,7 @@ checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1855,7 +2135,9 @@ version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ + "flate2", "memchr", + "ruzstd", ] [[package]] @@ -1986,6 +2268,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "pango" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" +dependencies = [ + "bitflags 1.3.2", + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "pangocairo" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ad2ec87789371b551fd2367c10aa37060412ffd3e60abd99491b21b93a3f9b" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "glib", + "libc", + "pango", + "pangocairo-sys", +] + +[[package]] +name = "pangocairo-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848d2df9b7f1a8c7a19d994de443bcbe5d4382610ccb8e64247f932be74fcf76" +dependencies = [ + "cairo-sys-rs", + "glib-sys", + "libc", + "pango-sys", + "system-deps", +] + [[package]] name = "parking_lot" version = "0.12.4" @@ -2034,6 +2369,93 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "piet" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381186490a3e2017a506d62b759ea8eaf4be14666b13ed53973e8ae193451b1" +dependencies = [ + "kurbo", + "unic-bidi", +] + +[[package]] +name = "piet-cairo" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dc0b38ac300c79deb9bfc8c7f91a08f2b080338648f7202981094b22321bb9" +dependencies = [ + "cairo-rs", + "pango", + "pangocairo", + "piet", + "unicode-segmentation", + "xi-unicode", +] + +[[package]] +name = "piet-common" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd8497cc0bcfecb1e14e027428c5e3eaf9af6e14761176e1212006d8bdba387" +dependencies = [ + "cairo-rs", + "cairo-sys-rs", + "cfg-if", + "core-graphics", + "piet", + "piet-cairo", + "piet-coregraphics", + "piet-direct2d", + "piet-web", + "png", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "piet-coregraphics" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a819b41d2ddb1d8abf3e45e49422f866cba281b4abb5e2fb948bba06e2c3d3f7" +dependencies = [ + "associative-cache", + "core-foundation", + "core-foundation-sys", + "core-graphics", + "core-text", + "foreign-types", + "piet", +] + +[[package]] +name = "piet-direct2d" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd00e91df4f987be40eb13042afe6ee9e54468466bdb7486390b40d4fef0993e" +dependencies = [ + "associative-cache", + "dwrote", + "piet", + "utf16_lit", + "winapi", + "wio", +] + +[[package]] +name = "piet-web" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a560232a94e535979923d49062d1c6d5407b3804bcd0d0b4cb9e25a9b41db1e" +dependencies = [ + "js-sys", + "piet", + "unicode-segmentation", + "wasm-bindgen", + "web-sys", + "xi-unicode", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -2051,7 +2473,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2106,6 +2528,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -2146,7 +2581,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] @@ -2212,7 +2681,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2456,7 +2925,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn", + "syn 2.0.104", "walkdir", ] @@ -2523,6 +2992,15 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ruzstd" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" @@ -2576,7 +3054,7 @@ checksum = "22fc4f90c27b57691bbaf11d8ecc7cfbfe98a4da6dbe60226115d322aa80c06e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2617,7 +3095,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2675,7 +3153,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2735,6 +3213,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "sketches-ddsketch" version = "0.3.0" @@ -2787,12 +3271,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.104" @@ -2821,9 +3322,28 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.20.0" @@ -2872,7 +3392,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2883,7 +3403,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2943,7 +3463,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2966,7 +3486,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -2978,6 +3498,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -2989,7 +3520,7 @@ dependencies = [ "serde_spanned", "toml_datetime", "toml_write", - "winnow", + "winnow 0.7.11", ] [[package]] @@ -3064,6 +3595,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "trace_dump" +version = "0.0.0" +dependencies = [ + "addr2line", + "blake3", + "piet-common", +] + [[package]] name = "tracing" version = "0.1.41" @@ -3084,7 +3624,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3218,6 +3758,22 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.18.0" @@ -3230,12 +3786,69 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unic-bidi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09" +dependencies = [ + "matches", + "unic-ucd-bidi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-bidi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "url" version = "2.5.4" @@ -3247,6 +3860,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf16_lit" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14706d2a800ee8ff38c1d3edb873cd616971ea59eb7c0d046bb44ef59b06a1ae" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3283,6 +3902,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -3364,7 +3989,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -3399,7 +4024,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3531,7 +4156,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3542,7 +4167,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3752,6 +4377,15 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.11" @@ -3761,6 +4395,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -3776,6 +4419,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + [[package]] name = "yoke" version = "0.8.0" @@ -3796,7 +4445,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -3827,7 +4476,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3838,7 +4487,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3858,7 +4507,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -3892,5 +4541,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] diff --git a/Cargo.toml b/Cargo.toml index 2c16795c3..28ca73d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "src/hyperlight_guest_bin", "src/hyperlight_component_util", "src/hyperlight_component_macro", + "src/trace_dump", ] # Guests have custom linker flags, so we need to exclude them from the workspace exclude = [ diff --git a/src/trace_dump/Cargo.toml b/src/trace_dump/Cargo.toml new file mode 100644 index 000000000..0149980e8 --- /dev/null +++ b/src/trace_dump/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "trace_dump" +version = "0.0.0" +publish = false +edition = "2021" + +[dependencies] +addr2line = "0.24.2" +piet-common = { version = "0.6.2", features = [ "png" ] } +blake3 = { version = "1.5.5" } + +[[bin]] +name = "trace_dump" +path = "main.rs" diff --git a/src/trace_dump/main.rs b/src/trace_dump/main.rs new file mode 100644 index 000000000..c187efac1 --- /dev/null +++ b/src/trace_dump/main.rs @@ -0,0 +1,831 @@ +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::Mutex; +use std::time::Duration; + +use piet_common::{kurbo, Color, RenderContext, Text, TextLayout, TextLayoutBuilder}; + +fn read_u128(inf: &mut File) -> Result { + let mut bytes: [u8; 16] = [0; 16]; + inf.read_exact(&mut bytes)?; + Ok(u128::from_ne_bytes(bytes)) +} +fn read_u64(inf: &mut File) -> Option { + let mut bytes: [u8; 8] = [0; 8]; + inf.read_exact(&mut bytes).ok()?; + Some(u64::from_ne_bytes(bytes)) +} +fn read_u64_vec(inf: &mut File) -> Option> { + let len = read_u64(inf)?; + let mut vec = Vec::with_capacity(len as usize); + for _ in 0..len { + vec.push(read_u64(inf)?); + } + Some(vec) +} +fn read_blake3_hash(inf: &mut File) -> Option { + let mut bytes: [u8; 32] = [0; 32]; + inf.read_exact(&mut bytes).ok()?; + Some(blake3::Hash::from_bytes(bytes)) +} + +fn dump_stack(state: &mut State, trace: Rc<[u64]>) { + for frame in &*trace { + println!(" {:x} {}", frame, state.symbol_cache.format_symbol(*frame)); + } +} + +fn dump_ident(_state: &mut State, _rs: &mut (), now: Duration, hash: blake3::Hash) -> Option<()> { + println!("\n[{:9?}] BLAKE3 hash of binary is {}", now, hash); + Some(()) +} + +fn dump_unwind(state: &mut State, _rs: &mut (), now: Duration, trace: Rc<[u64]>) -> Option<()> { + println!("\n[{:9?}] Guest requested stack trace", now); + dump_stack(state, trace); + Some(()) +} + +fn dump_alloc( + state: &mut State, + _rs: &mut (), + now: Duration, + ptr: u64, + amt: u64, + trace: Rc<[u64]>, +) -> Option<()> { + println!("\n[{:9?}] Allocated {} bytes at 0x{:x}", now, amt, ptr); + dump_stack(state, trace); + Some(()) +} + +fn dump_free( + state: &mut State, + _rs: &mut (), + now: Duration, + ptr: u64, + amt: u64, + trace: Rc<[u64]>, +) -> Option<()> { + println!("\n[{:9?}] Freed {} bytes at 0x{:x}", now, amt, ptr); + dump_stack(state, trace); + Some(()) +} + +// todo: this should use something more reasonable than a hash table +// for each node. let's measure the out-degree and see if a small +// array is better, to start. +struct TraceTrie { + value: T, + children: HashMap>, +} +impl TraceTrie { + fn new() -> Self { + Self { + value: Default::default(), + children: HashMap::new(), + } + } + fn apply_path<'a, 'i, F: Fn(&mut T), I: Iterator>( + &'a mut self, + trace: I, + f: F, + ) { + let mut node = self; + for frame in trace { + f(&mut node.value); + node = (*node).children.entry(*frame).or_insert(Self::new()) + } + f(&mut node.value); + } +} + +struct SymbolCache { + loader: addr2line::Loader, + symbol_cache: HashMap)>>, +} +impl SymbolCache { + fn resolve_symbol<'c>(&'c mut self, addr: u64) -> &'c Option<(String, Option)> { + self.symbol_cache.entry(addr).or_insert_with(|| { + let frame = &self.loader.find_frames(addr).ok()?.next().ok()??; + let function = frame.function.as_ref()?; + let demangled = + addr2line::demangle_auto(function.name.to_string_lossy(), function.language) + .to_string(); + Some((demangled, frame.location.as_ref()?.line)) + }) + } + fn format_symbol(&mut self, addr: u64) -> String { + match self.resolve_symbol(addr) { + None => format!("{}", addr), + Some((f, None)) => f.clone(), + Some((f, Some(l))) => format!("{}:{}", f, l), + } + } +} + +enum Visualisation { + Bar, + Flame, +} +impl std::fmt::Display for Visualisation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Visualisation::Bar => write!(f, "bar"), + Visualisation::Flame => write!(f, "flame"), + } + } +} + +struct State { + inf: File, + symbol_cache: SymbolCache, + out_dir: Option, + start_time: Option, + end_time: Option, + allocs: HashMap)>, + sites: HashMap, + traces: TraceTrie, + total: u64, + max_total: u64, + max_duration: Duration, + num_durations: u64, +} + +struct ViewParams { + margin: f64, + width: f64, + height: f64, + label_gap: f64, + amount_gap: f64, + bar_start: f64, + bar_height: f64, + bar_leading: f64, + bar_brush: R::Brush, +} + +fn draw_bg(render_context: &mut R, v: &ViewParams) { + let bg_brush = render_context.solid_brush(Color::rgb8(255, 255, 255)); + render_context.fill( + kurbo::Rect { + x0: 0.0, + y0: 0.0, + x1: v.width + v.margin * 2.0, + y1: v.height + v.margin * 2.0, + }, + &bg_brush, + ); +} +fn draw_bar( + render_context: &mut R, + v: &ViewParams, + stroke: bool, + n: u64, + label: String, + value: u64, + max_value: u64, +) -> Option<()> { + let left = v.margin + v.bar_start; + let top = v.margin + (n as f64) * (v.bar_height + v.bar_leading); + if stroke { + render_context.stroke( + kurbo::Rect { + x0: left, + y0: top, + //x1: v.margin + v.width, + x1: left + (v.width - v.bar_start), + y1: top + v.bar_height, + }, + &v.bar_brush, + 1.0, + ); + } + let right = left + (v.width - v.bar_start) * value as f64 / max_value as f64; + render_context.fill( + kurbo::Rect { + x0: left, + y0: top, + x1: right, + y1: top + v.bar_height, + }, + &v.bar_brush, + ); + let layout = render_context.text().new_text_layout(label).build().ok()?; + let metric = layout + .line_metric(0) + .expect("there must be at least one line metric"); + render_context.draw_text( + &layout, + kurbo::Point { + x: left - v.label_gap - layout.trailing_whitespace_width(), + y: top - metric.y_offset + (v.bar_height - metric.baseline) / 2.0, + }, + ); + let layout = render_context + .text() + .new_text_layout(format!("{}", value)) + .default_attribute(piet_common::TextAttribute::TextColor(Color::rgb8( + 255, 255, 255, + ))) + .build() + .ok()?; + let metric = layout + .line_metric(0) + .expect("there must be at least one line metric"); + if right - left > v.amount_gap + layout.trailing_whitespace_width() { + render_context.draw_text( + &layout, + kurbo::Point { + x: right - v.amount_gap - layout.trailing_whitespace_width(), + y: top + (v.bar_height - metric.height) / 2.0, + }, + ); + } else { + // hopefully the choice of colour doesn't affect the layout much + let layout = render_context + .text() + .new_text_layout(format!("{}", value)) + .build() + .ok()?; + let metric = layout + .line_metric(0) + .expect("there must be at least one line metric"); + render_context.draw_text( + &layout, + kurbo::Point { + x: right + v.amount_gap, + y: top + (v.bar_height - metric.height) / 2.0, + }, + ); + } + Some(()) +} + +trait RenderWrapper { + fn render(&mut self, ctx: &mut R, width: u64, height: u64) -> Option<()>; +} +fn render_bitmap( + mut out: O, + device: &mut piet_common::Device, + mut render: F, +) -> Option<()> { + let width = 1920; + let height = 1080; + let mut bitmap = device.bitmap_target(width, height, 1.0).ok()?; + { + let mut render_context = bitmap.render_context(); + render.render(&mut render_context, width as u64, height as u64)?; + render_context.finish().ok()?; + } + out.write_all( + bitmap + .to_image_buf(piet_common::ImageFormat::RgbaPremul) + .expect("unable to access image buffer") + .raw_pixels(), + ) + .expect("write to stdout"); + Some(()) +} + +impl ViewParams { + fn new(ctx: &mut R, ht: u64, wd: u64) -> Self { + let margin = 20.0; + let width = wd as f64 - margin * 2.0; + let height = ht as f64 - margin * 2.0; + let bar_brush = ctx.solid_brush(Color::rgb8(128, 0, 128)); + Self { + margin, + width, + height, + label_gap: 10.0, + amount_gap: 5.0, + bar_start: width / 4.0, + bar_height: 12.0, + bar_leading: 4.0, + bar_brush: bar_brush, + } + } +} + +struct BarRenderer<'r> { + state: &'r mut State, + now: Duration, +} +impl<'r, 'a, 's> RenderWrapper for BarRenderer<'r> { + fn render(&mut self, ctx: &mut R, wd: u64, ht: u64) -> Option<()> { + let v = ViewParams::new(ctx, ht, wd); + draw_bg(ctx, &v); + draw_bar( + ctx, + &v, + true, + 0, + "Execution time".to_string(), + self.now.as_micros() as u64, + self.state.max_duration.as_micros() as u64, + )?; + draw_bar( + ctx, + &v, + true, + 1, + "Total memory consumption".to_string(), + self.state.total, + self.state.max_total, + )?; + + let mut points: Vec<(&u64, &u64)> = self.state.sites.iter().collect(); + points.sort_by_key(|(_, size)| *size); + for (i, (site, size)) in points.iter().rev().enumerate() { + draw_bar( + ctx, + &v, + false, + (3 + i) as u64, + (&mut self.state.symbol_cache).format_symbol(**site), + **size, + self.state.total, + )?; + } + Some(()) + } +} + +struct FlameRenderer<'r> { + state: &'r mut State, + now: Duration, +} +#[derive(Clone, Copy)] +struct FlameView { + total_allocated: u64, + bottom: f64, + left: f64, + color: u8, +} +fn draw_flame( + ctx: &mut R, + v: &ViewParams, + fv: &FlameView, + sc: &mut SymbolCache, + t: &TraceTrie, + addr: Option, +) -> Option<()> { + let rect = kurbo::Rect { + x0: v.margin + fv.left, + y0: v.margin + fv.bottom - v.bar_height, + x1: v.margin + fv.left + (t.value as f64) * v.width / (fv.total_allocated as f64), + y1: v.margin + fv.bottom, + }; + ctx.fill(rect, &Color::rgb8(255, 0, fv.color)); + if let Some(addr) = addr { + ctx.save().ok()?; + ctx.clip(rect); + let layout = ctx + .text() + .new_text_layout(sc.format_symbol(addr)) + .default_attribute(piet_common::TextAttribute::FontSize(9.0)) + .build() + .ok()?; + ctx.draw_text( + &layout, + kurbo::Point { + x: v.margin + fv.left, + y: v.margin + fv.bottom - v.bar_height, + }, + ); + ctx.restore().ok()?; + } + let mut child_fv = FlameView { + total_allocated: fv.total_allocated, + bottom: fv.bottom - v.bar_height, + left: fv.left, + color: fv.color, + }; + for (addr, child) in &t.children { + draw_flame(ctx, v, &child_fv, sc, child, Some(*addr))?; + child_fv.left += (child.value as f64) * v.width / (fv.total_allocated as f64); + child_fv.color = child_fv.color.wrapping_add(85); + } + Some(()) +} +impl<'r, 'a, 's> RenderWrapper for FlameRenderer<'r> { + fn render(&mut self, ctx: &mut R, wd: u64, ht: u64) -> Option<()> { + let mut v = ViewParams::new(ctx, ht, wd); + v.bar_start = v.width / 8.0; + draw_bg(ctx, &v); + draw_bar( + ctx, + &v, + true, + 0, + "Execution time".to_string(), + self.now.as_micros() as u64, + self.state.max_duration.as_micros() as u64, + )?; + draw_bar( + ctx, + &v, + true, + 1, + "Total memory consumption".to_string(), + self.state.total, + self.state.max_total, + )?; + + let fv = FlameView { + total_allocated: self.state.total, + bottom: v.height, + left: 0.0, + color: 0, + }; + draw_flame( + ctx, + &v, + &fv, + &mut self.state.symbol_cache, + &self.state.traces, + None, + )?; + Some(()) + } +} + +struct RenderState<'a> { + device: &'a mut piet_common::Device, + bar_out: std::process::ChildStdin, + flame_out: std::process::ChildStdin, +} +fn render_state(state: &mut State, rs: &mut RenderState, now: Duration) -> Option<()> { + let late_enough = state.start_time.map(|t| now >= t).unwrap_or(true); + let early_enough = state.end_time.map(|t| now <= t).unwrap_or(true); + if late_enough && early_enough { + render_bitmap(&mut rs.bar_out, rs.device, BarRenderer { state, now })?; + render_bitmap(&mut rs.flame_out, rs.device, FlameRenderer { state, now })?; + } + Some(()) +} + +fn render_ident( + _state: &mut State, + _rs: &mut RenderState, + _now: Duration, + _hash: blake3::Hash, +) -> Option<()> { + Some(()) +} + +fn render_unwind( + _state: &mut State, + _rs: &mut RenderState, + _now: Duration, + _trace: Rc<[u64]>, +) -> Option<()> { + Some(()) +} + +fn render_alloc( + state: &mut State, + rs: &mut RenderState, + now: Duration, + _ptr: u64, + amt: u64, + trace: Rc<[u64]>, +) -> Option<()> { + for frame in trace.as_ref() { + *state.sites.entry(*frame).or_insert(0) += amt; + } + state.traces.apply_path(trace.iter().rev(), |t| *t += amt); + render_state(state, rs, now)?; + Some(()) +} + +fn render_free( + state: &mut State, + rs: &mut RenderState, + now: Duration, + ptr: u64, + _amt: u64, + _trace: Rc<[u64]>, +) -> Option<()> { + let (amt, trace) = state + .allocs + .get(&ptr) + .expect("free of un-allocated address"); + for frame in trace.as_ref() { + *state + .sites + .get_mut(frame) + .expect("free of un-allocated site") -= amt; + } + state.traces.apply_path(trace.iter().rev(), |t| *t -= amt); + render_state(state, rs, now)?; + Some(()) +} + +fn read_file( + state: &mut State, + mut handle_state: S, + handle_ident: I, + handle_unwind: U, + handle_alloc: A, + handle_free: F, +) -> Option<()> +where + I: Fn(&mut State, &mut S, Duration, blake3::Hash) -> Option<()>, + U: Fn(&mut State, &mut S, Duration, Rc<[u64]>) -> Option<()>, + A: Fn(&mut State, &mut S, Duration, u64, u64, Rc<[u64]>) -> Option<()>, + F: Fn(&mut State, &mut S, Duration, u64, u64, Rc<[u64]>) -> Option<()>, +{ + loop { + let time = match read_u128(&mut state.inf) { + Ok(t) => t, + Err(e) => { + if e.kind() == std::io::ErrorKind::UnexpectedEof { + break; + } else { + return None; + } + } + }; + let now = Duration::from_micros(time.try_into().expect("duration too large for u64")); + state.max_duration = std::cmp::max(state.max_duration, now); + state.num_durations += 1; + + let frame_id = read_u64(&mut state.inf)?; + + if frame_id == 0 { + let hash = read_blake3_hash(&mut state.inf)?; + handle_ident(state, &mut handle_state, now, hash)?; + } else if frame_id == 1 { + let trace: Rc<[u64]> = read_u64_vec(&mut state.inf)?.into(); + handle_unwind(state, &mut handle_state, now, trace)?; + } else if frame_id == 2 { + let ptr = read_u64(&mut state.inf)?; + let amt = read_u64(&mut state.inf)?; + let trace: Rc<[u64]> = read_u64_vec(&mut state.inf)?.into(); + state.allocs.insert(ptr, (amt, trace.clone())); + state.total += amt; + if state.total > state.max_total { + state.max_total = state.total; + } + handle_alloc(state, &mut handle_state, now, ptr, amt, trace)?; + } else if frame_id == 3 { + let ptr = read_u64(&mut state.inf)?; + let _ = read_u64_vec(&mut state.inf)?; + let amt_trace = state + .allocs + .get(&ptr) + .expect("free of un-allocated address"); + let amt = amt_trace.0; + let trace = amt_trace.1.clone(); + state.total -= amt; + handle_free(state, &mut handle_state, now, ptr, amt, trace)?; + } else { + return None; + } + } + Some(()) +} + +fn mkv_for(out_dir: &PathBuf, vis: Visualisation, start: Duration) -> PathBuf { + out_dir.join(format!("{:08}.{}.mkv", start.as_micros(), vis)) +} +fn ffmpeg_for( + out_dir: &PathBuf, + vis: Visualisation, + start: Duration, +) -> Option { + let out = std::fs::File::create(out_dir.join(format!("{:08}.{}.out", start.as_micros(), vis))) + .ok()?; + let err = std::fs::File::create(out_dir.join(format!("{:08}.{}.err", start.as_micros(), vis))) + .ok()?; + let mkv = mkv_for(out_dir, vis, start); + let _ = std::fs::remove_file(&mkv); + std::process::Command::new("ffmpeg") + .args([ + "-f", + "rawvideo", + "-pix_fmt", + "rgba", + "-framerate", + "60", + "-video_size", + "1920x1080", + "-i", + "-", + "-c:v", + "libvpx-vp9", + "-crf", + "15", + "-b:v", + "0", + ]) + .arg(mkv) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::from(out)) + .stderr(std::process::Stdio::from(err)) + .spawn() + .ok() +} + +fn spawn_render_thread( + state: &mut State, + exe_file_name: String, + in_file_name: String, + interval: (Duration, Duration), +) -> std::thread::JoinHandle> { + let out_dir = state.out_dir.clone(); + let max_total = state.max_total; + let max_duration = state.max_duration; + std::thread::spawn(move || { + let out_dir = out_dir?; + eprintln!( + "> {:08} -- {:08}", + interval.0.as_micros(), + interval.1.as_micros() + ); + let loader = addr2line::Loader::new(exe_file_name).ok()?; + let inf = File::open(in_file_name).expect("could not open dump file"); + let mut bar_ffmpeg = ffmpeg_for(&out_dir, Visualisation::Bar, interval.0)?; + let mut flame_ffmpeg = ffmpeg_for(&out_dir, Visualisation::Flame, interval.0)?; + let mut job_state = State { + inf: inf, + symbol_cache: SymbolCache { + loader, + symbol_cache: HashMap::new(), + }, + start_time: Some(interval.0), + end_time: Some(interval.1), + out_dir: Some(out_dir), + allocs: HashMap::new(), + sites: HashMap::new(), + traces: TraceTrie::new(), + total: 0, + max_total, + max_duration, + num_durations: 0, + }; + /* plot each individual frame */ + let mut device = piet_common::Device::new().expect("could not create Piet device"); + let rs = RenderState { + device: &mut device, + bar_out: bar_ffmpeg.stdin.take().expect("bar ffmpeg stdin"), + flame_out: flame_ffmpeg.stdin.take().expect("flame ffmpeg stdin"), + }; + read_file( + &mut job_state, + rs, + render_ident, + render_unwind, + render_alloc, + render_free, + )?; + bar_ffmpeg.wait().ok()?; + flame_ffmpeg.wait().ok()?; + Some(()) + }) +} + +fn main() { + let args: Vec = env::args().collect(); + let is_list = args.len() == 4 && args[3] == "list_frames"; + let is_plot = args.len() == 6 && args[3] == "plot_mem"; + if !is_list && !is_plot { + eprintln!("usage: {} list_frames", args[0]); + eprintln!( + "usage: {} plot_mem ", + args[0] + ); + return; + } + let Ok(loader) = addr2line::Loader::new(&args[1]) else { + eprintln!("could not load guest binary {}", args[1]); + return; + }; + let inf = File::open(args[2].clone()).expect("could not open trace file"); + let state = State { + inf: inf, + symbol_cache: SymbolCache { + loader: loader, + symbol_cache: HashMap::new(), + }, + start_time: None, + end_time: None, + out_dir: None, + allocs: HashMap::new(), + sites: HashMap::new(), + traces: TraceTrie::new(), + total: 0, + max_total: 0, + max_duration: Duration::ZERO, + num_durations: 0, + }; + if is_list { + dump_trace(state); + } else if is_plot { + plot_mem(args, state); + } +} + +fn dump_trace(mut state: State) { + read_file( + &mut state, + (), + dump_ident, + dump_unwind, + dump_alloc, + dump_free, + ); +} + +fn plot_mem(args: Vec, mut state: State) { + let out_dir = PathBuf::from(args[4].clone()); + state.out_dir = Some(out_dir.clone()); + std::fs::create_dir_all(&out_dir).expect("could not create output dir"); + + /* first pass: compute the maximum memory usage */ + match read_file( + &mut state, + (), + |_, _, _, _| Some(()), + |_, _, _, _| Some(()), + |_, _, _, _, _, _| Some(()), + |_, _, _, _, _, _| Some(()), + ) { + Some(()) => (), + None => { + eprintln!("i/o error encountered"); + () + } + } + eprintln!("max total memory used is {}", state.max_total); + state + .inf + .seek(SeekFrom::Start(0)) + .expect("couldn't seek back"); + state.allocs = HashMap::new(); + state.total = 0; + + /* second pass: compute fair durations so that each parallel job + * processes the same number of frames */ + let num_segments = str::parse::(&args[5]).expect("number of segments must be a number"); + let durations_per_segment = (state.num_durations - 1) / num_segments + 1; + state.num_durations = 0; + let jobs = Mutex::new(Vec::new()); + let start_duration = Mutex::new(Duration::ZERO); + let count_frame = |s: &mut State, _: &mut (), n: Duration, _, _, _| { + if s.num_durations == 1 { + *start_duration.lock().unwrap() = n; + } + if s.num_durations == durations_per_segment { + (*jobs.lock().unwrap()).push((*start_duration.lock().unwrap(), n)); + s.num_durations = 0; + } + Some(()) + }; + read_file( + &mut state, + (), + |_, _, _, _| Some(()), + |_, _, _, _| Some(()), + count_frame, + count_frame, + ); + if state.num_durations > 0 { + (*jobs.lock().unwrap()).push((*start_duration.lock().unwrap(), state.max_duration)); + } + + /* third pass: render in parallel */ + let mut handles = Vec::new(); + for job in &*jobs.lock().unwrap() { + handles.push(spawn_render_thread( + &mut state, + args[1].clone(), + args[2].clone(), + *job, + )); + } + for handle in handles { + handle.join().expect("thread died"); + } + + /* merge all the parallel rendered segments */ + let mut merge_bar = std::process::Command::new("mkvmerge"); + merge_bar.arg("-o").arg(out_dir.join("bar.mkv")); + let mut merge_flame = std::process::Command::new("mkvmerge"); + merge_flame.arg("-o").arg(out_dir.join("flame.mkv")); + for (n, job) in (*jobs.lock().unwrap()).iter().enumerate() { + if n > 0 { + merge_bar.arg("+"); + merge_flame.arg("+"); + } + merge_bar.arg(mkv_for(&out_dir, Visualisation::Bar, job.0)); + merge_flame.arg(mkv_for(&out_dir, Visualisation::Flame, job.0)); + } + merge_bar.status().unwrap(); + merge_flame.status().unwrap(); +} From 5d74e1950a2bab6fedbd7562f046ef16ec26bfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Mon, 7 Jul 2025 19:43:03 +0300 Subject: [PATCH 09/21] add license header to trace_dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- src/trace_dump/main.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/trace_dump/main.rs b/src/trace_dump/main.rs index c187efac1..afd3f8b81 100644 --- a/src/trace_dump/main.rs +++ b/src/trace_dump/main.rs @@ -1,3 +1,19 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + use std::collections::HashMap; use std::env; use std::fs::File; From 946c5656fae2c87a7c42d2d12f360874f2776dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 15:08:16 +0300 Subject: [PATCH 10/21] [hyperlight-guest-tracing] Add crates that generate guest tracing records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `hyperlight-guest-tracing` defines a `TraceBuffer` that keeps `TraceRecord`s that the guest issues. When the buffer capacity is reached, it automatically issues an `Out` instruction with the corresponding info for the host to retrieve the buffer. - The guest can issue `TraceRecord`s by using the `hyperlight-guest-tracing-macro` crate that pushes new records to the buffer. - `hyperlight_common` contains the definitions for the frame types a guest can send to the host using the `Out` instruction. Signed-off-by: Doru Blânzeanu --- Cargo.lock | 22 +++ Cargo.toml | 4 + src/hyperlight_common/Cargo.toml | 1 + src/hyperlight_common/src/outb.rs | 9 +- src/hyperlight_guest_tracing/Cargo.toml | 17 ++ src/hyperlight_guest_tracing/src/lib.rs | 161 ++++++++++++++++++ src/hyperlight_guest_tracing_macro/Cargo.toml | 24 +++ src/hyperlight_guest_tracing_macro/src/lib.rs | 150 ++++++++++++++++ 8 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 src/hyperlight_guest_tracing/Cargo.toml create mode 100644 src/hyperlight_guest_tracing/src/lib.rs create mode 100644 src/hyperlight_guest_tracing_macro/Cargo.toml create mode 100644 src/hyperlight_guest_tracing_macro/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 709611f13..83a8eb710 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1367,6 +1367,8 @@ version = "0.7.0" dependencies = [ "anyhow", "hyperlight-common", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "serde_json", ] @@ -1380,10 +1382,29 @@ dependencies = [ "glob", "hyperlight-common", "hyperlight-guest", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "log", "spin 0.10.0", ] +[[package]] +name = "hyperlight-guest-tracing" +version = "0.7.0" +dependencies = [ + "hyperlight-common", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing-macro" +version = "0.7.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "hyperlight-host" version = "0.7.0" @@ -1409,6 +1430,7 @@ dependencies = [ "goblin", "hyperlight-common", "hyperlight-component-macro", + "hyperlight-guest-tracing", "hyperlight-testing", "kvm-bindings", "kvm-ioctls", diff --git a/Cargo.toml b/Cargo.toml index 28ca73d70..18a311405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ members = [ "src/hyperlight_guest", "src/hyperlight_host", "src/hyperlight_guest_capi", + "src/hyperlight_guest_tracing", + "src/hyperlight_guest_tracing_macro", "src/hyperlight_testing", "fuzz", "src/hyperlight_guest_bin", @@ -40,6 +42,8 @@ hyperlight-host = { path = "src/hyperlight_host", version = "0.7.0", default-fea hyperlight-guest = { path = "src/hyperlight_guest", version = "0.7.0", default-features = false } hyperlight-guest-bin = { path = "src/hyperlight_guest_bin", version = "0.7.0", default-features = false } hyperlight-testing = { path = "src/hyperlight_testing", default-features = false } +hyperlight-guest-tracing = { path = "src/hyperlight_guest_tracing", default-features = false } +hyperlight-guest-tracing-macro = { path = "src/hyperlight_guest_tracing_macro", default-features = false } hyperlight-component-util = { path = "src/hyperlight_component_util", version = "0.7.0", default-features = false } hyperlight-component-macro = { path = "src/hyperlight_component_macro", version = "0.7.0", default-features = false } diff --git a/src/hyperlight_common/Cargo.toml b/src/hyperlight_common/Cargo.toml index cf317bd9d..ac83d6322 100644 --- a/src/hyperlight_common/Cargo.toml +++ b/src/hyperlight_common/Cargo.toml @@ -25,6 +25,7 @@ spin = "0.10.0" [features] default = ["tracing"] fuzzing = ["dep:arbitrary"] +trace_guest = [] unwind_guest = [] mem_profile = [] std = [] diff --git a/src/hyperlight_common/src/outb.rs b/src/hyperlight_common/src/outb.rs index 43f07a9cf..54bba6f74 100644 --- a/src/hyperlight_common/src/outb.rs +++ b/src/hyperlight_common/src/outb.rs @@ -93,6 +93,7 @@ impl TryFrom for Exception { /// - TraceRecordStack: records the stack trace of the guest /// - TraceMemoryAlloc: records memory allocation events /// - TraceMemoryFree: records memory deallocation events +/// - TraceRecord: records a trace event in the guest pub enum OutBAction { Log = 99, CallFunction = 101, @@ -101,9 +102,11 @@ pub enum OutBAction { #[cfg(feature = "unwind_guest")] TraceRecordStack = 104, #[cfg(feature = "mem_profile")] - TraceMemoryAlloc, + TraceMemoryAlloc = 105, #[cfg(feature = "mem_profile")] - TraceMemoryFree, + TraceMemoryFree = 106, + #[cfg(feature = "trace_guest")] + TraceRecord = 107, } impl TryFrom for OutBAction { @@ -120,6 +123,8 @@ impl TryFrom for OutBAction { 105 => Ok(OutBAction::TraceMemoryAlloc), #[cfg(feature = "mem_profile")] 106 => Ok(OutBAction::TraceMemoryFree), + #[cfg(feature = "trace_guest")] + 107 => Ok(OutBAction::TraceRecord), _ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)), } } diff --git a/src/hyperlight_guest_tracing/Cargo.toml b/src/hyperlight_guest_tracing/Cargo.toml new file mode 100644 index 000000000..e77144eaa --- /dev/null +++ b/src/hyperlight_guest_tracing/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hyperlight-guest-tracing" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +description = """Provides the tracing functionality for the hyperlight guest.""" + +[dependencies] +hyperlight-common = { workspace = true, default-features = false, features = ["trace_guest"] } +spin = "0.10.0" + +[lints] +workspace = true diff --git a/src/hyperlight_guest_tracing/src/lib.rs b/src/hyperlight_guest_tracing/src/lib.rs new file mode 100644 index 000000000..5264f2efa --- /dev/null +++ b/src/hyperlight_guest_tracing/src/lib.rs @@ -0,0 +1,161 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#![no_std] + +// === Dependencies === +extern crate alloc; + +use core::mem::MaybeUninit; + +use hyperlight_common::outb::OutBAction; +use spin::Mutex; + +/// Global trace buffer for storing trace records. +static TRACE_BUFFER: Mutex = Mutex::new(TraceBuffer::new()); + +/// Maximum number of entries in the trace buffer. +const MAX_NO_OF_ENTRIES: usize = 64; + +/// Maximum length of a trace message in bytes. +pub const MAX_TRACE_MSG_LEN: usize = 64; + +#[derive(Debug, Copy, Clone)] +/// Represents a trace record of a guest with a number of cycles and a message. +pub struct TraceRecord { + /// The number of CPU cycles returned by the invariant TSC. + pub cycles: u64, + /// The length of the message in bytes. + pub msg_len: usize, + /// The message associated with the trace record. + pub msg: [u8; MAX_TRACE_MSG_LEN], +} + +/// A buffer for storing trace records. +struct TraceBuffer { + /// The entries in the trace buffer. + entries: [TraceRecord; MAX_NO_OF_ENTRIES], + /// The index where the next entry will be written. + write_index: usize, +} + +impl TraceBuffer { + /// Creates a new `TraceBuffer` with uninitialized entries. + const fn new() -> Self { + Self { + entries: unsafe { [MaybeUninit::zeroed().assume_init(); MAX_NO_OF_ENTRIES] }, + write_index: 0, + } + } + + /// Push a new trace record into the buffer. + /// If the buffer is full, it sends the records to the host. + fn push(&mut self, entry: TraceRecord) { + let mut write_index = self.write_index; + + self.entries[write_index] = entry; + write_index = (write_index + 1) % MAX_NO_OF_ENTRIES; + + self.write_index = write_index; + + if write_index == 0 { + // If buffer is full send to host + self.send_to_host(MAX_NO_OF_ENTRIES); + } + } + + /// Flush the trace buffer, sending any remaining records to the host. + fn flush(&mut self) { + if self.write_index > 0 { + self.send_to_host(self.write_index); + self.write_index = 0; // Reset write index after flushing + } + } + + /// Send the trace records to the host. + fn send_to_host(&self, count: usize) { + unsafe { + core::arch::asm!("out dx, al", + in("dx") OutBAction::TraceRecord as u16, + in("rax") count as u64, + in("rcx") &self.entries as * const _ as u64); + } + } +} + +/// Module for checking invariant TSC support and reading the timestamp counter +pub mod invariant_tsc { + use core::arch::x86_64::{__cpuid, _rdtsc}; + + /// Check if the processor supports invariant TSC + /// + /// Returns true if CPUID.80000007H:EDX[8] is set, indicating invariant TSC support + pub fn has_invariant_tsc() -> bool { + // Check if extended CPUID functions are available + let max_extended = unsafe { __cpuid(0x80000000) }; + if max_extended.eax < 0x80000007 { + return false; + } + + // Query CPUID.80000007H for invariant TSC support + let cpuid_result = unsafe { __cpuid(0x80000007) }; + + // Check bit 8 of EDX register for invariant TSC support + (cpuid_result.edx & (1 << 8)) != 0 + } + + /// Read the timestamp counter + /// + /// This function provides a high-performance timestamp by reading the TSC. + /// Should only be used when invariant TSC is supported for reliable timing. + /// + /// # Safety + /// This function uses unsafe assembly instructions but is safe to call. + /// However, the resulting timestamp is only meaningful if invariant TSC is supported. + pub fn read_tsc() -> u64 { + unsafe { _rdtsc() } + } +} + +/// Create a trace record with the given message. +/// +/// Note: The message must not exceed `MAX_TRACE_MSG_LEN` bytes. +/// If the message is too long, it will be skipped. +pub fn create_trace_record(msg: &str) { + if msg.len() > MAX_TRACE_MSG_LEN { + return; // Message too long, skip tracing + } + + let cycles = invariant_tsc::read_tsc(); + + let entry = TraceRecord { + cycles, + msg: { + let mut arr = [0u8; MAX_TRACE_MSG_LEN]; + arr[..msg.len()].copy_from_slice(msg.as_bytes()); + arr + }, + msg_len: msg.len(), + }; + + let mut buffer = TRACE_BUFFER.lock(); + buffer.push(entry); +} + +/// Flush the trace buffer to send any remaining trace records to the host. +pub fn flush_trace_buffer() { + let mut buffer = TRACE_BUFFER.lock(); + buffer.flush(); +} diff --git a/src/hyperlight_guest_tracing_macro/Cargo.toml b/src/hyperlight_guest_tracing_macro/Cargo.toml new file mode 100644 index 000000000..6e749ac20 --- /dev/null +++ b/src/hyperlight_guest_tracing_macro/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "hyperlight-guest-tracing-macro" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +description = """Provides the tracing macros for the hyperlight guest, enabling structured logging and tracing capabilities.""" + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0.40" +syn = { version = "2.0.104", features = ["full"] } + +[features] +default = [] + +[lib] +proc-macro = true + +[lints] +workspace = true diff --git a/src/hyperlight_guest_tracing_macro/src/lib.rs b/src/hyperlight_guest_tracing_macro/src/lib.rs new file mode 100644 index 000000000..c2e3e4bc2 --- /dev/null +++ b/src/hyperlight_guest_tracing_macro/src/lib.rs @@ -0,0 +1,150 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{ItemFn, Lit, parse_macro_input}; + +/// A procedural macro attribute for tracing function calls. +/// Usage: +/// ```rust +/// #[trace_function] +/// fn my_function() { +/// // // Function body +/// } +/// ``` +/// +/// This macro will create a trace record when the function is called, if the `trace_guest` +/// feature is enabled. +/// +/// The trace record will contain the function name as a string. +/// Note: This macro is intended to be used with the `hyperlight_guest_tracing` crate. +#[proc_macro_attribute] +pub fn trace_function(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(item as ItemFn); + + let fn_name = &input_fn.sig.ident; + let fn_name_str = fn_name.to_string(); + let fn_vis = &input_fn.vis; + let fn_sig = &input_fn.sig; + let fn_block = &input_fn.block; + let fn_attrs = &input_fn.attrs; + let fn_output = &input_fn.sig.output; + + let expanded = match fn_output { + syn::ReturnType::Default => { + // No return value (unit) + quote! { + #(#fn_attrs)* + #fn_vis #fn_sig { + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(concat!("> ", #fn_name_str)); + // Call the original function body + #fn_block + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(concat!("< ", #fn_name_str)); + } + } + } + syn::ReturnType::Type(_, _) => { + // Has a return value + quote! { + #(#fn_attrs)* + #fn_vis #fn_sig { + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(concat!("> ", #fn_name_str)); + let __trace_result = (|| #fn_block )(); + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(concat!("< ", #fn_name_str)); + __trace_result + } + } + } + }; + + TokenStream::from(expanded) +} + +/// Input structure for the trace macro +struct TraceInput { + message: Lit, +} + +impl Parse for TraceInput { + fn parse(input: ParseStream) -> syn::Result { + Ok(TraceInput { + message: input.parse()?, + }) + } +} + +/// This macro creates a trace record with a message. +/// +/// Usage: +/// ```rust +/// trace!("message"); +/// ``` +#[proc_macro] +pub fn trace(input: TokenStream) -> TokenStream { + // Convert to proc_macro2::TokenStream for parsing + let input2: proc_macro2::TokenStream = input.clone().into(); + + // Try to parse as message + if let Ok(parsed) = syn::parse2::(input2) { + let trace_message = match parsed.message { + Lit::Str(lit_str) => lit_str.value(), + _ => "expression".to_string(), + }; + + let expanded = quote! { + { + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(#trace_message); + } + }; + + return TokenStream::from(expanded); + } + + // Fallback: treat the entire input as an expression with default message + let expanded = quote! { + { + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record("expression"); + } + }; + + TokenStream::from(expanded) +} + +/// This macro flushes the trace buffer, sending any remaining trace records to the host. +/// +/// Usage: +/// ```rust +/// flush!(); +/// ``` +#[proc_macro] +pub fn flush(_input: TokenStream) -> TokenStream { + let expanded = quote! { + { + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::flush_trace_buffer(); + } + }; + + TokenStream::from(expanded) +} From d3c416c2a70dd10a875cc0e25ae614e631cc06e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 15:19:54 +0300 Subject: [PATCH 11/21] [hyperlight_host/trace] Handle the trace records sent by a guest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - When an `Out` instruction is intercepted, the Hypervisor checks for the frame Id, to verify what type of exit it is. Based on this, when a trace record type is received, we copy the array of trace records from the guest's memory, calculate the timestamp based on the cycles returned by the guest's RDTSC and write the record to the trace file. Signed-off-by: Doru Blânzeanu --- src/hyperlight_host/Cargo.toml | 3 +- .../src/hypervisor/hyperv_linux.rs | 18 +++- .../src/hypervisor/hyperv_windows.rs | 14 ++- src/hyperlight_host/src/hypervisor/kvm.rs | 15 ++- src/hyperlight_host/src/hypervisor/mod.rs | 5 +- src/hyperlight_host/src/sandbox/mod.rs | 34 ++++++ src/hyperlight_host/src/sandbox/outb.rs | 101 +++++++++++++++++- 7 files changed, 178 insertions(+), 12 deletions(-) diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 76a152b38..23753330c 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -39,6 +39,7 @@ tracing = { version = "0.1.41", features = ["log"] } tracing-log = "0.2.0" tracing-core = "0.1.34" hyperlight-common = { workspace = true, default-features = true, features = [ "std" ] } +hyperlight-guest-tracing = { workspace = true, default-features = true, optional = true } vmm-sys-util = "0.14.0" crossbeam-channel = "0.5.15" thiserror = "2.0.12" @@ -131,7 +132,7 @@ executable_heap = [] print_debug = [] # Dumps the VM state to a file on unexpected errors or crashes. The path of the file will be printed on stdout and logged. crashdump = ["dep:chrono"] -trace_guest = [] +trace_guest = ["hyperlight-common/trace_guest", "dep:hyperlight-guest-tracing"] # This feature enables unwinding the guest stack from the host, in # order to produce stack traces for debugging or profiling. unwind_guest = [ "trace_guest", "dep:framehop", "dep:fallible-iterator", "hyperlight-common/unwind_guest" ] diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index ef56a7044..2c7eb7a8e 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -48,7 +48,7 @@ use mshv_bindings::{ hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, hv_partition_synthetic_processor_features, }; -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] use mshv_bindings::{ hv_register_name, hv_register_name_HV_X64_REGISTER_RAX, hv_register_name_HV_X64_REGISTER_RBP, hv_register_name_HV_X64_REGISTER_RCX, hv_register_name_HV_X64_REGISTER_RSP, @@ -58,7 +58,7 @@ use tracing::{Span, instrument}; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] use super::TraceRegister; use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; #[cfg(gdb)] @@ -554,7 +554,7 @@ impl Debug for HypervLinuxDriver { } } -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] impl From for hv_register_name { fn from(r: TraceRegister) -> Self { match r { @@ -761,6 +761,12 @@ impl Hypervisor for HypervLinuxDriver { libc::EINTR, ))) } else { + #[cfg(feature = "trace_guest")] + if self.trace_info.guest_start_epoch.is_none() { + // Set the guest start epoch to the current time, before running the vcpu + crate::debug!("HyperV - Guest Start Epoch set"); + self.trace_info.guest_start_epoch = Some(std::time::Instant::now()); + } // Note: if a `InterruptHandle::kill()` called while this thread is **here** // Then the vcpu will run, but we will keep sending signals to this thread // to interrupt it until `running` is set to false. The `vcpu_fd::run()` call will @@ -1114,7 +1120,7 @@ impl Hypervisor for HypervLinuxDriver { Ok(()) } - #[cfg(feature = "unwind_guest")] + #[cfg(feature = "trace_guest")] fn read_trace_reg(&self, reg: TraceRegister) -> Result { let mut assoc = [hv_register_assoc { name: reg.into(), @@ -1129,6 +1135,10 @@ impl Hypervisor for HypervLinuxDriver { fn trace_info_as_ref(&self) -> &TraceInfo { &self.trace_info } + #[cfg(feature = "trace_guest")] + fn trace_info_as_mut(&mut self) -> &mut TraceInfo { + &mut self.trace_info + } } impl Drop for HypervLinuxDriver { diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 55b01e8bb..08350d40c 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -44,7 +44,7 @@ use { std::sync::Mutex, }; -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] use super::TraceRegister; use super::fpu::{FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper}; @@ -732,6 +732,12 @@ impl Hypervisor for HypervWindowsDriver { Reserved: Default::default(), } } else { + #[cfg(feature = "trace_guest")] + if self.trace_info.guest_start_epoch.is_none() { + // Set the guest start epoch to the current time, before running the vcpu + crate::debug!("MSHV - Guest Start Epoch set"); + self.trace_info.guest_start_epoch = Some(std::time::Instant::now()); + } self.processor.run()? }; self.interrupt_handle @@ -1046,7 +1052,7 @@ impl Hypervisor for HypervWindowsDriver { Ok(()) } - #[cfg(feature = "unwind_guest")] + #[cfg(feature = "trace_guest")] fn read_trace_reg(&self, reg: TraceRegister) -> Result { let regs = self.processor.get_regs()?; match reg { @@ -1062,6 +1068,10 @@ impl Hypervisor for HypervWindowsDriver { fn trace_info_as_ref(&self) -> &TraceInfo { &self.trace_info } + #[cfg(feature = "trace_guest")] + fn trace_info_as_mut(&mut self) -> &mut TraceInfo { + &mut self.trace_info + } } impl Drop for HypervWindowsDriver { diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index b63c44eb4..0b668c305 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -29,7 +29,7 @@ use tracing::{Span, instrument}; #[cfg(crashdump)] use {super::crashdump, std::path::Path}; -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] use super::TraceRegister; use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT}; #[cfg(gdb)] @@ -643,6 +643,13 @@ impl Hypervisor for KVMDriver { { Err(kvm_ioctls::Error::new(libc::EINTR)) } else { + #[cfg(feature = "trace_guest")] + if self.trace_info.guest_start_epoch.is_none() { + // Set the guest start epoch to the current time, before running the vcpu + crate::debug!("KVM - Guest Start Epoch set"); + self.trace_info.guest_start_epoch = Some(std::time::Instant::now()); + } + // Note: if a `InterruptHandle::kill()` called while this thread is **here** // Then the vcpu will run, but we will keep sending signals to this thread // to interrupt it until `running` is set to false. The `vcpu_fd::run()` call will @@ -965,7 +972,7 @@ impl Hypervisor for KVMDriver { Ok(()) } - #[cfg(feature = "unwind_guest")] + #[cfg(feature = "trace_guest")] fn read_trace_reg(&self, reg: TraceRegister) -> Result { let regs = self.vcpu_fd.get_regs()?; Ok(match reg { @@ -981,6 +988,10 @@ impl Hypervisor for KVMDriver { fn trace_info_as_ref(&self) -> &TraceInfo { &self.trace_info } + #[cfg(feature = "trace_guest")] + fn trace_info_as_mut(&mut self) -> &mut TraceInfo { + &mut self.trace_info + } } impl Drop for KVMDriver { diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index 2be3fdf28..a5332d9be 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -270,12 +270,15 @@ pub(crate) trait Hypervisor: Debug + Sync + Send { } /// Read a register for trace/unwind purposes - #[cfg(feature = "unwind_guest")] + #[cfg(feature = "trace_guest")] fn read_trace_reg(&self, reg: TraceRegister) -> Result; /// Get a reference of the trace info for the guest #[cfg(feature = "trace_guest")] fn trace_info_as_ref(&self) -> &TraceInfo; + /// Get a mutable reference of the trace info for the guest + #[cfg(feature = "trace_guest")] + fn trace_info_as_mut(&mut self) -> &mut TraceInfo; } /// A virtual CPU that can be run until an exit occurs diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index 5a2f7be71..7c3c5b156 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -105,6 +105,17 @@ pub(crate) struct TraceInfo { /// early as the creation of the sandbox being traced. #[allow(dead_code)] pub epoch: std::time::Instant, + /// The frequency of the timestamp counter. + #[allow(dead_code)] + pub tsc_freq: u64, + /// The epoch at which the guest started, if it has started. + /// This is used to calculate the time spent in the guest relative to the + /// time of the host. + #[allow(dead_code)] + pub guest_start_epoch: Option, + /// The start guest time, in TSC cycles, for the current guest. + #[allow(dead_code)] + pub guest_start_tsc: Option, /// The file to which the trace is being written #[allow(dead_code)] pub file: Arc>, @@ -139,8 +150,13 @@ impl TraceInfo { let cache = framehop::x86_64::CacheX86_64::new(); (unwinder, Arc::new(Mutex::new(cache))) }; + let tsc_freq = Self::calculate_tsc_freq()?; + let ret = Self { epoch: std::time::Instant::now(), + tsc_freq, + guest_start_epoch: None, + guest_start_tsc: None, file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)), #[cfg(feature = "unwind_guest")] unwind_module, @@ -156,6 +172,24 @@ impl TraceInfo { })?; Ok(ret) } + + /// Calculate the TSC frequency based on the RDTSC instruction. + fn calculate_tsc_freq() -> crate::Result { + if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() { + return Err(crate::new_error!( + "Invariant TSC is not supported on this platform" + )); + } + let start = hyperlight_guest_tracing::invariant_tsc::read_tsc(); + let start_time = std::time::Instant::now(); + // Sleep for 1 second to get a good sample + std::thread::sleep(std::time::Duration::from_secs(1)); + let end = hyperlight_guest_tracing::invariant_tsc::read_tsc(); + let end_time = std::time::Instant::now(); + let elapsed = end_time.duration_since(start_time).as_secs_f64(); + + Ok(((end - start) as f64 / elapsed) as u64) + } } pub(crate) trait WrapperGetter { diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 5a559321a..524c9d811 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] use std::io::Write; use std::sync::{Arc, Mutex}; @@ -26,6 +26,8 @@ use hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; use hyperlight_common::outb::{Exception, OutBAction}; +#[cfg(feature = "trace_guest")] +use hyperlight_guest_tracing::TraceRecord; use log::{Level, Record}; use tracing::{Span, instrument}; use tracing_log::format_trace; @@ -33,7 +35,7 @@ use tracing_log::format_trace; use super::host_funcs::FunctionRegistry; use super::mem_mgr::MemMgrWrapper; use crate::hypervisor::handlers::{OutBHandler, OutBHandlerFunction, OutBHandlerWrapper}; -#[cfg(feature = "unwind_guest")] +#[cfg(feature = "trace_guest")] use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; @@ -211,6 +213,53 @@ pub(super) fn record_trace_frame( Ok(()) } +#[cfg(feature = "trace_guest")] +pub(super) fn record_guest_trace_frame( + trace_info: &TraceInfo, + frame_id: u64, + cycles: u64, + write_frame: F, +) -> Result<()> { + let Ok(mut out) = trace_info.file.lock() else { + return Ok(()); + }; + // frame structure: + // 16 bytes timestamp + + // The number of cycles spent in the guest relative to the first received trace record + let cycles_spent = cycles + - trace_info + .guest_start_tsc + .as_ref() + .map_or_else(|| 0, |c| *c); + // Convert cycles to microseconds based on the TSC frequency + let micros = cycles_spent as f64 / trace_info.tsc_freq as f64 * 1_000_000f64; + + // Convert to a Duration + let guest_duration = std::time::Duration::from_micros(micros as u64); + + // Calculate the time when the guest started execution relative to the host epoch + // Note: This is relative to the time saved when the `TraceInfo` was created (before the + // Hypervisor is created). + let guest_start_time = trace_info + .guest_start_epoch + .as_ref() + .unwrap_or(&trace_info.epoch) + .saturating_duration_since(trace_info.epoch); + + // Calculate the timestamp when the actual frame was recorded relative to the host epoch + let timestamp = guest_start_time + .checked_add(guest_duration) + .unwrap_or(guest_duration); + + let _ = out.write_all(×tamp.as_micros().to_ne_bytes()); + // 8 bytes frame type id + let _ = out.write_all(&frame_id.to_ne_bytes()); + // frame data + write_frame(&mut out); + Ok(()) +} + /// Handles OutB operations from the guest. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] fn handle_outb_impl( @@ -287,6 +336,54 @@ fn handle_outb_impl( write_stack(f, &stack); }) } + #[cfg(feature = "trace_guest")] + OutBAction::TraceRecord => { + let Ok(len) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RAX) else { + return Ok(()); + }; + let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else { + return Ok(()); + }; + let mut buffer = vec![0u8; len as usize * std::mem::size_of::()]; + let buffer = &mut buffer[..]; + + // Read the trace records from the guest memory + // TODO: maybe this can be done without copying? + mem_mgr + .as_ref() + .shared_mem + .copy_to_slice(buffer, ptr as usize - SandboxMemoryLayout::BASE_ADDRESS) + // .read::((addr - SandboxMemoryLayout::BASE_ADDRESS as u64) as usize) + .map_err(|e| { + new_error!( + "Failed to copy trace records from guest memory to host: {:?}", + e + ) + })?; + + let traces = unsafe { + std::slice::from_raw_parts(buffer.as_ptr() as *const TraceRecord, len as usize) + }; + + { + let trace_info = _hv.trace_info_as_mut(); + // Store the start guest cycles if not already set + // This is the `entrypoint` of the guest execution + // This should be set only once, at the start of the guest execution + if trace_info.guest_start_tsc.is_none() && !traces.is_empty() { + trace_info.guest_start_tsc = Some(traces[0].cycles); + } + } + + for record in traces { + record_guest_trace_frame(_hv.trace_info_as_ref(), 4u64, record.cycles, |f| { + let _ = f.write_all(&record.msg_len.to_ne_bytes()); + let _ = f.write_all(&record.msg[..record.msg_len]); + })? + } + + Ok(()) + } } } From c8b0fd1f61c1b285bec66335805e62089ac88871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 15:24:58 +0300 Subject: [PATCH 12/21] [hyperlight_guest] Add traces in the guest to track the execution timing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add traces wherever we think it might be of use to help us better profile the codebase. - Add flush instructions before halting or running an `OutB` instruction to ensure the records are sent before the guest exits due to an error or a normal exit Signed-off-by: Doru Blânzeanu --- src/hyperlight_guest/Cargo.toml | 6 ++++++ src/hyperlight_guest/src/exit.rs | 10 ++++++++++ src/hyperlight_guest/src/guest_handle/host_comm.rs | 7 +++++++ src/hyperlight_guest/src/guest_handle/io.rs | 6 ++++++ src/hyperlight_guest_bin/Cargo.toml | 3 +++ src/hyperlight_guest_bin/src/exceptions/gdt.rs | 1 + src/hyperlight_guest_bin/src/exceptions/handler.rs | 1 + src/hyperlight_guest_bin/src/exceptions/idt.rs | 1 + src/hyperlight_guest_bin/src/exceptions/idtr.rs | 1 + src/hyperlight_guest_bin/src/guest_function/call.rs | 5 +++++ src/hyperlight_guest_bin/src/lib.rs | 3 +++ 11 files changed, 44 insertions(+) diff --git a/src/hyperlight_guest/Cargo.toml b/src/hyperlight_guest/Cargo.toml index 8fce35293..d55aa4f92 100644 --- a/src/hyperlight_guest/Cargo.toml +++ b/src/hyperlight_guest/Cargo.toml @@ -15,3 +15,9 @@ Provides only the essential building blocks for interacting with the host enviro anyhow = { version = "1.0.98", default-features = false } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } hyperlight-common = { workspace = true } +hyperlight-guest-tracing = { workspace = true, optional = true } +hyperlight-guest-tracing-macro = { workspace = true} + +[features] +default = [] +trace_guest = ["dep:hyperlight-guest-tracing"] diff --git a/src/hyperlight_guest/src/exit.rs b/src/hyperlight_guest/src/exit.rs index 82d8a9545..42a9cc0fd 100644 --- a/src/hyperlight_guest/src/exit.rs +++ b/src/hyperlight_guest/src/exit.rs @@ -21,17 +21,22 @@ use hyperlight_common::outb::OutBAction; /// Halt the execution of the guest and returns control to the host. #[inline(never)] +#[hyperlight_guest_tracing_macro::trace_function] pub fn halt() { + // Ensure all tracing data is flushed before halting + hyperlight_guest_tracing_macro::flush!(); unsafe { asm!("hlt", options(nostack)) } } /// Exits the VM with an Abort OUT action and code 0. #[unsafe(no_mangle)] +#[hyperlight_guest_tracing_macro::trace_function] pub extern "C" fn abort() -> ! { abort_with_code(&[0, 0xFF]) } /// Exits the VM with an Abort OUT action and a specific code. +#[hyperlight_guest_tracing_macro::trace_function] 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) @@ -42,6 +47,7 @@ pub fn abort_with_code(code: &[u8]) -> ! { /// /// # Safety /// This function is unsafe because it dereferences a raw pointer. +#[hyperlight_guest_tracing_macro::trace_function] pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_char) -> ! { unsafe { // Step 1: Send abort code (typically 1 byte, but `code` allows flexibility) @@ -62,7 +68,10 @@ pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_cha } /// OUT bytes to the host through multiple exits. +#[hyperlight_guest_tracing_macro::trace_function] pub(crate) fn outb(port: u16, data: &[u8]) { + // Ensure all tracing data is flushed before sending OUT bytes + hyperlight_guest_tracing_macro::flush!(); unsafe { let mut i = 0; while i < data.len() { @@ -79,6 +88,7 @@ pub(crate) fn outb(port: u16, data: &[u8]) { } /// OUT function for sending a 32-bit value to the host. +#[hyperlight_guest_tracing_macro::trace_function] pub(crate) unsafe fn out32(port: u16, val: u32) { unsafe { asm!("out dx, eax", in("dx") port, in("eax") val, options(preserves_flags, nomem, nostack)); diff --git a/src/hyperlight_guest/src/guest_handle/host_comm.rs b/src/hyperlight_guest/src/guest_handle/host_comm.rs index 972685ae1..d5cf57ffa 100644 --- a/src/hyperlight_guest/src/guest_handle/host_comm.rs +++ b/src/hyperlight_guest/src/guest_handle/host_comm.rs @@ -35,6 +35,7 @@ use crate::exit::out32; impl GuestHandle { /// Get user memory region as bytes. + #[hyperlight_guest_tracing_macro::trace_function] pub fn read_n_bytes_from_user_memory(&self, num: u64) -> Result> { let peb_ptr = self.peb().unwrap(); let user_memory_region_ptr = unsafe { (*peb_ptr).init_data.ptr as *mut u8 }; @@ -63,6 +64,7 @@ impl GuestHandle { /// /// When calling `call_host_function`, this function is called /// internally to get the return value. + #[hyperlight_guest_tracing_macro::trace_function] pub fn get_host_return_value>(&self) -> Result { let return_value = self .try_pop_shared_input_data_into::() @@ -83,6 +85,7 @@ impl GuestHandle { /// /// Note: The function return value must be obtained by calling /// `get_host_return_value`. + #[hyperlight_guest_tracing_macro::trace_function] pub fn call_host_function_without_returning_result( &self, function_name: &str, @@ -114,6 +117,7 @@ impl GuestHandle { /// sends it to the host, and then retrieves the return value. /// /// The return value is deserialized into the specified type `T`. + #[hyperlight_guest_tracing_macro::trace_function] pub fn call_host_function>( &self, function_name: &str, @@ -124,6 +128,7 @@ impl GuestHandle { self.get_host_return_value::() } + #[hyperlight_guest_tracing_macro::trace_function] pub fn get_host_function_details(&self) -> HostFunctionDetails { let peb_ptr = self.peb().unwrap(); let host_function_details_buffer = @@ -140,6 +145,7 @@ impl GuestHandle { } /// Write an error to the shared output data buffer. + #[hyperlight_guest_tracing_macro::trace_function] pub fn write_error(&self, error_code: ErrorCode, message: Option<&str>) { let guest_error: GuestError = GuestError::new( error_code, @@ -155,6 +161,7 @@ impl GuestHandle { } /// Log a message with the specified log level, source, caller, source file, and line number. + #[hyperlight_guest_tracing_macro::trace_function] pub fn log_message( &self, log_level: LogLevel, diff --git a/src/hyperlight_guest/src/guest_handle/io.rs b/src/hyperlight_guest/src/guest_handle/io.rs index ce8c82b0c..700bf06c6 100644 --- a/src/hyperlight_guest/src/guest_handle/io.rs +++ b/src/hyperlight_guest/src/guest_handle/io.rs @@ -27,6 +27,7 @@ use crate::error::{HyperlightGuestError, Result}; impl GuestHandle { /// Pops the top element from the shared input data buffer and returns it as a T + #[hyperlight_guest_tracing_macro::trace_function] pub fn try_pop_shared_input_data_into(&self) -> Result where T: for<'a> TryFrom<&'a [u8]>, @@ -68,6 +69,7 @@ impl GuestHandle { let buffer = &idb[last_element_offset_rel as usize..]; // convert the buffer to T + hyperlight_guest_tracing_macro::trace!("Start converting buffer"); let type_t = match T::try_from(buffer) { Ok(t) => Ok(t), Err(_e) => { @@ -77,6 +79,7 @@ impl GuestHandle { )); } }; + hyperlight_guest_tracing_macro::trace!("Finish converting buffer"); // update the stack pointer to point to the element we just popped of since that is now free idb[..8].copy_from_slice(&last_element_offset_rel.to_le_bytes()); @@ -88,6 +91,7 @@ impl GuestHandle { } /// Pushes the given data onto the shared output data buffer. + #[hyperlight_guest_tracing_macro::trace_function] pub fn push_shared_output_data(&self, data: Vec) -> Result<()> { let peb_ptr = self.peb().unwrap(); let output_stack_size = unsafe { (*peb_ptr).output_stack.size as usize }; @@ -133,7 +137,9 @@ impl GuestHandle { } // write the actual data + hyperlight_guest_tracing_macro::trace!("Start copy of data"); odb[stack_ptr_rel as usize..stack_ptr_rel as usize + data.len()].copy_from_slice(&data); + hyperlight_guest_tracing_macro::trace!("Finish copy of data"); // write the offset to the newly written data, to the top of the stack let bytes: [u8; 8] = stack_ptr_rel.to_le_bytes(); diff --git a/src/hyperlight_guest_bin/Cargo.toml b/src/hyperlight_guest_bin/Cargo.toml index a0347f866..ff30980b4 100644 --- a/src/hyperlight_guest_bin/Cargo.toml +++ b/src/hyperlight_guest_bin/Cargo.toml @@ -17,11 +17,14 @@ and third-party code used by our C-API needed to build a native hyperlight-guest default = ["libc", "printf"] libc = [] # compile musl libc printf = [] # compile printf +trace_guest = ["hyperlight-common/trace_guest", "dep:hyperlight-guest-tracing", "hyperlight-guest/trace_guest"] mem_profile = ["hyperlight-common/unwind_guest","hyperlight-common/mem_profile"] [dependencies] hyperlight-guest = { workspace = true, default-features = false } hyperlight-common = { workspace = true, default-features = false } +hyperlight-guest-tracing = { workspace = true, optional = true } +hyperlight-guest-tracing-macro = { workspace = true } buddy_system_allocator = "0.11.0" log = { version = "0.4", default-features = false } spin = "0.10.0" diff --git a/src/hyperlight_guest_bin/src/exceptions/gdt.rs b/src/hyperlight_guest_bin/src/exceptions/gdt.rs index 0a3b2cfb6..0da032321 100644 --- a/src/hyperlight_guest_bin/src/exceptions/gdt.rs +++ b/src/hyperlight_guest_bin/src/exceptions/gdt.rs @@ -72,6 +72,7 @@ struct GdtPointer { } /// Load the GDT +#[hyperlight_guest_tracing_macro::trace_function] pub unsafe fn load_gdt() { unsafe { let gdt_ptr = GdtPointer { diff --git a/src/hyperlight_guest_bin/src/exceptions/handler.rs b/src/hyperlight_guest_bin/src/exceptions/handler.rs index ab0da4cfe..42e401347 100644 --- a/src/hyperlight_guest_bin/src/exceptions/handler.rs +++ b/src/hyperlight_guest_bin/src/exceptions/handler.rs @@ -60,6 +60,7 @@ pub type HandlerT = fn(n: u64, info: *mut ExceptionInfo, ctx: *mut Context, pf_a /// Exception handler #[unsafe(no_mangle)] +#[hyperlight_guest_tracing_macro::trace_function] pub extern "C" fn hl_exception_handler( stack_pointer: u64, exception_number: u64, diff --git a/src/hyperlight_guest_bin/src/exceptions/idt.rs b/src/hyperlight_guest_bin/src/exceptions/idt.rs index 3107c4df4..820e96861 100644 --- a/src/hyperlight_guest_bin/src/exceptions/idt.rs +++ b/src/hyperlight_guest_bin/src/exceptions/idt.rs @@ -71,6 +71,7 @@ impl IdtEntry { // Architectures Software Developer's Manual). pub(crate) static mut IDT: [IdtEntry; 256] = unsafe { core::mem::zeroed() }; +#[hyperlight_guest_tracing_macro::trace_function] pub(crate) fn init_idt() { set_idt_entry(Exception::DivideByZero as usize, _do_excp0); // Divide by zero set_idt_entry(Exception::Debug as usize, _do_excp1); // Debug diff --git a/src/hyperlight_guest_bin/src/exceptions/idtr.rs b/src/hyperlight_guest_bin/src/exceptions/idtr.rs index d1d54830b..0d020f1e8 100644 --- a/src/hyperlight_guest_bin/src/exceptions/idtr.rs +++ b/src/hyperlight_guest_bin/src/exceptions/idtr.rs @@ -40,6 +40,7 @@ impl Idtr { } } +#[hyperlight_guest_tracing_macro::trace_function] pub(crate) unsafe fn load_idt() { unsafe { init_idt(); diff --git a/src/hyperlight_guest_bin/src/guest_function/call.rs b/src/hyperlight_guest_bin/src/guest_function/call.rs index 0c7c99a0e..85dc013cf 100644 --- a/src/hyperlight_guest_bin/src/guest_function/call.rs +++ b/src/hyperlight_guest_bin/src/guest_function/call.rs @@ -27,6 +27,7 @@ use crate::{GUEST_HANDLE, REGISTERED_GUEST_FUNCTIONS}; type GuestFunc = fn(&FunctionCall) -> Result>; +#[hyperlight_guest_tracing_macro::trace_function] pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result> { // Validate this is a Guest Function Call if function_call.function_call_type() != FunctionCallType::Guest { @@ -61,6 +62,7 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result core::mem::transmute::(function_pointer) }; + hyperlight_guest_tracing_macro::trace!("Calling guest function"); p_function(&function_call) } else { // The given function is not registered. The guest should implement a function called guest_dispatch_function to handle this. @@ -72,6 +74,7 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result fn guest_dispatch_function(function_call: FunctionCall) -> Result>; } + hyperlight_guest_tracing_macro::trace!("Guest function {} not found"); unsafe { guest_dispatch_function(function_call) } } } @@ -80,6 +83,7 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result // and we will leak memory as the epilogue will not be called as halt() is not going to return. #[unsafe(no_mangle)] #[inline(never)] +#[hyperlight_guest_tracing_macro::trace_function] fn internal_dispatch_function() -> Result<()> { let handle = unsafe { GUEST_HANDLE }; @@ -100,6 +104,7 @@ fn internal_dispatch_function() -> Result<()> { // This is implemented as a separate function to make sure that epilogue in the internal_dispatch_function is called before the halt() // which if it were included in the internal_dispatch_function cause the epilogue to not be called because the halt() would not return // when running in the hypervisor. +#[hyperlight_guest_tracing_macro::trace_function] pub(crate) extern "C" fn dispatch_function() { // The hyperlight host likes to use one partition and reset it in // various ways; if that has happened, there might stale TLB diff --git a/src/hyperlight_guest_bin/src/lib.rs b/src/hyperlight_guest_bin/src/lib.rs index 133e282e7..dbf2e4127 100644 --- a/src/hyperlight_guest_bin/src/lib.rs +++ b/src/hyperlight_guest_bin/src/lib.rs @@ -32,6 +32,7 @@ use hyperlight_common::mem::HyperlightPEB; use hyperlight_common::outb::OutBAction; use hyperlight_guest::exit::{abort_with_code_and_message, halt}; use hyperlight_guest::guest_handle::handle::GuestHandle; +use hyperlight_guest_tracing_macro::{trace, trace_function}; use log::LevelFilter; use spin::Once; @@ -155,6 +156,7 @@ unsafe extern "C" { static INIT: Once = Once::new(); #[unsafe(no_mangle)] +#[trace_function] pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) { if peb_address == 0 { panic!("PEB address is null"); @@ -204,6 +206,7 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve (*peb_ptr).guest_function_dispatch_ptr = dispatch_function as usize as u64; + trace!("hyperlight_main"); hyperlight_main(); } }); From 6e5187ddc333651bb437da7fd0f84093e9b99638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 15:28:10 +0300 Subject: [PATCH 13/21] Update sample guests to accept features that enable tracing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- Justfile | 10 +- .../rust_guests/callbackguest/Cargo.lock | 29 +- .../rust_guests/callbackguest/Cargo.toml | 8 +- src/tests/rust_guests/dummyguest/Cargo.lock | 264 +++++++++++++++++- src/tests/rust_guests/dummyguest/Cargo.toml | 13 +- src/tests/rust_guests/simpleguest/Cargo.lock | 29 +- src/tests/rust_guests/simpleguest/Cargo.toml | 7 + src/tests/rust_guests/witguest/Cargo.lock | 21 ++ src/tests/rust_guests/witguest/Cargo.toml | 8 +- 9 files changed, 372 insertions(+), 17 deletions(-) diff --git a/Justfile b/Justfile index 276c7985d..0c5b03a59 100644 --- a/Justfile +++ b/Justfile @@ -38,11 +38,11 @@ witguest-wit: cargo install --locked wasm-tools cd src/tests/rust_guests/witguest && wasm-tools component wit guest.wit -w -o interface.wasm -build-rust-guests target=default-target: (witguest-wit) - cd src/tests/rust_guests/callbackguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }} - cd src/tests/rust_guests/simpleguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }} - cd src/tests/rust_guests/dummyguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }} - cd src/tests/rust_guests/witguest && cargo build --profile={{ if target == "debug" { "dev" } else { target } }} +build-rust-guests target=default-target features="": (witguest-wit) + cd src/tests/rust_guests/callbackguest && cargo build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} + cd src/tests/rust_guests/simpleguest && cargo build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} + cd src/tests/rust_guests/dummyguest && cargo build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} + cd src/tests/rust_guests/witguest && cargo build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} @move-rust-guests target=default-target: cp {{ callbackguest_source }}/{{ target }}/callbackguest* {{ rust_guests_bin_dir }}/{{ target }}/ diff --git a/src/tests/rust_guests/callbackguest/Cargo.lock b/src/tests/rust_guests/callbackguest/Cargo.lock index 82979afeb..0015fb70d 100644 --- a/src/tests/rust_guests/callbackguest/Cargo.lock +++ b/src/tests/rust_guests/callbackguest/Cargo.lock @@ -85,6 +85,8 @@ version = "0.7.0" dependencies = [ "anyhow", "hyperlight-common", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "serde_json", ] @@ -98,10 +100,29 @@ dependencies = [ "glob", "hyperlight-common", "hyperlight-guest", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "log", "spin 0.10.0", ] +[[package]] +name = "hyperlight-guest-tracing" +version = "0.7.0" +dependencies = [ + "hyperlight-common", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing-macro" +version = "0.7.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itoa" version = "1.0.15" @@ -132,9 +153,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -233,9 +254,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", diff --git a/src/tests/rust_guests/callbackguest/Cargo.toml b/src/tests/rust_guests/callbackguest/Cargo.toml index ccbdf6a0a..2d63452e4 100644 --- a/src/tests/rust_guests/callbackguest/Cargo.toml +++ b/src/tests/rust_guests/callbackguest/Cargo.toml @@ -6,4 +6,10 @@ edition = "2021" [dependencies] hyperlight-guest = { path = "../../../hyperlight_guest" } hyperlight-guest-bin = { path = "../../../hyperlight_guest_bin" } -hyperlight-common = { path = "../../../hyperlight_common", default-features = false } \ No newline at end of file +hyperlight-common = { path = "../../../hyperlight_common", default-features = false } + +[features] +default = [] +trace_guest = ["hyperlight-guest-bin/trace_guest"] +unwind_guest = ["hyperlight-common/unwind_guest"] +mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] \ No newline at end of file diff --git a/src/tests/rust_guests/dummyguest/Cargo.lock b/src/tests/rust_guests/dummyguest/Cargo.lock index 3848de252..d87ea19cc 100644 --- a/src/tests/rust_guests/dummyguest/Cargo.lock +++ b/src/tests/rust_guests/dummyguest/Cargo.lock @@ -1,7 +1,269 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "buddy_system_allocator" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0108968a3a2dab95b089c0fc3f1afa7759aa5ebe6f1d86d206d6f7ba726eb" +dependencies = [ + "spin 0.9.8", +] + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "dummyguest" version = "0.4.0" +dependencies = [ + "hyperlight-common", + "hyperlight-guest-bin", +] + +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" +dependencies = [ + "bitflags", + "rustc_version", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hyperlight-common" +version = "0.7.0" +dependencies = [ + "anyhow", + "flatbuffers", + "log", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest" +version = "0.7.0" +dependencies = [ + "anyhow", + "hyperlight-common", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", + "serde_json", +] + +[[package]] +name = "hyperlight-guest-bin" +version = "0.7.0" +dependencies = [ + "buddy_system_allocator", + "cc", + "cfg-if", + "glob", + "hyperlight-common", + "hyperlight-guest", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", + "log", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing" +version = "0.7.0" +dependencies = [ + "hyperlight-common", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing-macro" +version = "0.7.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/src/tests/rust_guests/dummyguest/Cargo.toml b/src/tests/rust_guests/dummyguest/Cargo.toml index 619031cae..3de8389e9 100644 --- a/src/tests/rust_guests/dummyguest/Cargo.toml +++ b/src/tests/rust_guests/dummyguest/Cargo.toml @@ -1,4 +1,15 @@ [package] name = "dummyguest" version = "0.4.0" -edition = "2021" \ No newline at end of file +edition = "2021" + + +[dependencies] +hyperlight-guest-bin = { path = "../../../hyperlight_guest_bin" } +hyperlight-common = { path = "../../../hyperlight_common", default-features = false } + +[features] +default = [] +trace_guest = ["hyperlight-guest-bin/trace_guest"] +unwind_guest = ["hyperlight-common/unwind_guest"] +mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] \ No newline at end of file diff --git a/src/tests/rust_guests/simpleguest/Cargo.lock b/src/tests/rust_guests/simpleguest/Cargo.lock index b30d4ef5e..be07dfbf3 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.lock +++ b/src/tests/rust_guests/simpleguest/Cargo.lock @@ -76,6 +76,8 @@ version = "0.7.0" dependencies = [ "anyhow", "hyperlight-common", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "serde_json", ] @@ -89,10 +91,29 @@ dependencies = [ "glob", "hyperlight-common", "hyperlight-guest", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "log", "spin 0.10.0", ] +[[package]] +name = "hyperlight-guest-tracing" +version = "0.7.0" +dependencies = [ + "hyperlight-common", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing-macro" +version = "0.7.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itoa" version = "1.0.15" @@ -123,9 +144,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -234,9 +255,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", diff --git a/src/tests/rust_guests/simpleguest/Cargo.toml b/src/tests/rust_guests/simpleguest/Cargo.toml index f2e75ee1c..515b618d5 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.toml +++ b/src/tests/rust_guests/simpleguest/Cargo.toml @@ -8,3 +8,10 @@ hyperlight-guest = { path = "../../../hyperlight_guest" } hyperlight-guest-bin = { path = "../../../hyperlight_guest_bin" } hyperlight-common = { path = "../../../hyperlight_common", default-features = false } log = {version = "0.4", default-features = false } + +[features] +default = [] +trace_guest = ["hyperlight-guest-bin/trace_guest"] +unwind_guest = ["hyperlight-common/unwind_guest"] +mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] + diff --git a/src/tests/rust_guests/witguest/Cargo.lock b/src/tests/rust_guests/witguest/Cargo.lock index bcb5b4813..4c39c4272 100644 --- a/src/tests/rust_guests/witguest/Cargo.lock +++ b/src/tests/rust_guests/witguest/Cargo.lock @@ -219,6 +219,8 @@ version = "0.7.0" dependencies = [ "anyhow", "hyperlight-common", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "serde_json", ] @@ -232,10 +234,29 @@ dependencies = [ "glob", "hyperlight-common", "hyperlight-guest", + "hyperlight-guest-tracing", + "hyperlight-guest-tracing-macro", "log", "spin 0.10.0", ] +[[package]] +name = "hyperlight-guest-tracing" +version = "0.7.0" +dependencies = [ + "hyperlight-common", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing-macro" +version = "0.7.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indexmap" version = "2.9.0" diff --git a/src/tests/rust_guests/witguest/Cargo.toml b/src/tests/rust_guests/witguest/Cargo.toml index 63b38fc13..0be5aec00 100644 --- a/src/tests/rust_guests/witguest/Cargo.toml +++ b/src/tests/rust_guests/witguest/Cargo.toml @@ -7,4 +7,10 @@ edition = "2021" hyperlight-guest = { path = "../../../hyperlight_guest" } hyperlight-guest-bin = { path = "../../../hyperlight_guest_bin" } hyperlight-common = { path = "../../../hyperlight_common", default-features = false } -hyperlight-component-macro = { path = "../../../hyperlight_component_macro" } \ No newline at end of file +hyperlight-component-macro = { path = "../../../hyperlight_component_macro" } + +[features] +default = [] +trace_guest = ["hyperlight-guest-bin/trace_guest"] +unwind_guest = ["hyperlight-common/unwind_guest"] +mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] \ No newline at end of file From cc49e7b95fabd09d428df201a6cdc8652064e1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 15:29:05 +0300 Subject: [PATCH 14/21] Update utility for dumping logs to support the traces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- src/trace_dump/main.rs | 84 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/src/trace_dump/main.rs b/src/trace_dump/main.rs index afd3f8b81..eda5b5f8e 100644 --- a/src/trace_dump/main.rs +++ b/src/trace_dump/main.rs @@ -23,8 +23,21 @@ use std::rc::Rc; use std::sync::Mutex; use std::time::Duration; -use piet_common::{kurbo, Color, RenderContext, Text, TextLayout, TextLayoutBuilder}; +use piet_common::{Color, RenderContext, Text, TextLayout, TextLayoutBuilder, kurbo}; +fn read_u8_vec(inf: &mut File) -> Option> { + let len = read_u64(inf)?; + let mut vec = Vec::with_capacity(len as usize); + for _ in 0..len { + vec.push(read_u8(inf)?); + } + Some(vec) +} +fn read_u8(inf: &mut File) -> Option { + let mut bytes: [u8; 1] = [0; 1]; + inf.read_exact(&mut bytes).ok()?; + Some(u8::from_ne_bytes(bytes)) +} fn read_u128(inf: &mut File) -> Result { let mut bytes: [u8; 16] = [0; 16]; inf.read_exact(&mut bytes)?; @@ -92,6 +105,53 @@ fn dump_free( Some(()) } +fn dump_trace_record( + _state: &mut State, + _rs: &mut (), + indent: &mut u64, + now: Duration, + msg: Rc<[u8]>, +) -> Option<()> { + let msg = String::from_utf8_lossy(&msg); + + // Pritty printing of trace records to show indentation based on the trace depth. + // Indentation is increased for messages starting with `>`, and decreased for messages starting + // with `<`. + // With the exception of `> halt`, which decreases the indent (because `> entrypoint` does not + // have a corresponding `< entrypoint`) + let msg = if msg.starts_with('>') { + if msg == "> halt" { + if *indent > 0 { + *indent -= 1; + } + } + let indent_str = " ".repeat(*indent as usize); + let msg = format!("{}{}", indent_str, &msg); + if msg != "> halt" { + // If the message is not `> halt`, increment the indent. + // This is to ensure that the next message is indented correctly. + *indent += 1; + } + + msg + } else if msg.starts_with('<') { + if *indent > 0 { + *indent -= 1; + } + let indent_str = " ".repeat(*indent as usize); + let msg = format!("{}{}", indent_str, msg); + + msg + } else { + let indent_str = " ".repeat(*indent as usize); + format!("{}{}", indent_str, msg) + }; + + println!("\n[{:9?}] {}", now, msg); + + Some(()) +} + // todo: this should use something more reasonable than a hash table // for each node. let's measure the out-degree and see if a small // array is better, to start. @@ -542,20 +602,33 @@ fn render_free( Some(()) } -fn read_file( +fn render_trace_record( + _state: &mut State, + _rs: &mut RenderState, + _indent: &mut u64, + _now: Duration, + _msg: Rc<[u8]>, +) -> Option<()> { + Some(()) +} + +fn read_file( state: &mut State, mut handle_state: S, handle_ident: I, handle_unwind: U, handle_alloc: A, handle_free: F, + handle_trace_record: T, ) -> Option<()> where I: Fn(&mut State, &mut S, Duration, blake3::Hash) -> Option<()>, U: Fn(&mut State, &mut S, Duration, Rc<[u64]>) -> Option<()>, A: Fn(&mut State, &mut S, Duration, u64, u64, Rc<[u64]>) -> Option<()>, F: Fn(&mut State, &mut S, Duration, u64, u64, Rc<[u64]>) -> Option<()>, + T: Fn(&mut State, &mut S, &mut u64, Duration, Rc<[u8]>) -> Option<()>, { + let mut indent = 0; loop { let time = match read_u128(&mut state.inf) { Ok(t) => t, @@ -600,6 +673,9 @@ where let trace = amt_trace.1.clone(); state.total -= amt; handle_free(state, &mut handle_state, now, ptr, amt, trace)?; + } else if frame_id == 4 { + let msg = read_u8_vec(&mut state.inf)?.into(); + handle_trace_record(state, &mut handle_state, &mut indent, now, msg)?; } else { return None; } @@ -699,6 +775,7 @@ fn spawn_render_thread( render_unwind, render_alloc, render_free, + render_trace_record, )?; bar_ffmpeg.wait().ok()?; flame_ffmpeg.wait().ok()?; @@ -755,6 +832,7 @@ fn dump_trace(mut state: State) { dump_unwind, dump_alloc, dump_free, + dump_trace_record, ); } @@ -771,6 +849,7 @@ fn plot_mem(args: Vec, mut state: State) { |_, _, _, _| Some(()), |_, _, _, _, _, _| Some(()), |_, _, _, _, _, _| Some(()), + |_, _, _, _, _| Some(()), ) { Some(()) => (), None => { @@ -810,6 +889,7 @@ fn plot_mem(args: Vec, mut state: State) { |_, _, _, _| Some(()), count_frame, count_frame, + |_, _, _, _, _| Some(()), ); if state.num_durations > 0 { (*jobs.lock().unwrap()).push((*start_duration.lock().unwrap(), state.max_duration)); From 0a58a0602b6888ad2d190e783c21e0dabb9a893a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 15:38:08 +0300 Subject: [PATCH 15/21] Add documentation on how to use the tracing functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- docs/hyperlight-metrics-logs-and-traces.md | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/hyperlight-metrics-logs-and-traces.md b/docs/hyperlight-metrics-logs-and-traces.md index 9158b43e2..55042c45f 100644 --- a/docs/hyperlight-metrics-logs-and-traces.md +++ b/docs/hyperlight-metrics-logs-and-traces.md @@ -91,3 +91,72 @@ NOTE: when running this on windows that this is a linux container, so you will n ``` Once the container or the exe is running, the trace output can be viewed in the jaeger UI at [http://localhost:16686/search](http://localhost:16686/search). + +## Guest Tracing, Unwinding, and Memory Profiling + +Hyperlight provides advanced observability features for guest code running inside micro virtual machines. You can enable guest-side tracing, stack unwinding, and memory profiling using the `trace_guest`, `unwind_guest`, and `mem_profile` features. This section explains how to build, run, and inspect guest traces. + +### Building a Guest with Tracing Support + +To build a guest with tracing enabled, use the following commands: + +```bash +just build-rust-guests debug trace_guest +just move-rust-guests debug +``` + +This will build the guest binaries with the `trace_guest` feature enabled and move them to the appropriate location for use by the host. + +### Running a Hyperlight Example with Guest Tracing + +Once the guest is built, you can run a Hyperlight example with guest tracing enabled. For example: + +```bash +cargo run --example hello-world --features trace_guest +``` + +This will execute the `hello-world` example, loading the guest with tracing enabled. During execution, trace data will be collected and written to a file in the `trace` directory. + +### Inspecting Guest Trace Files + +To inspect the trace file generated by the guest, use the `trace_dump` crate. You will need the path to the guest symbols and the trace file. Run the following command: + +```bash +cargo run -p trace_dump list_frames +``` + +Replace `` with the path to the guest binary or symbol file, and `` with the path to the trace file in the `trace` directory. + +This command will list the stack frames and tracing information captured during guest execution, allowing you to analyze guest behavior, stack traces, and memory usage. + +#### Example + +```bash +cargo run -p trace_dump ./src/tests/rust_guests/bin/debug/simpleguest ./trace/.trace list_frames +``` + +You can use additional features such as `unwind_guest` and `mem_profile` by enabling them during the build and run steps. Refer to the guest and host documentation for more details on these features. + +> **Note:** Make sure to follow the build and run steps in order, and ensure that the guest binaries are up to date before running the host example. + +## System Prerequisites for `trace_dump` + +To build and use the `trace_dump` crate and related guest tracing features, you must have the following system libraries and development tools installed on your system: + +- **glib-2.0** development files + - Fedora/RHEL/CentOS: + ```bash + sudo dnf install glib2-devel pkgconf-pkg-config + ``` +- **cairo** and **cairo-gobject** development files + - Fedora/RHEL/CentOS: + ```bash + sudo dnf install cairo-devel cairo-gobject-devel + ``` +- **pango** development files + - Fedora/RHEL/CentOS: + ```bash + sudo dnf install pango-devel + ``` + +These libraries are required by Rust crates such as `glib-sys`, `cairo-sys-rs`, and `pango-sys`, which are dependencies of the tracing and visualization tools. If you encounter errors about missing `.pc` files (e.g., `glib-2.0.pc`, `cairo.pc`, `pango.pc`), ensure the corresponding `-devel` packages are installed. From 26966ecf4f3e058042f3bfb5df2bc5393cbca3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Tue, 8 Jul 2025 17:25:53 +0300 Subject: [PATCH 16/21] Fix clippy warnings for unrelated work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- src/hyperlight_host/src/hypervisor/surrogate_process.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/surrogate_process.rs b/src/hyperlight_host/src/hypervisor/surrogate_process.rs index caaa79d00..d026362d2 100644 --- a/src/hyperlight_host/src/hypervisor/surrogate_process.rs +++ b/src/hyperlight_host/src/hypervisor/surrogate_process.rs @@ -83,7 +83,6 @@ impl Drop for SurrogateProcess { "Failed to return surrogate process to surrogate process manager when dropping : {:?}", e ); - return; } }, Err(e) => { @@ -91,7 +90,6 @@ impl Drop for SurrogateProcess { "Failed to get surrogate process manager when dropping SurrogateProcess: {:?}", e ); - return; } } } From 705417e90a1e73dde34c806941a9bff2f037b6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Wed, 9 Jul 2025 12:07:22 +0300 Subject: [PATCH 17/21] Fix clippy warning of method missing # Safety section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- src/hyperlight_host/src/sandbox/initialized_multi_use.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 6208d8f85..7cab689d3 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -136,6 +136,7 @@ impl MultiUseSandbox { /// `rgn.region_type` is ignored, since guest PTEs are not created /// for the new memory. /// + /// # Safety /// It is the caller's responsibility to ensure that the host side /// of the region remains intact and is not written to until this /// mapping is removed, either due to the destruction of the From 9ad54de7a0f2c8bc61eb8f3e52a855b969a0d991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Wed, 16 Jul 2025 14:36:31 +0300 Subject: [PATCH 18/21] [hyperlight-guest-tracing] fixup to add tests and improve macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This adds tests for both the macro and the tracing crates - Some refactoring was needed to the tracing crate to test thoroughly - Improve macros by allowing expressions to be wrapped with trace records Signed-off-by: Doru Blânzeanu --- src/hyperlight_guest/src/guest_handle/io.rs | 27 +-- .../src/exceptions/handler.rs | 5 +- .../src/guest_function/call.rs | 8 +- src/hyperlight_guest_bin/src/lib.rs | 5 +- src/hyperlight_guest_tracing/src/lib.rs | 182 ++++++++++++++---- src/hyperlight_guest_tracing_macro/src/lib.rs | 143 ++++++++++---- 6 files changed, 283 insertions(+), 87 deletions(-) diff --git a/src/hyperlight_guest/src/guest_handle/io.rs b/src/hyperlight_guest/src/guest_handle/io.rs index 700bf06c6..4654f0001 100644 --- a/src/hyperlight_guest/src/guest_handle/io.rs +++ b/src/hyperlight_guest/src/guest_handle/io.rs @@ -69,17 +69,18 @@ impl GuestHandle { let buffer = &idb[last_element_offset_rel as usize..]; // convert the buffer to T - hyperlight_guest_tracing_macro::trace!("Start converting buffer"); - let type_t = match T::try_from(buffer) { - Ok(t) => Ok(t), - Err(_e) => { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!("Unable to convert buffer to {}", type_name::()), - )); + let type_t = hyperlight_guest_tracing_macro::trace!( + "convert buffer", + match T::try_from(buffer) { + Ok(t) => Ok(t), + Err(_e) => { + return Err(HyperlightGuestError::new( + ErrorCode::GuestError, + format!("Unable to convert buffer to {}", type_name::()), + )); + } } - }; - hyperlight_guest_tracing_macro::trace!("Finish converting buffer"); + ); // update the stack pointer to point to the element we just popped of since that is now free idb[..8].copy_from_slice(&last_element_offset_rel.to_le_bytes()); @@ -137,9 +138,9 @@ impl GuestHandle { } // write the actual data - hyperlight_guest_tracing_macro::trace!("Start copy of data"); - odb[stack_ptr_rel as usize..stack_ptr_rel as usize + data.len()].copy_from_slice(&data); - hyperlight_guest_tracing_macro::trace!("Finish copy of data"); + hyperlight_guest_tracing_macro::trace!("copy data", { + odb[stack_ptr_rel as usize..stack_ptr_rel as usize + data.len()].copy_from_slice(&data); + }); // write the offset to the newly written data, to the top of the stack let bytes: [u8; 8] = stack_ptr_rel.to_le_bytes(); diff --git a/src/hyperlight_guest_bin/src/exceptions/handler.rs b/src/hyperlight_guest_bin/src/exceptions/handler.rs index 42e401347..65ae5257b 100644 --- a/src/hyperlight_guest_bin/src/exceptions/handler.rs +++ b/src/hyperlight_guest_bin/src/exceptions/handler.rs @@ -60,12 +60,15 @@ pub type HandlerT = fn(n: u64, info: *mut ExceptionInfo, ctx: *mut Context, pf_a /// Exception handler #[unsafe(no_mangle)] -#[hyperlight_guest_tracing_macro::trace_function] pub extern "C" fn hl_exception_handler( stack_pointer: u64, exception_number: u64, page_fault_address: u64, ) { + // This function does not return, so wrapping it in a tracing macro + // issues a warning of unreachable code. + hyperlight_guest_tracing_macro::trace!("> hl_exception_handler"); + let ctx = stack_pointer as *mut Context; let exn_info = (stack_pointer + size_of::() as u64) as *mut ExceptionInfo; diff --git a/src/hyperlight_guest_bin/src/guest_function/call.rs b/src/hyperlight_guest_bin/src/guest_function/call.rs index 85dc013cf..376b0cf35 100644 --- a/src/hyperlight_guest_bin/src/guest_function/call.rs +++ b/src/hyperlight_guest_bin/src/guest_function/call.rs @@ -62,8 +62,7 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result core::mem::transmute::(function_pointer) }; - hyperlight_guest_tracing_macro::trace!("Calling guest function"); - p_function(&function_call) + hyperlight_guest_tracing_macro::trace!("guest_function", p_function(&function_call)) } else { // The given function is not registered. The guest should implement a function called guest_dispatch_function to handle this. @@ -74,8 +73,9 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result fn guest_dispatch_function(function_call: FunctionCall) -> Result>; } - hyperlight_guest_tracing_macro::trace!("Guest function {} not found"); - unsafe { guest_dispatch_function(function_call) } + hyperlight_guest_tracing_macro::trace!("default guest function", unsafe { + guest_dispatch_function(function_call) + }) } } diff --git a/src/hyperlight_guest_bin/src/lib.rs b/src/hyperlight_guest_bin/src/lib.rs index dbf2e4127..9d8a2c35e 100644 --- a/src/hyperlight_guest_bin/src/lib.rs +++ b/src/hyperlight_guest_bin/src/lib.rs @@ -206,8 +206,9 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve (*peb_ptr).guest_function_dispatch_ptr = dispatch_function as usize as u64; - trace!("hyperlight_main"); - hyperlight_main(); + trace!("hyperlight_main", + hyperlight_main(); + ); } }); diff --git a/src/hyperlight_guest_tracing/src/lib.rs b/src/hyperlight_guest_tracing/src/lib.rs index 5264f2efa..0c254b293 100644 --- a/src/hyperlight_guest_tracing/src/lib.rs +++ b/src/hyperlight_guest_tracing/src/lib.rs @@ -23,14 +23,18 @@ use core::mem::MaybeUninit; use hyperlight_common::outb::OutBAction; use spin::Mutex; +/// Type alias for the function that sends trace records to the host. +type SendToHostFn = fn(u64, &[TraceRecord]); + /// Global trace buffer for storing trace records. -static TRACE_BUFFER: Mutex = Mutex::new(TraceBuffer::new()); +static TRACE_BUFFER: Mutex = Mutex::new(TraceBuffer::new(send_to_host)); /// Maximum number of entries in the trace buffer. -const MAX_NO_OF_ENTRIES: usize = 64; +/// From local testing, 32 entries seems to be a good balance between performance and memory usage. +const MAX_NO_OF_ENTRIES: usize = 32; /// Maximum length of a trace message in bytes. -pub const MAX_TRACE_MSG_LEN: usize = 64; +const MAX_TRACE_MSG_LEN: usize = 64; #[derive(Debug, Copy, Clone)] /// Represents a trace record of a guest with a number of cycles and a message. @@ -43,20 +47,45 @@ pub struct TraceRecord { pub msg: [u8; MAX_TRACE_MSG_LEN], } +impl TryFrom<&str> for TraceRecord { + type Error = &'static str; + + fn try_from(msg: &str) -> Result { + if msg.len() > MAX_TRACE_MSG_LEN { + return Err("Message too long"); + } + + let cycles = invariant_tsc::read_tsc(); + + Ok(TraceRecord { + cycles, + msg: { + let mut arr = [0u8; MAX_TRACE_MSG_LEN]; + arr[..msg.len()].copy_from_slice(msg.as_bytes()); + arr + }, + msg_len: msg.len(), + }) + } +} + /// A buffer for storing trace records. struct TraceBuffer { /// The entries in the trace buffer. entries: [TraceRecord; MAX_NO_OF_ENTRIES], /// The index where the next entry will be written. write_index: usize, + /// Function to send the trace records to the host. + send_to_host: SendToHostFn, } impl TraceBuffer { /// Creates a new `TraceBuffer` with uninitialized entries. - const fn new() -> Self { + const fn new(f: SendToHostFn) -> Self { Self { entries: unsafe { [MaybeUninit::zeroed().assume_init(); MAX_NO_OF_ENTRIES] }, write_index: 0, + send_to_host: f, } } @@ -72,26 +101,26 @@ impl TraceBuffer { if write_index == 0 { // If buffer is full send to host - self.send_to_host(MAX_NO_OF_ENTRIES); + (self.send_to_host)(MAX_NO_OF_ENTRIES as u64, &self.entries); } } /// Flush the trace buffer, sending any remaining records to the host. fn flush(&mut self) { if self.write_index > 0 { - self.send_to_host(self.write_index); + (self.send_to_host)(self.write_index as u64, &self.entries); self.write_index = 0; // Reset write index after flushing } } +} - /// Send the trace records to the host. - fn send_to_host(&self, count: usize) { - unsafe { - core::arch::asm!("out dx, al", +/// Send the trace records to the host. +fn send_to_host(len: u64, records: &[TraceRecord]) { + unsafe { + core::arch::asm!("out dx, al", in("dx") OutBAction::TraceRecord as u16, - in("rax") count as u64, - in("rcx") &self.entries as * const _ as u64); - } + in("rax") len, + in("rcx") records.as_ptr() as u64); } } @@ -129,29 +158,18 @@ pub mod invariant_tsc { } } -/// Create a trace record with the given message. +/// Attempt to create a trace record from the message. +/// If the message is too long it will skip the record. +/// This is useful for ensuring that the trace buffer does not overflow. +/// If the message is successfully converted, it will be pushed to the trace buffer. /// -/// Note: The message must not exceed `MAX_TRACE_MSG_LEN` bytes. -/// If the message is too long, it will be skipped. +/// **Note**: The message must not exceed `MAX_TRACE_MSG_LEN` bytes. pub fn create_trace_record(msg: &str) { - if msg.len() > MAX_TRACE_MSG_LEN { - return; // Message too long, skip tracing + // Maybe panic if the message is too long? + if let Ok(entry) = TraceRecord::try_from(msg) { + let mut buffer = TRACE_BUFFER.lock(); + buffer.push(entry); } - - let cycles = invariant_tsc::read_tsc(); - - let entry = TraceRecord { - cycles, - msg: { - let mut arr = [0u8; MAX_TRACE_MSG_LEN]; - arr[..msg.len()].copy_from_slice(msg.as_bytes()); - arr - }, - msg_len: msg.len(), - }; - - let mut buffer = TRACE_BUFFER.lock(); - buffer.push(entry); } /// Flush the trace buffer to send any remaining trace records to the host. @@ -159,3 +177,101 @@ pub fn flush_trace_buffer() { let mut buffer = TRACE_BUFFER.lock(); buffer.flush(); } + +#[cfg(test)] +mod tests { + use alloc::format; + + use super::*; + + /// This is a mock function for testing purposes. + /// In a real scenario, this would send the trace records to the host. + fn mock_send_to_host(_len: u64, _records: &[TraceRecord]) {} + + fn create_test_entry(msg: &str) -> TraceRecord { + let cycles = invariant_tsc::read_tsc(); + + TraceRecord { + cycles, + msg: { + let mut arr = [0u8; MAX_TRACE_MSG_LEN]; + arr[..msg.len()].copy_from_slice(msg.as_bytes()); + arr + }, + msg_len: msg.len(), + } + } + + #[test] + fn test_push_trace_record() { + let mut buffer = TraceBuffer::new(mock_send_to_host); + + let msg = "Test message"; + let entry = create_test_entry(msg); + + buffer.push(entry); + assert_eq!(buffer.write_index, 1); + assert_eq!(buffer.entries[0].msg_len, msg.len()); + assert_eq!(&buffer.entries[0].msg[..msg.len()], msg.as_bytes()); + assert!(buffer.entries[0].cycles > 0); // Ensure cycles is set + } + + #[test] + fn test_flush_trace_buffer() { + let mut buffer = TraceBuffer::new(mock_send_to_host); + + let msg = "Test message"; + let entry = create_test_entry(msg); + + buffer.push(entry); + assert_eq!(buffer.write_index, 1); + assert_eq!(buffer.entries[0].msg_len, msg.len()); + assert_eq!(&buffer.entries[0].msg[..msg.len()], msg.as_bytes()); + assert!(buffer.entries[0].cycles > 0); + + // Flush the buffer + buffer.flush(); + + // After flushing, the entryes should still be intact, we don't clear them + assert_eq!(buffer.write_index, 0); + assert_eq!(buffer.entries[0].msg_len, msg.len()); + assert_eq!(&buffer.entries[0].msg[..msg.len()], msg.as_bytes()); + assert!(buffer.entries[0].cycles > 0); + } + + #[test] + fn test_auto_flush_on_full() { + let mut buffer = TraceBuffer::new(mock_send_to_host); + + // Fill the buffer to trigger auto-flush + for i in 0..MAX_NO_OF_ENTRIES { + let msg = format!("Message {}", i); + let entry = create_test_entry(&msg); + buffer.push(entry); + } + + // After filling, the write index should be 0 (buffer is full) + assert_eq!(buffer.write_index, 0); + + // The first entry should still be intact + assert_eq!(buffer.entries[0].msg_len, "Message 0".len()); + } + + /// Test TraceRecord creation with a valid message + #[test] + fn test_trace_record_creation_valid() { + let msg = "Valid message"; + let entry = TraceRecord::try_from(msg).expect("Failed to create TraceRecord"); + assert_eq!(entry.msg_len, msg.len()); + assert_eq!(&entry.msg[..msg.len()], msg.as_bytes()); + assert!(entry.cycles > 0); // Ensure cycles is set + } + + /// Test TraceRecord creation with a message that exceeds the maximum length + #[test] + fn test_trace_record_creation_too_long() { + let long_msg = "A".repeat(MAX_TRACE_MSG_LEN + 1); + let result = TraceRecord::try_from(long_msg.as_str()); + assert!(result.is_err(), "Expected error for message too long"); + } +} diff --git a/src/hyperlight_guest_tracing_macro/src/lib.rs b/src/hyperlight_guest_tracing_macro/src/lib.rs index c2e3e4bc2..a6144482b 100644 --- a/src/hyperlight_guest_tracing_macro/src/lib.rs +++ b/src/hyperlight_guest_tracing_macro/src/lib.rs @@ -16,13 +16,12 @@ limitations under the License. use proc_macro::TokenStream; use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::{ItemFn, Lit, parse_macro_input}; +use syn::{ItemFn, parse_macro_input}; /// A procedural macro attribute for tracing function calls. /// Usage: /// ```rust -/// #[trace_function] +/// #[hyperlight_guest_tracing_macro::trace_function] /// fn my_function() { /// // // Function body /// } @@ -79,63 +78,139 @@ pub fn trace_function(_attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::from(expanded) } -/// Input structure for the trace macro -struct TraceInput { - message: Lit, +// Input structure for the trace macro +struct TraceMacroInput { + message: syn::Lit, + statement: Option, } -impl Parse for TraceInput { - fn parse(input: ParseStream) -> syn::Result { - Ok(TraceInput { - message: input.parse()?, - }) +impl syn::parse::Parse for TraceMacroInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let message: syn::Lit = input.parse()?; + if !matches!(message, syn::Lit::Str(_)) { + return Err(input.error("first argument to trace! must be a string literal")); + } + let statement = if input.peek(syn::Token![,]) { + let _: syn::Token![,] = input.parse()?; + Some(input.parse()?) + } else { + None + }; + Ok(TraceMacroInput { message, statement }) } } -/// This macro creates a trace record with a message. +/// This macro creates a trace record with a message, or traces a block with entry/exit records. /// /// Usage: /// ```rust +/// use hyperlight_guest_tracing_macro::trace; /// trace!("message"); +/// trace!("message", { /* block of code */ }); +/// ``` +/// +/// When called with an expression or statement as the second argument, it is wrapped in a block, +/// entry and exit trace records are created at the start and end of block, and the result of the block is returned. +/// +/// # Examples +/// +/// ## Basic usage: trace with message only +/// +/// ``` +/// use hyperlight_guest_tracing_macro::trace; +/// trace!("hello"); +/// ``` +/// +/// ## Trace with a block, returning a value +/// +/// ``` +/// use hyperlight_guest_tracing_macro::trace; +/// let x = trace!("block", { 42 }); +/// assert_eq!(x, 42); +/// ``` +/// +/// ## Trace with a block using local variables +/// +/// ``` +/// use hyperlight_guest_tracing_macro::trace; +/// let y = 10; +/// let z = trace!("sum", { y + 5 }); +/// assert_eq!(z, 15); +/// ``` +/// +/// ## Trace with a block that returns a reference +/// +/// ``` +/// use hyperlight_guest_tracing_macro::trace; +/// let s = String::from("abc"); +/// let r: &str = trace!("ref", { &s }); +/// assert_eq!(r, "abc"); +/// ``` +/// +/// ## Control flow: `return` inside the block returns from the function +/// +/// ``` +/// use hyperlight_guest_tracing_macro::trace; +/// fn foo() -> i32 { +/// let _ = trace!("fail", { +/// // This return only exits the closure, not the function `foo`. +/// return 42; +/// }); +/// assert!(false, "This should not be reached"); +/// } +/// ``` +/// +/// ## Control flow: `break` inside the block exits the outer loop +/// +/// ``` +/// use hyperlight_guest_tracing_macro::trace; +/// let mut x = 0; +/// for i in 1..3 { +/// x = i; +/// let _ = trace!("msg", { +/// // This break should exit the loop. +/// break; +/// }); +/// } +/// assert_eq!(x, 1, "Loop should break after the first iteration"); /// ``` #[proc_macro] pub fn trace(input: TokenStream) -> TokenStream { - // Convert to proc_macro2::TokenStream for parsing - let input2: proc_macro2::TokenStream = input.clone().into(); - - // Try to parse as message - if let Ok(parsed) = syn::parse2::(input2) { - let trace_message = match parsed.message { - Lit::Str(lit_str) => lit_str.value(), - _ => "expression".to_string(), + let parsed = syn::parse_macro_input!(input as TraceMacroInput); + let trace_message = match parsed.message { + syn::Lit::Str(ref lit_str) => lit_str.value(), + _ => unreachable!(), + }; + if let Some(statement) = parsed.statement { + let entry = format!("+ {}", trace_message); + let exit = format!("- {}", trace_message); + let expanded = quote! { + { + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(#entry); + let __trace_result = #statement; + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::create_trace_record(#exit); + __trace_result + } }; - + TokenStream::from(expanded) + } else { let expanded = quote! { { #[cfg(feature = "trace_guest")] hyperlight_guest_tracing::create_trace_record(#trace_message); } }; - - return TokenStream::from(expanded); + TokenStream::from(expanded) } - - // Fallback: treat the entire input as an expression with default message - let expanded = quote! { - { - #[cfg(feature = "trace_guest")] - hyperlight_guest_tracing::create_trace_record("expression"); - } - }; - - TokenStream::from(expanded) } /// This macro flushes the trace buffer, sending any remaining trace records to the host. /// /// Usage: /// ```rust -/// flush!(); +/// hyperlight_guest_tracing_macro::flush!(); /// ``` #[proc_macro] pub fn flush(_input: TokenStream) -> TokenStream { From 60ea81dd3af32055716705fcbdfacdbf8bfc53b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Wed, 16 Jul 2025 14:47:15 +0300 Subject: [PATCH 19/21] [hyperlight-host/trace] Fixup: update the way the TSC frequency is calculated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rely on TSC readings on the host from when the guest is started and when the first records are received. Use these two moments in time and TSC readings to calculate the TSC frequency. - Afterwards we store the TSC reading from inside the guest and calculate all incoming TSC readings relative to the first one and using the frequency we can calculate the amount of time it took. Signed-off-by: Doru Blânzeanu --- .../src/hypervisor/hyperv_linux.rs | 6 +- .../src/hypervisor/hyperv_windows.rs | 6 +- src/hyperlight_host/src/hypervisor/kvm.rs | 4 +- src/hyperlight_host/src/sandbox/mod.rs | 84 ++++++++++++++----- src/hyperlight_host/src/sandbox/outb.rs | 31 +++++-- 5 files changed, 96 insertions(+), 35 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index 2c7eb7a8e..65f8910be 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -763,8 +763,10 @@ impl Hypervisor for HypervLinuxDriver { } else { #[cfg(feature = "trace_guest")] if self.trace_info.guest_start_epoch.is_none() { - // Set the guest start epoch to the current time, before running the vcpu - crate::debug!("HyperV - Guest Start Epoch set"); + // Store the guest start epoch and cycles to trace the guest execution time + crate::debug!("MSHV - Guest Start Epoch set"); + self.trace_info.guest_start_tsc = + Some(hyperlight_guest_tracing::invariant_tsc::read_tsc()); self.trace_info.guest_start_epoch = Some(std::time::Instant::now()); } // Note: if a `InterruptHandle::kill()` called while this thread is **here** diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 08350d40c..01c2b0560 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -734,8 +734,10 @@ impl Hypervisor for HypervWindowsDriver { } else { #[cfg(feature = "trace_guest")] if self.trace_info.guest_start_epoch.is_none() { - // Set the guest start epoch to the current time, before running the vcpu - crate::debug!("MSHV - Guest Start Epoch set"); + // Store the guest start epoch and cycles to trace the guest execution time + crate::debug!("HyperV - Guest Start Epoch set"); + self.trace_info.guest_start_tsc = + Some(hyperlight_guest_tracing::invariant_tsc::read_tsc()); self.trace_info.guest_start_epoch = Some(std::time::Instant::now()); } self.processor.run()? diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 0b668c305..188c27ed7 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -645,9 +645,11 @@ impl Hypervisor for KVMDriver { } else { #[cfg(feature = "trace_guest")] if self.trace_info.guest_start_epoch.is_none() { - // Set the guest start epoch to the current time, before running the vcpu + // Store the guest start epoch and cycles to trace the guest execution time crate::debug!("KVM - Guest Start Epoch set"); self.trace_info.guest_start_epoch = Some(std::time::Instant::now()); + self.trace_info.guest_start_tsc = + Some(hyperlight_guest_tracing::invariant_tsc::read_tsc()); } // Note: if a `InterruptHandle::kill()` called while this thread is **here** diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index 7c3c5b156..5b367cb6f 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -90,7 +90,7 @@ pub type ExtraAllowedSyscall = i64; /// Determine whether a suitable hypervisor is available to run /// this sandbox. /// -// Returns a boolean indicating whether a suitable hypervisor is present. +/// Returns a boolean indicating whether a suitable hypervisor is present. #[instrument(skip_all, parent = Span::current())] pub fn is_hypervisor_present() -> bool { hypervisor::get_available_hypervisor().is_some() @@ -103,18 +103,23 @@ pub fn is_hypervisor_present() -> bool { pub(crate) struct TraceInfo { /// The epoch against which trace events are timed; at least as /// early as the creation of the sandbox being traced. - #[allow(dead_code)] pub epoch: std::time::Instant, /// The frequency of the timestamp counter. - #[allow(dead_code)] - pub tsc_freq: u64, + pub tsc_freq: Option, /// The epoch at which the guest started, if it has started. /// This is used to calculate the time spent in the guest relative to the - /// time of the host. - #[allow(dead_code)] + /// time when the host started. pub guest_start_epoch: Option, - /// The start guest time, in TSC cycles, for the current guest. - #[allow(dead_code)] + /// The start guest time, in TSC cycles, for the current guest has a double purpose. + /// This field is used in two ways: + /// 1. It contains the TSC value recorded on the host when the guest started. + /// This is used to calculate the TSC frequency which is the same on the host and guest. + /// The TSC frequency is used to convert TSC values to timestamps in the trace. + /// **NOTE**: This is only used until the TSC frequency is calculated, when the first + /// records are received. + /// 2. To store the TSC value at recorded on the guest when the guest started (first record + /// received) + /// This is used to calculate the records timestamps relative to when guest started. pub guest_start_tsc: Option, /// The file to which the trace is being written #[allow(dead_code)] @@ -139,8 +144,17 @@ impl TraceInfo { ) -> crate::Result { let mut path = std::env::current_dir()?; path.push("trace"); + + // create directory if it does not exist + if !path.exists() { + std::fs::create_dir(&path)?; + } path.push(uuid::Uuid::new_v4().to_string()); path.set_extension("trace"); + + log::info!("Creating trace file at: {}", path.display()); + println!("Creating trace file at: {}", path.display()); + #[cfg(feature = "unwind_guest")] let hash = unwind_module.hash(); #[cfg(feature = "unwind_guest")] @@ -150,11 +164,17 @@ impl TraceInfo { let cache = framehop::x86_64::CacheX86_64::new(); (unwinder, Arc::new(Mutex::new(cache))) }; - let tsc_freq = Self::calculate_tsc_freq()?; + if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() { + // If the platform does not support invariant TSC, warn the user. + // On Azure nested virtualization, the TSC invariant bit is not correctly reported, this is a known issue. + log::warn!( + "Invariant TSC is not supported on this platform, trace timestamps may be inaccurate" + ); + } let ret = Self { epoch: std::time::Instant::now(), - tsc_freq, + tsc_freq: None, guest_start_epoch: None, guest_start_tsc: None, file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)), @@ -173,22 +193,40 @@ impl TraceInfo { Ok(ret) } - /// Calculate the TSC frequency based on the RDTSC instruction. - fn calculate_tsc_freq() -> crate::Result { - if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() { - return Err(crate::new_error!( - "Invariant TSC is not supported on this platform" - )); - } - let start = hyperlight_guest_tracing::invariant_tsc::read_tsc(); - let start_time = std::time::Instant::now(); - // Sleep for 1 second to get a good sample - std::thread::sleep(std::time::Duration::from_secs(1)); - let end = hyperlight_guest_tracing::invariant_tsc::read_tsc(); + /// Calculate the TSC frequency based on the RDTSC instruction on the host. + fn calculate_tsc_freq(&mut self) -> crate::Result<()> { + let (start, start_time) = match ( + self.guest_start_tsc.as_ref(), + self.guest_start_epoch.as_ref(), + ) { + (Some(start), Some(start_time)) => (*start, *start_time), + _ => { + // If the guest start TSC and time are not set, we use the current time and TSC. + // This is not ideal, but it allows us to calculate the TSC frequency without + // failing. + // This is a fallback mechanism to ensure that we can still calculate, however it + // should be noted that this may lead to inaccuracies in the TSC frequency. + // The start time should be already set before running the guest for each sandbox. + log::error!( + "Guest start TSC and time are not set. Calculating TSC frequency will use current time and TSC." + ); + ( + hyperlight_guest_tracing::invariant_tsc::read_tsc(), + std::time::Instant::now(), + ) + } + }; + let end_time = std::time::Instant::now(); + let end = hyperlight_guest_tracing::invariant_tsc::read_tsc(); + let elapsed = end_time.duration_since(start_time).as_secs_f64(); + let tsc_freq = ((end - start) as f64 / elapsed) as u64; + + log::info!("Calculated TSC frequency: {} Hz", tsc_freq); + self.tsc_freq = Some(tsc_freq); - Ok(((end - start) as f64 / elapsed) as u64) + Ok(()) } } diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 524c9d811..f71b728c2 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -232,8 +232,13 @@ pub(super) fn record_guest_trace_frame( .guest_start_tsc .as_ref() .map_or_else(|| 0, |c| *c); + // Convert cycles to microseconds based on the TSC frequency - let micros = cycles_spent as f64 / trace_info.tsc_freq as f64 * 1_000_000f64; + let tsc_freq = trace_info + .tsc_freq + .as_ref() + .ok_or_else(|| new_error!("TSC frequency not set in TraceInfo"))?; + let micros = cycles_spent as f64 / *tsc_freq as f64 * 1_000_000f64; // Convert to a Duration let guest_duration = std::time::Duration::from_micros(micros as u64); @@ -353,7 +358,6 @@ fn handle_outb_impl( .as_ref() .shared_mem .copy_to_slice(buffer, ptr as usize - SandboxMemoryLayout::BASE_ADDRESS) - // .read::((addr - SandboxMemoryLayout::BASE_ADDRESS as u64) as usize) .map_err(|e| { new_error!( "Failed to copy trace records from guest memory to host: {:?}", @@ -367,11 +371,24 @@ fn handle_outb_impl( { let trace_info = _hv.trace_info_as_mut(); - // Store the start guest cycles if not already set - // This is the `entrypoint` of the guest execution - // This should be set only once, at the start of the guest execution - if trace_info.guest_start_tsc.is_none() && !traces.is_empty() { - trace_info.guest_start_tsc = Some(traces[0].cycles); + + // Calculate the TSC frequency based on the current TSC reading + // This is done only once, when the first trace record is received + // Ideally, we should use a timer or a clock to measure the time elapsed, + // but that adds delays. + // To avoid that we store the TSC value and a timestamp right + // before starting the guest execution and then calculate the TSC frequency when + // the first trace record is received, based on the current TSC value and clock. + if trace_info.tsc_freq.is_none() { + trace_info.calculate_tsc_freq()?; + + // After the TSC frequency is calculated, we no longer need the value of TSC + // recorded on the host when the guest started, so we can set the guest_start_tsc field + // to store the TSC value recorded on the guest when the guest started executing. + // This is used to calculate the records timestamps relative to the first trace record. + if !traces.is_empty() { + trace_info.guest_start_tsc = Some(traces[0].cycles); + } } } From eb949d393f4e8e9d7e5690611912d238413a5610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Wed, 16 Jul 2025 14:47:54 +0300 Subject: [PATCH 20/21] [ci] fixup: Add checks for tracing features and run tracing tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- .github/workflows/dep_rust.yml | 8 ++++++++ Justfile | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/.github/workflows/dep_rust.yml b/.github/workflows/dep_rust.yml index f08958515..67c3e3e12 100644 --- a/.github/workflows/dep_rust.yml +++ b/.github/workflows/dep_rust.yml @@ -137,6 +137,7 @@ jobs: cargo check -p hyperlight-host --features crashdump cargo check -p hyperlight-host --features print_debug cargo check -p hyperlight-host --features gdb + cargo check -p hyperlight-host --features trace_guest,unwind_guest,mem_profile # without any features just test-compilation-no-default-features ${{ matrix.config }} @@ -169,6 +170,13 @@ jobs: RUST_LOG: debug run: just test-rust-crashdump ${{ matrix.config }} ${{ matrix.hypervisor == 'mshv' && 'mshv2' || ''}} + - name: Run Rust Tracing tests - linux + if: runner.os == 'Linux' + env: + CARGO_TERM_COLOR: always + RUST_LOG: debug + run: just test-rust-tracing ${{ matrix.config }} ${{ matrix.hypervisor == 'mshv3' && 'mshv3' || ''}} + - name: Download benchmarks from "latest" run: just bench-download ${{ runner.os }} ${{ matrix.hypervisor }} ${{ matrix.cpu}} dev-latest # compare to prerelease env: diff --git a/Justfile b/Justfile index 0c5b03a59..9e4c4208e 100644 --- a/Justfile +++ b/Justfile @@ -82,6 +82,7 @@ test-like-ci config=default-target hypervisor="kvm": cargo check -p hyperlight-host --features crashdump cargo check -p hyperlight-host --features print_debug cargo check -p hyperlight-host --features gdb + cargo check -p hyperlight-host --features trace_guest,unwind_guest,mem_profile @# without any driver (should fail to compile) just test-compilation-no-default-features {{config}} @@ -89,6 +90,9 @@ test-like-ci config=default-target hypervisor="kvm": @# test the crashdump feature just test-rust-crashdump {{config}} + @# test the tracing related features + just test-rust-tracing {{config}} {{ if hypervisor == "mshv3" {"mshv3"} else {""} }} + # runs all tests test target=default-target features="": (test-unit target features) (test-isolated target features) (test-integration "rust" target features) (test-integration "c" target features) (test-seccomp target features) @@ -141,6 +145,25 @@ test-rust-gdb-debugging target=default-target features="": test-rust-crashdump target=default-target features="": cargo test --profile={{ if target == "debug" { "dev" } else { target } }} {{ if features =="" {'--features crashdump'} else { "--features crashdump," + features } }} -- test_crashdump +# rust test for tracing +test-rust-tracing target=default-target features="": + # Run tests for the tracing guest and macro + cargo test -p hyperlight-guest-tracing --profile={{ if target == "debug" { "dev" } else { target } }} + cargo test -p hyperlight-guest-tracing-macro --profile={{ if target == "debug" { "dev" } else { target } }} + + # Prepare the tracing guest for testing + just build-rust-guests {{ target }} trace_guest + just move-rust-guests {{ target }} + # Run hello-world example with tracing enabled to get the trace output + # Capture the trace file path and print use it afterwards to run cargo run -p trace_dump + cargo run --profile={{ if target == "debug" { "dev" } else { target } }} --example hello-world --features {{ if features =="" {'trace_guest'} else { "trace_guest," + features } }} \ + | sed -n 's/.*Creating trace file at: \(.*\)/\1/p' \ + | xargs -I {} cargo run -p trace_dump ./{{ simpleguest_source }}/{{ target }}/simpleguest {} list_frames + + # Rebuild the tracing guest without the tracing feature + # This is to ensure that the tracing feature does not affect the other tests + just build-rust-guests {{ target }} + just move-rust-guests {{ target }} ################ ### LINTING #### From 11b40859990627a17272821c627153cb88f93cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= Date: Wed, 16 Jul 2025 15:54:02 +0300 Subject: [PATCH 21/21] fixup: fix unrelated clippy warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doru Blânzeanu --- src/hyperlight_host/src/mem/layout.rs | 6 ++++++ src/hyperlight_host/src/mem/mgr.rs | 3 +++ src/hyperlight_host/src/sandbox/initialized_multi_use.rs | 1 + 3 files changed, 10 insertions(+) diff --git a/src/hyperlight_host/src/mem/layout.rs b/src/hyperlight_host/src/mem/layout.rs index a8a1e5fc9..b453d90fb 100644 --- a/src/hyperlight_host/src/mem/layout.rs +++ b/src/hyperlight_host/src/mem/layout.rs @@ -224,18 +224,24 @@ impl SandboxMemoryLayout { pub(crate) const PML4_OFFSET: usize = 0x0000; /// The offset into the sandbox's memory where the Page Directory Pointer /// Table starts. + #[cfg(feature = "init-paging")] pub(super) const PDPT_OFFSET: usize = 0x1000; /// The offset into the sandbox's memory where the Page Directory starts. + #[cfg(feature = "init-paging")] pub(super) const PD_OFFSET: usize = 0x2000; /// The offset into the sandbox's memory where the Page Tables start. + #[cfg(feature = "init-paging")] pub(super) const PT_OFFSET: usize = 0x3000; /// The address (not the offset) to the start of the page directory + #[cfg(feature = "init-paging")] pub(super) const PD_GUEST_ADDRESS: usize = Self::BASE_ADDRESS + Self::PD_OFFSET; /// The address (not the offset) into sandbox memory where the Page /// Directory Pointer Table starts + #[cfg(feature = "init-paging")] pub(super) const PDPT_GUEST_ADDRESS: usize = Self::BASE_ADDRESS + Self::PDPT_OFFSET; /// The address (not the offset) into sandbox memory where the Page /// Tables start + #[cfg(feature = "init-paging")] pub(super) const PT_GUEST_ADDRESS: usize = Self::BASE_ADDRESS + Self::PT_OFFSET; /// The maximum amount of memory a single sandbox will be allowed. /// The addressable virtual memory with current paging setup is virtual address 0x0 - 0x40000000 (excl.), diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 3abdb11fe..4e4aa0112 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -340,6 +340,9 @@ impl SandboxMemoryManager { shared_mem.write_u64(offset, load_addr_u64)?; } + // The load method returns a LoadInfo which can also be a different type once the + // `unwind_guest` feature is enabled. + #[allow(clippy::let_unit_value)] let load_info = exe_info.load( load_addr.clone().try_into()?, &mut shared_mem.as_mut_slice()[layout.get_guest_code_offset()..], diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 7cab689d3..e1d805d41 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -162,6 +162,7 @@ impl MultiUseSandbox { /// Map the contents of a file into the guest at a particular address /// /// Returns the length of the mapping + #[allow(dead_code)] #[instrument(err(Debug), skip(self, _fp, _guest_base), parent = Span::current())] pub(crate) fn map_file_cow(&mut self, _fp: &Path, _guest_base: u64) -> Result { #[cfg(windows)]