Skip to main content

hyperlight_guest_bin/
lib.rs

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