Skip to main content

hyperlight_guest_bin/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3#![no_std]
4
5// === Dependencies ===
6extern crate alloc;
7
8use core::fmt::Write;
9
10use arch::dispatch::dispatch_function;
11use buddy_system_allocator::LockedHeap;
12use guest_function::register::GuestFunctionRegister;
13use guest_logger::init_logger;
14use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
15use hyperlight_common::log_level::GuestLogFilter;
16use hyperlight_common::mem::HyperlightPEB;
17#[cfg(feature = "mem_profile")]
18use hyperlight_common::outb::OutBAction;
19use hyperlight_guest::exit::write_abort;
20use hyperlight_guest::guest_handle::handle::GuestHandle;
21
22// === Modules ===
23#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/mod.rs")]
24#[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")]
25mod arch;
26// temporarily expose the architecture-specific exception interface;
27// this should be replaced with something a bit more abstract in the
28// near future.
29#[cfg(target_arch = "x86_64")]
30pub mod exception;
31pub mod guest_function {
32    pub(super) mod call;
33    pub mod definition;
34    pub mod register;
35}
36
37pub mod error;
38pub mod guest_logger;
39pub mod host_comm;
40pub mod memory;
41pub mod paging;
42
43/// Bridge between picolibc's POSIX expectations and the Hyperlight host.
44/// cbindgen:ignore
45#[cfg(feature = "libc")]
46mod libc_stubs;
47
48/// Shared initialisation code used by multiple architectures
49mod init;
50
51/// Re-export the libc bindings from hyperlight-libc when the libc feature is enabled.
52#[cfg(feature = "libc")]
53pub use hyperlight_libc as libc;
54
55// Globals
56#[cfg(all(feature = "mem_profile", target_arch = "x86_64"))]
57struct ProfiledLockedHeap<const ORDER: usize>(LockedHeap<ORDER>);
58#[cfg(all(feature = "mem_profile", target_arch = "x86_64"))]
59unsafe impl<const ORDER: usize> alloc::alloc::GlobalAlloc for ProfiledLockedHeap<ORDER> {
60    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
61        let addr = unsafe { self.0.alloc(layout) };
62        unsafe {
63            core::arch::asm!("out dx, al",
64                in("dx") OutBAction::TraceMemoryAlloc as u16,
65                in("rax") layout.size() as u64,
66                in("rcx") addr as u64);
67        }
68        addr
69    }
70    unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
71        unsafe {
72            core::arch::asm!("out dx, al",
73                in("dx") OutBAction::TraceMemoryFree as u16,
74                in("rax") layout.size() as u64,
75                in("rcx") ptr as u64);
76            self.0.dealloc(ptr, layout)
77        }
78    }
79    unsafe fn alloc_zeroed(&self, layout: core::alloc::Layout) -> *mut u8 {
80        let addr = unsafe { self.0.alloc_zeroed(layout) };
81        unsafe {
82            core::arch::asm!("out dx, al",
83                in("dx") OutBAction::TraceMemoryAlloc as u16,
84                in("rax") layout.size() as u64,
85                in("rcx") addr as u64);
86        }
87        addr
88    }
89    unsafe fn realloc(
90        &self,
91        ptr: *mut u8,
92        layout: core::alloc::Layout,
93        new_size: usize,
94    ) -> *mut u8 {
95        let new_ptr = unsafe { self.0.realloc(ptr, layout, new_size) };
96        unsafe {
97            core::arch::asm!("out dx, al",
98                in("dx") OutBAction::TraceMemoryFree as u16,
99                in("rax") layout.size() as u64,
100                in("rcx") ptr);
101            core::arch::asm!("out dx, al",
102                in("dx") OutBAction::TraceMemoryAlloc as u16,
103                in("rax") new_size as u64,
104                in("rcx") new_ptr);
105        }
106        new_ptr
107    }
108}
109
110// === Globals ===
111#[cfg(not(all(feature = "mem_profile", target_arch = "x86_64")))]
112#[global_allocator]
113pub(crate) static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::empty();
114#[cfg(all(feature = "mem_profile", target_arch = "x86_64"))]
115#[global_allocator]
116pub(crate) static HEAP_ALLOCATOR: ProfiledLockedHeap<32> =
117    ProfiledLockedHeap(LockedHeap::<32>::empty());
118
119pub static mut GUEST_HANDLE: GuestHandle = GuestHandle::new();
120pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister<GuestFunc> =
121    GuestFunctionRegister::new();
122
123const VERSION_STR: &str = env!("CARGO_PKG_VERSION");
124
125// Embed the hyperlight-guest-bin crate version as a proper ELF note so the
126// host can verify ABI compatibility at load time.
127#[used]
128#[unsafe(link_section = ".note.hyperlight-version")]
129static HYPERLIGHT_VERSION_NOTE: hyperlight_common::version_note::ElfNote<
130    {
131        hyperlight_common::version_note::padded_name_size(
132            hyperlight_common::version_note::HYPERLIGHT_NOTE_NAME.len() + 1,
133        )
134    },
135    { hyperlight_common::version_note::padded_desc_size(VERSION_STR.len() + 1) },
136> = hyperlight_common::version_note::ElfNote::new(
137    hyperlight_common::version_note::HYPERLIGHT_NOTE_NAME,
138    VERSION_STR,
139    hyperlight_common::version_note::HYPERLIGHT_NOTE_TYPE,
140);
141
142/// The size of one page in the host OS, which may have some impacts
143/// on how buffers for host consumption should be aligned. Code only
144/// working with the guest page tables should use
145/// [`hyperlight_common::vm::PAGE_SIZE`] instead.
146pub static mut OS_PAGE_SIZE: u32 = 0;
147
148// === Panic Handler ===
149// It looks like rust-analyzer doesn't correctly manage no_std crates,
150// and so it displays an error about a duplicate panic_handler.
151// See more here: https://github.com/rust-lang/rust-analyzer/issues/4490
152// The cfg_attr attribute is used to avoid clippy failures as test pulls in std which pulls in a panic handler
153#[cfg_attr(not(test), panic_handler)]
154#[allow(clippy::panic)]
155// to satisfy the clippy when cfg == test
156#[allow(dead_code)]
157fn panic(info: &core::panic::PanicInfo) -> ! {
158    _panic_handler(info)
159}
160
161/// A writer that sends all output to the hyperlight host
162/// using output ports. This allows us to not impose a
163/// buffering limit on error message size on the guest end,
164/// though one exists for the host.
165struct HyperlightAbortWriter;
166impl core::fmt::Write for HyperlightAbortWriter {
167    fn write_str(&mut self, s: &str) -> core::fmt::Result {
168        write_abort(s.as_bytes());
169        Ok(())
170    }
171}
172
173#[inline(always)]
174fn _panic_handler(info: &core::panic::PanicInfo) -> ! {
175    let mut w = HyperlightAbortWriter;
176
177    // begin abort sequence by writing the error code
178    write_abort(&[ErrorCode::UnknownError as u8]);
179
180    let write_res = write!(w, "{}", info);
181    if write_res.is_err() {
182        write_abort("panic: message format failed".as_bytes());
183    }
184
185    // write abort terminator to finish the abort
186    // and signal to the host that the message can now be read
187    write_abort(&[0xFF]);
188    unreachable!();
189}
190
191// === Entrypoint ===
192
193unsafe extern "C" {
194    fn hyperlight_main();
195
196    #[cfg(feature = "libc")]
197    fn srand(seed: u32);
198}
199
200#[cfg(feature = "libc")]
201pub(crate) fn refresh_libc_rng() {
202    let seed_ptr = hyperlight_guest::layout::libc_rng_seed_gva();
203    // SAFETY: The host maps this aligned u64 scratch slot for the guest's
204    // lifetime and writes it only while the guest is stopped.
205    let request = unsafe { seed_ptr.read_volatile() };
206    if request >> 32 != 0 {
207        // SAFETY: The scratch slot has the validity and exclusivity described
208        // above. The libc feature provides srand with a u32 seed.
209        unsafe {
210            srand(request as u32);
211            // clear request u32 and zero u32 seed
212            seed_ptr.write_volatile(0u64);
213        }
214    }
215}
216
217#[tracing::instrument(skip_all, parent = tracing::Span::current(), level= "Trace")]
218extern "C" fn hyperlight_main_default() {
219    // no-op
220}
221
222core::arch::global_asm!(
223    ".weak hyperlight_main",
224    ".set hyperlight_main, {}",
225    sym hyperlight_main_default,
226);
227
228/// Architecture-nonspecific initialisation: set up the heap,
229/// coordinate some addresses and configuration with the host, and run
230/// user initialisation
231pub(crate) extern "C" fn generic_init(
232    peb_address: u64,
233    _seed: u64,
234    ops: u64,
235    max_log_level: u64,
236) -> u64 {
237    unsafe {
238        GUEST_HANDLE = GuestHandle::init(peb_address as *mut HyperlightPEB);
239        #[allow(static_mut_refs)]
240        let peb_ptr = GUEST_HANDLE.peb().unwrap();
241
242        let heap_start = (*peb_ptr).guest_heap.ptr as usize;
243        let heap_size = (*peb_ptr).guest_heap.size as usize;
244        #[cfg(not(all(feature = "mem_profile", target_arch = "x86_64")))]
245        let heap_allocator = &HEAP_ALLOCATOR;
246        #[cfg(all(feature = "mem_profile", target_arch = "x86_64"))]
247        let heap_allocator = &HEAP_ALLOCATOR.0;
248        heap_allocator
249            .try_lock()
250            .expect("Failed to access HEAP_ALLOCATOR")
251            .init(heap_start, heap_size);
252        peb_ptr
253    };
254
255    // Save the guest start TSC for tracing
256    #[cfg(feature = "trace_guest")]
257    let guest_start_tsc = hyperlight_guest_tracing::invariant_tsc::read_tsc();
258
259    #[cfg(feature = "libc")]
260    unsafe {
261        let srand_seed = (((peb_address << 8) ^ (_seed >> 4)) >> 32) as u32;
262        srand(srand_seed);
263    }
264
265    unsafe {
266        OS_PAGE_SIZE = ops as u32;
267    }
268
269    // set up the logger
270    let guest_log_level_filter =
271        GuestLogFilter::try_from(max_log_level).expect("Invalid log level");
272    init_logger(guest_log_level_filter.into());
273
274    // It is important that all the tracing events are produced after the tracing is initialized.
275    #[cfg(feature = "trace_guest")]
276    if guest_log_level_filter != GuestLogFilter::Off {
277        hyperlight_guest_tracing::init_guest_tracing(
278            guest_start_tsc,
279            guest_log_level_filter.into(),
280        );
281    }
282
283    // Open a span to partly capture the initialization of the guest.
284    // This is done here because the tracing subscriber is initialized and the guest is in a
285    // well-known state
286    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
287    let _entered = tracing::span!(tracing::Level::INFO, "generic_init").entered();
288
289    #[cfg(feature = "macros")]
290    for registration in __private::GUEST_FUNCTION_INIT {
291        registration();
292    }
293
294    unsafe {
295        hyperlight_main();
296    }
297
298    // All this tracing logic shall be done right before the call to `hlt` which is done after this
299    // function returns
300    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
301    {
302        // NOTE: This is necessary to avoid closing the span twice. Flush closes all the open
303        // spans, when preparing to close a guest function call context.
304        // It is not mandatory, though, but avoids a warning on the host that alerts a spans
305        // that has not been opened but is being closed.
306        _entered.exit();
307
308        // Ensure that any tracing output from the initialisation phase is
309        // flushed to the host, if necessary.
310        hyperlight_guest_tracing::flush();
311    }
312
313    dispatch_function as *const () as usize as u64
314}
315
316#[cfg(feature = "macros")]
317#[doc(hidden)]
318pub mod __private {
319    pub use alloc::vec::Vec;
320
321    pub use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall;
322    pub use hyperlight_common::func::ResultType;
323    pub use hyperlight_guest::error::HyperlightGuestError;
324    pub use linkme;
325
326    #[linkme::distributed_slice]
327    pub static GUEST_FUNCTION_INIT: [fn()];
328
329    pub trait FromResult {
330        type Output;
331        fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self;
332    }
333
334    use alloc::string::String;
335
336    use hyperlight_common::for_each_return_type;
337
338    macro_rules! impl_maybe_unwrap {
339        ($ty:ty, $enum:ident) => {
340            impl FromResult for $ty {
341                type Output = Self;
342                fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
343                    // Unwrapping here is fine as this would only run in a guest
344                    // and not in the host.
345                    res.unwrap()
346                }
347            }
348
349            impl FromResult for Result<$ty, HyperlightGuestError> {
350                type Output = $ty;
351                fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
352                    res
353                }
354            }
355        };
356    }
357
358    for_each_return_type!(impl_maybe_unwrap);
359}
360
361#[cfg(feature = "macros")]
362pub use hyperlight_guest_macro::{dispatch, guest_function, host_function, main};
363
364pub use crate::guest_function::definition::GuestFunc;