From 15554a81078762dd8f82b931e7901dc357139a96 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/17] [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 e3f1c3497224dcf6b45ec619fd229fa298ea6e25 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/17] [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 449f9b17e..e4032f1d3 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 bc744c05c..23ac99ae8 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"] mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"] 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 90e91f496..42b533601 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 @@ -646,7 +656,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 { @@ -1142,6 +1157,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 cd0398854..f5d2b3c4f 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -57,6 +57,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}; @@ -280,6 +282,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 @@ -291,6 +296,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, @@ -301,6 +307,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)?; @@ -351,6 +358,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 @@ -674,7 +683,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 3da9786cd..f791e877f 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}; @@ -297,12 +299,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, @@ -312,6 +319,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()?; @@ -390,6 +398,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 @@ -570,7 +580,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 c6e33c84b..454b30ce4 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -40,6 +40,9 @@ pub(crate) mod uninitialized_evolve; /// 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 @@ -86,6 +89,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 a37f747e2..4c74bbf40 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(feature = "init-paging")] 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; @@ -78,6 +80,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()); @@ -151,6 +155,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 = { @@ -222,6 +227,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)) } @@ -238,6 +245,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)) } @@ -260,6 +269,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 a04e125c778a150fff193b0d46d683b782c08ae1 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/17] [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 90cb76573..50a3d15d1 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -345,7 +345,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); @@ -354,8 +354,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 e27f91ff2..6689dc410 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -309,12 +309,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 21dd43771f181cd26a4dfcbec23aeb86109d03e0 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/17] [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 23ac99ae8..85520a667 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"] mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"] diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index 42b533601..e49440e9e 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::{ @@ -525,6 +532,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( @@ -1071,6 +1091,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 f5d2b3c4f..e5f5039c8 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -41,6 +41,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; @@ -1034,6 +1036,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 f791e877f..5b65143f5 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}; @@ -946,6 +948,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 d3f8b6373f58e647b2aa7e43102cbdb801b26735 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/17] [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 e4032f1d3..156baeebb 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 85520a667..d63ab43b0 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"] mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"] diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index e49440e9e..2fcee67f0 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -1102,6 +1102,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 { @@ -1121,6 +1126,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}; @@ -1189,7 +1196,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 e5f5039c8..2b755807e 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -1048,6 +1048,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 5b65143f5..24cceeb90 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -960,6 +960,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 50a3d15d1..8d69ba2e6 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -347,7 +347,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); @@ -373,12 +373,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 454b30ce4..afacd3020 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -40,6 +40,8 @@ pub(crate) mod uninitialized_evolve; /// 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}; @@ -47,6 +49,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}; @@ -101,20 +105,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 6689dc410..1318f0eb4 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -80,6 +80,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 crate::sandbox_state::sandbox::UninitializedSandbox for UninitializedSandbox { @@ -250,8 +251,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(), @@ -259,7 +260,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()?; @@ -278,6 +279,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. @@ -308,7 +310,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 4c74bbf40..7d0ed0af6 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}; @@ -80,8 +81,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()); @@ -155,7 +155,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 = { @@ -214,6 +214,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 35e044bcaa661fa015f7d278d4a71a41c59eef21 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/17] 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 8d69ba2e6..538ad626c 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -319,6 +319,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 ac2bdc6b5..cabc83824 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 6a5dc641d2164b4d3dfefd38ab4366813b4fed84 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/17] 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 f48a196d6..c036ace1e 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 d63ab43b0..de8d76cbe 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"] mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"] mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"] 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 82e08d22965f105f7245f995b090b7a4fb78bc5d 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/17] 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 156baeebb..78154b402 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 ce5395223d51cb74ad63b40b6f409e88eb8a259b 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/17] 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 1e376fdd45ea291a12d89d34815d42d6691cd7de 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/17] [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 78154b402..4b091702c 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 ce5f50e2f5596ea15b1028900c0720781693d6b2 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/17] [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 de8d76cbe..0f3949628 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 2fcee67f0..4b6d91239 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)] @@ -532,7 +532,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 { @@ -739,6 +739,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 @@ -1092,7 +1098,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(), @@ -1107,6 +1113,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 2b755807e..7b4cca309 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -41,7 +41,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}; @@ -723,6 +723,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 @@ -1037,7 +1043,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 { @@ -1053,6 +1059,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 24cceeb90..be127f63b 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)] @@ -627,6 +627,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 @@ -949,7 +956,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 { @@ -965,6 +972,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 afacd3020..34bc0c08c 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -102,6 +102,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>, @@ -136,8 +147,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, @@ -153,6 +169,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 d325ddc0128227b72ca2953c7e0b47a24edf58ee 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/17] [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 97e90f3a6..431415752 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.clone(), @@ -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 e2072bf1f..fb5e9e9fe 100644 --- a/src/hyperlight_guest_bin/src/exceptions/handler.rs +++ b/src/hyperlight_guest_bin/src/exceptions/handler.rs @@ -62,6 +62,7 @@ type handler_t = fn(n: u64, info: *mut ExceptionInfo, ctx: *mut Context, pf_addr /// 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 dc547a199..5e388efef 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 d829e2a85..c763ceaba 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 { @@ -60,6 +61,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. @@ -71,6 +73,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) } } } @@ -79,6 +82,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 }; @@ -99,6 +103,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 c036ace1e..b2de7bcdd 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 b2ff4209eddf0426215d9edb3e4b5df9f08c0e7f 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/17] 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 66b06a587..cb2890592 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 a1edc4b10..b6d65c1bf 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 9738dba1166d80add7f66b1280a162c12839dfbc 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/17] 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 9218de5aeaedff4cc6ff968ae4cec95b4cccb5cb 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/17] 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 8d3558eb547feb2cf9129607d48ed8e4322d07ad 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/17] 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 66af14fac9e4fa744e4dc1363beac75f7484df50 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/17] 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 8df9d08ef..38d34e502 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -189,6 +189,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