diff --git a/Justfile b/Justfile index e6cf371bb..9ae49e3d2 100644 --- a/Justfile +++ b/Justfile @@ -143,7 +143,7 @@ like-ci config=default-target hypervisor="kvm": just bench-ci main {{config}} {{ if hypervisor == "mshv" { "mshv2" } else if hypervisor == "mshv3" { "mshv3" } else { "kvm" } }} # runs all tests -test target=default-target features="": (test-unit target features) (test-isolated target features) (test-integration "rust" target features) (test-integration "c" target features) (test-seccomp target features) +test target=default-target features="": (test-unit target features) (test-isolated target features) (test-integration "rust" target features) (test-integration "c" target features) (test-seccomp target features) (test-doc target features) # runs unit tests test-unit target=default-target features="": @@ -218,6 +218,8 @@ test-rust-tracing target=default-target features="": just build-rust-guests {{ target }} just move-rust-guests {{ target }} +test-doc target=default-target features="": + cargo test --profile={{ if target == "debug" { "dev" } else { target } }} {{ if features =="" {''} else { "--features " + features } }} --doc ################ ### LINTING #### ################ diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs b/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs index b726140db..b381da592 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs @@ -51,7 +51,7 @@ pub enum ParameterValue { String(String), /// bool Bool(bool), - /// Vec + /// `Vec` VecBytes(Vec), } @@ -75,7 +75,7 @@ pub enum ParameterType { String, /// bool Bool, - /// Vec + /// `Vec` VecBytes, } @@ -100,7 +100,7 @@ pub enum ReturnValue { Bool(bool), /// () Void(()), - /// Vec + /// `Vec` VecBytes(Vec), } @@ -128,7 +128,7 @@ pub enum ReturnType { Bool, /// () Void, - /// Vec + /// `Vec` VecBytes, } diff --git a/src/hyperlight_host/src/func/ret_type.rs b/src/hyperlight_host/src/func/ret_type.rs index e4b69e7bc..89fe1481f 100644 --- a/src/hyperlight_host/src/func/ret_type.rs +++ b/src/hyperlight_host/src/func/ret_type.rs @@ -32,12 +32,12 @@ pub trait SupportedReturnType: Sized + Clone + Send + Sync + 'static { fn from_value(value: ReturnValue) -> Result; } -/// A trait to handle either a SupportedReturnType or a Result +/// A trait to handle either a [`SupportedReturnType`] or a [`Result`] pub trait ResultType { /// The return type of the supported return value type ReturnType: SupportedReturnType; - /// Convert the return type into a Result + /// Convert the return type into a `Result` fn into_result(self) -> Result; } diff --git a/src/hyperlight_host/src/func/utils.rs b/src/hyperlight_host/src/func/utils.rs index f06c35a3c..6e92ca041 100644 --- a/src/hyperlight_host/src/func/utils.rs +++ b/src/hyperlight_host/src/func/utils.rs @@ -18,10 +18,9 @@ limitations under the License. /// up to 32 parameters. This is useful to implement traits on functions /// for may parameter tuples. /// -/// Usage: -/// ```rust -/// use hyperlight_host::func::for_each_tuple; +/// This is an internal utility macro used within the func module. /// +/// ```ignore /// macro_rules! my_macro { /// ([$count:expr] ($($name:ident: $type:ident),*)) => { /// // $count is the arity of the tuple @@ -30,7 +29,7 @@ limitations under the License. /// }; /// } /// -/// for_each_tuple!(impl_host_function); +/// for_each_tuple!(my_macro); /// ``` macro_rules! for_each_tuple { (@ diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 7e6a69a9c..9a32a9ecc 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -14,8 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ #![warn(dead_code, missing_docs, unused_mut)] -//! This crate contains an SDK that is used to execute specially- -// compiled binaries within a very lightweight hypervisor environment. +//! Hyperlight host runtime for executing guest code in lightweight virtual machines. +//! +//! This crate provides the host-side runtime for Hyperlight, enabling safe execution +//! of untrusted guest code within micro virtual machines with minimal overhead. +//! The runtime manages sandbox creation, guest function calls, memory isolation, +//! and host-guest communication. +//! +//! The primary entry points are [`UninitializedSandbox`] for initial setup and +//! [`MultiUseSandbox`] for executing guest functions. +//! +//! ## Guest Requirements +//! +//! Hyperlight requires specially compiled guest binaries and cannot run regular +//! container images or executables. Guests must be built using either the Rust +//! API ([`hyperlight_guest`] with optional use of [`hyperlight_guest_bin`]), +//! or with the C API (`hyperlight_guest_capi`). +//! +//! [`hyperlight_guest`]: https://docs.rs/hyperlight_guest +//! [`hyperlight_guest_bin`]: https://docs.rs/hyperlight_guest_bin +//! #![cfg_attr(not(any(test, debug_assertions)), warn(clippy::panic))] #![cfg_attr(not(any(test, debug_assertions)), warn(clippy::expect_used))] diff --git a/src/hyperlight_host/src/mem/shared_mem.rs b/src/hyperlight_host/src/mem/shared_mem.rs index e61e1b7ee..bd39a73fd 100644 --- a/src/hyperlight_host/src/mem/shared_mem.rs +++ b/src/hyperlight_host/src/mem/shared_mem.rs @@ -183,11 +183,11 @@ unsafe impl Send for GuestSharedMemory {} /// /// Unfortunately, there appears to be no way to do this with defined /// behaviour in present Rust (see -/// e.g. https://github.com/rust-lang/unsafe-code-guidelines/issues/152). +/// e.g. ). /// Rust does not yet have its own defined memory model, but in the /// interim, it is widely treated as inheriting the current C/C++ /// memory models. The most immediate problem is that regardless of -/// anything else, under those memory models [1, p. 17-18; 2, p. 88], +/// anything else, under those memory models \[1, p. 17-18; 2, p. 88\], /// /// > The execution of a program contains a _data race_ if it /// > contains two [C++23: "potentially concurrent"] conflicting @@ -205,7 +205,7 @@ unsafe impl Send for GuestSharedMemory {} /// Despite Rust's de jure inheritance of the C memory model at the /// present time, the compiler in many cases de facto adheres to LLVM /// semantics, so it is worthwhile to consider what LLVM does in this -/// case as well. According to the the LangRef [3] memory model, +/// case as well. According to the the LangRef \[3\] memory model, /// loads which are involved in a race that includes at least one /// non-atomic access (whether the load or a store) return `undef`, /// making them roughly equivalent to reading uninitialized @@ -213,20 +213,20 @@ unsafe impl Send for GuestSharedMemory {} /// /// Considering a different direction, recent C++ papers have seemed /// to lean towards using `volatile` for similar use cases. For -/// example, in P1152R0 [4], JF Bastien notes that +/// example, in P1152R0 \[4\], JF Bastien notes that /// /// > We’ve shown that volatile is purposely defined to denote /// > external modifications. This happens for: /// > - Shared memory with untrusted code, where volatile is the /// > right way to avoid time-of-check time-of-use (ToCToU) -/// > races which lead to security bugs such as [PWN2OWN] and -/// > [XENXSA155]. +/// > races which lead to security bugs such as \[PWN2OWN\] and +/// > \[XENXSA155\]. /// /// Unfortunately, although this paper was adopted for C++20 (and, /// sadly, mostly un-adopted for C++23, although that does not concern /// us), the paper did not actually redefine volatile accesses or data /// races to prevent volatile accesses from racing with other accesses -/// and causing undefined behaviour. P1382R1 [5] would have amended +/// and causing undefined behaviour. P1382R1 \[5\] would have amended /// the wording of the data race definition to specifically exclude /// volatile, but, unfortunately, despite receiving a /// generally-positive reception at its first WG21 meeting more than @@ -272,8 +272,8 @@ unsafe impl Send for GuestSharedMemory {} /// the guest in this case. Unfortunately, while those operations are /// defined in LLVM, they are not presently exposed to Rust. While /// atomic fences that are not associated with memory accesses -/// (std::sync::atomic::fence) might at first glance seem to help with -/// this problem, they unfortunately do not [6]: +/// ([`std::sync::atomic::fence`]) might at first glance seem to help with +/// this problem, they unfortunately do not \[6\]: /// /// > A fence ‘A’ which has (at least) Release ordering semantics, /// > synchronizes with a fence ‘B’ with (at least) Acquire @@ -289,12 +289,12 @@ unsafe impl Send for GuestSharedMemory {} /// fence on a vmenter/vmexit between data being read and written. /// This is unsafe (not guaranteed in the type system)! /// -/// [1] N3047 C23 Working Draft. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3047.pdf -/// [2] N4950 C++23 Working Draft. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf -/// [3] LLVM Language Reference Manual, Memory Model for Concurrent Operations. https://llvm.org/docs/LangRef.html#memmodel -/// [4] P1152R0: Deprecating `volatile`. JF Bastien. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1152r0.html -/// [5] P1382R1: `volatile_load` and `volatile_store`. JF Bastien, Paul McKenney, Jeffrey Yasskin, and the indefatigable TBD. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1382r1.pdf -/// [6] Documentation for std::sync::atomic::fence. https://doc.rust-lang.org/std/sync/atomic/fn.fence.html +/// \[1\] N3047 C23 Working Draft. +/// \[2\] N4950 C++23 Working Draft. +/// \[3\] LLVM Language Reference Manual, Memory Model for Concurrent Operations. +/// \[4\] P1152R0: Deprecating `volatile`. JF Bastien. +/// \[5\] P1382R1: `volatile_load` and `volatile_store`. JF Bastien, Paul McKenney, Jeffrey Yasskin, and the indefatigable TBD. +/// \[6\] Documentation for std::sync::atomic::fence. #[derive(Clone, Debug)] pub struct HostSharedMemory { region: Arc, diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 6b925f264..7692a4379 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -49,14 +49,10 @@ use crate::{HyperlightError, Result, log_then_return}; /// Global counter for assigning unique IDs to sandboxes static SANDBOX_ID_COUNTER: AtomicU64 = AtomicU64::new(0); -/// A sandbox that supports being used Multiple times. -/// The implication of being used multiple times is two-fold: +/// A fully initialized sandbox that can execute guest functions multiple times. /// -/// 1. The sandbox can be used to call guest functions multiple times, each time a -/// guest function is called the state of the sandbox is reset to the state it was in before the call was made. -/// -/// 2. A MultiUseGuestCallContext can be created from the sandbox and used to make multiple guest function calls to the Sandbox. -/// in this case the state of the sandbox is not reset until the context is finished and the `MultiUseSandbox` is returned. +/// Guest functions can be called repeatedly while maintaining state between calls. +/// The sandbox supports creating snapshots and restoring to previous states. pub struct MultiUseSandbox { /// Unique identifier for this sandbox instance id: u64, @@ -94,7 +90,29 @@ impl MultiUseSandbox { } } - /// Create a snapshot of the current state of the sandbox's memory. + /// Creates a snapshot of the sandbox's current memory state. + /// + /// The snapshot is tied to this specific sandbox instance and can only be + /// restored to the same sandbox it was created from. + /// + /// # Examples + /// + /// ```no_run + /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # fn example() -> Result<(), Box> { + /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( + /// GuestBinary::FilePath("guest.bin".into()), + /// None + /// )?.evolve()?; + /// + /// // Modify sandbox state + /// sandbox.call_guest_function_by_name::("SetValue", 42)?; + /// + /// // Create snapshot belonging to this sandbox + /// let snapshot = sandbox.snapshot()?; + /// # Ok(()) + /// # } + /// ``` #[instrument(err(Debug), skip_all, parent = Span::current())] pub fn snapshot(&mut self) -> Result { let mapped_regions_iter = self.vm.get_mapped_regions(); @@ -108,7 +126,37 @@ impl MultiUseSandbox { }) } - /// Restore the sandbox's memory to the state captured in the given snapshot. + /// Restores the sandbox's memory to a previously captured snapshot state. + /// + /// The snapshot must have been created from this same sandbox instance. + /// Attempting to restore a snapshot from a different sandbox will return + /// a [`SnapshotSandboxMismatch`](crate::HyperlightError::SnapshotSandboxMismatch) error. + /// + /// # Examples + /// + /// ```no_run + /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # fn example() -> Result<(), Box> { + /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( + /// GuestBinary::FilePath("guest.bin".into()), + /// None + /// )?.evolve()?; + /// + /// // Take initial snapshot from this sandbox + /// let snapshot = sandbox.snapshot()?; + /// + /// // Modify sandbox state + /// sandbox.call_guest_function_by_name::("SetValue", 100)?; + /// let value: i32 = sandbox.call_guest_function_by_name("GetValue", ())?; + /// assert_eq!(value, 100); + /// + /// // Restore to previous state (same sandbox) + /// sandbox.restore(&snapshot)?; + /// let restored_value: i32 = sandbox.call_guest_function_by_name("GetValue", ())?; + /// assert_eq!(restored_value, 0); // Back to initial state + /// # Ok(()) + /// # } + /// ``` #[instrument(err(Debug), skip_all, parent = Span::current())] pub fn restore(&mut self, snapshot: &Snapshot) -> Result<()> { if self.id != snapshot.inner.sandbox_id() { @@ -136,8 +184,37 @@ impl MultiUseSandbox { Ok(()) } - /// Call a guest function by name, with the given return type and arguments. - /// The changes made to the sandbox are persisted + /// Calls a guest function by name with the specified arguments. + /// + /// Changes made to the sandbox during execution are persisted. + /// + /// # Examples + /// + /// ```no_run + /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # fn example() -> Result<(), Box> { + /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( + /// GuestBinary::FilePath("guest.bin".into()), + /// None + /// )?.evolve()?; + /// + /// // Call function with no arguments + /// let result: i32 = sandbox.call_guest_function_by_name("GetCounter", ())?; + /// + /// // Call function with single argument + /// let doubled: i32 = sandbox.call_guest_function_by_name("Double", 21)?; + /// assert_eq!(doubled, 42); + /// + /// // Call function with multiple arguments + /// let sum: i32 = sandbox.call_guest_function_by_name("Add", (10, 32))?; + /// assert_eq!(sum, 42); + /// + /// // Call function returning string + /// let message: String = sandbox.call_guest_function_by_name("Echo", "Hello, World!".to_string())?; + /// assert_eq!(message, "Hello, World!"); + /// # Ok(()) + /// # } + /// ``` #[instrument(err(Debug), skip(self, args), parent = Span::current())] pub fn call_guest_function_by_name( &mut self, @@ -154,19 +231,16 @@ impl MultiUseSandbox { }) } - /// Map a region of host memory into the sandbox. - /// - /// Depending on the host platform, there are likely alignment - /// requirements of at least one page for base and len. + /// Maps a region of host memory into the sandbox address space. /// - /// `rgn.region_type` is ignored, since guest PTEs are not created - /// for the new memory. + /// The base address and length must meet platform alignment requirements + /// (typically page-aligned). The `region_type` field is ignored as guest + /// page table entries are not created. /// /// # 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 - /// sandbox or due to a state rollback + /// + /// The caller must ensure the host memory region remains valid and unmodified + /// for the lifetime of `self`. #[instrument(err(Debug), skip(self, rgn), parent = Span::current())] pub unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> { if rgn.flags.contains(MemoryRegionFlags::STACK_GUARD) { @@ -187,7 +261,7 @@ impl MultiUseSandbox { /// Map the contents of a file into the guest at a particular address /// - /// Returns the length of the mapping + /// Returns the length of the mapping in bytes. #[allow(dead_code)] #[instrument(err(Debug), skip(self, _fp, _guest_base), parent = Span::current())] pub fn map_file_cow(&mut self, _fp: &Path, _guest_base: u64) -> Result { @@ -225,7 +299,9 @@ impl MultiUseSandbox { } } - /// This function is kept here for fuzz testing the parameter and return types + /// Calls a guest function with type-erased parameters and return values. + /// + /// This function is used for fuzz testing parameter and return type handling. #[cfg(feature = "fuzzing")] #[instrument(err(Debug), skip(self, args), parent = Span::current())] pub fn call_type_erased_guest_function_by_name( @@ -281,8 +357,35 @@ impl MultiUseSandbox { res } - /// Get a handle to the interrupt handler for this sandbox, - /// capable of interrupting guest execution. + /// Returns a handle for interrupting guest execution. + /// + /// # Examples + /// + /// ```no_run + /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use std::thread; + /// # use std::time::Duration; + /// # fn example() -> Result<(), Box> { + /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( + /// GuestBinary::FilePath("guest.bin".into()), + /// None + /// )?.evolve()?; + /// + /// // Get interrupt handle before starting long-running operation + /// let interrupt_handle = sandbox.interrupt_handle(); + /// + /// // Spawn thread to interrupt after timeout + /// let handle_clone = interrupt_handle.clone(); + /// thread::spawn(move || { + /// thread::sleep(Duration::from_secs(5)); + /// handle_clone.kill(); + /// }); + /// + /// // This call may be interrupted by the spawned thread + /// let result = sandbox.call_guest_function_by_name::("LongRunningFunction", ()); + /// # Ok(()) + /// # } + /// ``` pub fn interrupt_handle(&self) -> Arc { self.vm.interrupt_handle() } diff --git a/src/hyperlight_host/src/sandbox/snapshot.rs b/src/hyperlight_host/src/sandbox/snapshot.rs index d91f52437..e9e996e7a 100644 --- a/src/hyperlight_host/src/sandbox/snapshot.rs +++ b/src/hyperlight_host/src/sandbox/snapshot.rs @@ -19,6 +19,6 @@ use crate::mem::shared_mem_snapshot::SharedMemorySnapshot; /// A snapshot capturing the state of the memory in a `MultiUseSandbox`. #[derive(Clone)] pub struct Snapshot { - /// TODO: Use Arc + // TODO: Use Arc pub(crate) inner: SharedMemorySnapshot, } diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index cfac5979c..65e9d9a80 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -62,13 +62,17 @@ pub(crate) struct SandboxRuntimeConfig { pub(crate) guest_core_dump: bool, } -/// A preliminary `Sandbox`, not yet ready to execute guest code. +/// A preliminary sandbox that represents allocated memory and registered host functions, +/// but has not yet created the underlying virtual machine. /// -/// Prior to initializing a full-fledged `Sandbox`, you must create one of -/// these `UninitializedSandbox`es with the `new` function, register all the -/// host-implemented functions you need to be available to the guest, then -/// call `evolve` to transform your -/// `UninitializedSandbox` into an initialized `Sandbox`. +/// This struct holds the configuration and setup needed for a sandbox without actually +/// creating the VM. It allows you to: +/// - Set up memory layout and load guest binary data +/// - Register host functions that will be available to the guest +/// - Configure sandbox settings before VM creation +/// +/// The virtual machine is not created until you call [`evolve`](Self::evolve) to transform +/// this into an initialized [`MultiUseSandbox`]. pub struct UninitializedSandbox { /// Registered host functions pub(crate) host_funcs: Arc>, @@ -90,7 +94,11 @@ impl Debug for UninitializedSandbox { } impl UninitializedSandbox { - /// Evolve `self` to a `MultiUseSandbox` without any additional metadata. + /// Creates and initializes the virtual machine, transforming this into a ready-to-use sandbox. + /// + /// This method consumes the `UninitializedSandbox` and performs the final initialization + /// steps to create the underlying virtual machine. Once evolved, the resulting + /// [`MultiUseSandbox`] can execute guest code and handle function calls. #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub fn evolve(self) -> Result { evolve_impl_multi_use(self) @@ -125,7 +133,10 @@ impl<'a> From<&'a [u8]> for GuestBlob<'a> { } } -/// A `GuestEnvironment` is a structure that contains the guest binary and an optional GuestBinary. +/// Container for a guest binary and optional initialization data. +/// +/// This struct combines a guest binary (either from a file or memory buffer) with +/// optional data that will be available to the guest during execution. #[derive(Debug)] pub struct GuestEnvironment<'a, 'b> { /// The guest binary, which can be a file path or a buffer. @@ -154,13 +165,12 @@ impl<'a> From> for GuestEnvironment<'a, '_> { } impl UninitializedSandbox { - /// Create a new sandbox configured to run the binary at path - /// `bin_path`. + /// Creates a new uninitialized sandbox for the given guest environment. /// - /// The instrument attribute is used to generate tracing spans and also to emit an error should the Result be an error. - /// The skip attribute is used to skip the guest binary from being printed in the tracing span. - /// The name attribute is used to name the tracing span. - /// The err attribute is used to emit an error should the Result be an error, it uses the std::`fmt::Debug trait` to print the error. + /// The guest binary can be provided as either a file path or memory buffer. + /// An optional configuration can customize memory sizes and sandbox settings. + /// After creation, register host functions using [`register`](Self::register) + /// before calling [`evolve`](Self::evolve) to complete initialization and create the VM. #[instrument( err(Debug), skip(env), @@ -294,14 +304,15 @@ impl UninitializedSandbox { SandboxMemoryManager::load_guest_binary_into_memory(cfg, exe_info, guest_blob) } - /// Set the max log level to be used by the guest. - /// If this is not set then the log level will be determined by parsing the RUST_LOG environment variable. - /// If the RUST_LOG environment variable is not set then the max log level will be set to `LevelFilter::Error`. + /// Sets the maximum log level for guest code execution. + /// + /// If not set, the log level is determined by the `RUST_LOG` environment variable, + /// defaulting to [`LevelFilter::Error`] if unset. pub fn set_max_guest_log_level(&mut self, log_level: LevelFilter) { self.max_guest_log_level = Some(log_level); } - /// Register a host function with the given name in the sandbox. + /// Registers a host function that the guest can call. pub fn register( &mut self, name: impl AsRef, @@ -310,9 +321,10 @@ impl UninitializedSandbox { register_host_function(host_func, self, name.as_ref(), None) } - /// Register the host function with the given name in the sandbox. - /// Unlike `register`, this variant takes a list of extra syscalls that will - /// allowed during the execution of the function handler. + /// Registers a host function with additional allowed syscalls during execution. + /// + /// Unlike [`register`](Self::register), this variant allows specifying extra syscalls + /// that will be permitted when the function handler runs. #[cfg(all(feature = "seccomp", target_os = "linux"))] pub fn register_with_extra_allowed_syscalls< Args: ParameterTuple, @@ -327,10 +339,11 @@ impl UninitializedSandbox { register_host_function(host_func, self, name.as_ref(), Some(extra_allowed_syscalls)) } - /// Register a host function named "HostPrint" that will be called by the guest - /// when it wants to print to the console. - /// The "HostPrint" host function is kind of special, as we expect it to have the - /// `FnMut(String) -> i32` signature. + /// Registers the special "HostPrint" function for guest printing. + /// + /// This overrides the default behavior of writing to stdout. + /// The function expects the signature `FnMut(String) -> i32` + /// and will be called when the guest wants to print output. pub fn register_print( &mut self, print_func: impl Into>, @@ -348,12 +361,10 @@ impl UninitializedSandbox { Ok(()) } - /// Register a host function named "HostPrint" that will be called by the guest - /// when it wants to print to the console. - /// The "HostPrint" host function is kind of special, as we expect it to have the - /// `FnMut(String) -> i32` signature. - /// Unlike `register_print`, this variant takes a list of extra syscalls that will - /// allowed during the execution of the function handler. + /// Registers the "HostPrint" function with additional allowed syscalls. + /// + /// Like [`register_print`](Self::register_print), but allows specifying extra syscalls + /// that will be permitted during function execution. #[cfg(all(feature = "seccomp", target_os = "linux"))] pub fn register_print_with_extra_allowed_syscalls( &mut self,