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")]
37mod arch;
38// temporarily expose the architecture-specific exception interface;
39// this should be replaced with something a bit more abstract in the
40// near future.
41#[cfg(target_arch = "x86_64")]
42pub mod exception;
43pub mod guest_function {
44    pub(super) mod call;
45    pub mod definition;
46    pub mod register;
47}
48
49pub mod guest_logger;
50pub mod host_comm;
51pub mod memory;
52pub mod paging;
53
54// Globals
55#[cfg(feature = "mem_profile")]
56struct ProfiledLockedHeap<const ORDER: usize>(LockedHeap<ORDER>);
57#[cfg(feature = "mem_profile")]
58unsafe impl<const ORDER: usize> alloc::alloc::GlobalAlloc for ProfiledLockedHeap<ORDER> {
59    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
60        let addr = unsafe { self.0.alloc(layout) };
61        unsafe {
62            core::arch::asm!("out dx, al",
63                in("dx") OutBAction::TraceMemoryAlloc as u16,
64                in("rax") layout.size() as u64,
65                in("rcx") addr as u64);
66        }
67        addr
68    }
69    unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
70        unsafe {
71            core::arch::asm!("out dx, al",
72                in("dx") OutBAction::TraceMemoryFree as u16,
73                in("rax") layout.size() as u64,
74                in("rcx") ptr as u64);
75            self.0.dealloc(ptr, layout)
76        }
77    }
78    unsafe fn alloc_zeroed(&self, layout: core::alloc::Layout) -> *mut u8 {
79        let addr = unsafe { self.0.alloc_zeroed(layout) };
80        unsafe {
81            core::arch::asm!("out dx, al",
82                in("dx") OutBAction::TraceMemoryAlloc as u16,
83                in("rax") layout.size() as u64,
84                in("rcx") addr as u64);
85        }
86        addr
87    }
88    unsafe fn realloc(
89        &self,
90        ptr: *mut u8,
91        layout: core::alloc::Layout,
92        new_size: usize,
93    ) -> *mut u8 {
94        let new_ptr = unsafe { self.0.realloc(ptr, layout, new_size) };
95        unsafe {
96            core::arch::asm!("out dx, al",
97                in("dx") OutBAction::TraceMemoryFree as u16,
98                in("rax") layout.size() as u64,
99                in("rcx") ptr);
100            core::arch::asm!("out dx, al",
101                in("dx") OutBAction::TraceMemoryAlloc as u16,
102                in("rax") new_size as u64,
103                in("rcx") new_ptr);
104        }
105        new_ptr
106    }
107}
108
109// === Globals ===
110#[cfg(not(feature = "mem_profile"))]
111#[global_allocator]
112pub(crate) static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::empty();
113#[cfg(feature = "mem_profile")]
114#[global_allocator]
115pub(crate) static HEAP_ALLOCATOR: ProfiledLockedHeap<32> =
116    ProfiledLockedHeap(LockedHeap::<32>::empty());
117
118pub static mut GUEST_HANDLE: GuestHandle = GuestHandle::new();
119pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister<GuestFunc> =
120    GuestFunctionRegister::new();
121
122/// The size of one page in the host OS, which may have some impacts
123/// on how buffers for host consumption should be aligned. Code only
124/// working with the guest page tables should use
125/// [`hyperlight_common::vm::PAGE_SIZE`] instead.
126pub static mut OS_PAGE_SIZE: u32 = 0;
127
128// === Panic Handler ===
129// It looks like rust-analyzer doesn't correctly manage no_std crates,
130// and so it displays an error about a duplicate panic_handler.
131// See more here: https://github.com/rust-lang/rust-analyzer/issues/4490
132// The cfg_attr attribute is used to avoid clippy failures as test pulls in std which pulls in a panic handler
133#[cfg_attr(not(test), panic_handler)]
134#[allow(clippy::panic)]
135// to satisfy the clippy when cfg == test
136#[allow(dead_code)]
137fn panic(info: &core::panic::PanicInfo) -> ! {
138    _panic_handler(info)
139}
140
141/// A writer that sends all output to the hyperlight host
142/// using output ports. This allows us to not impose a
143/// buffering limit on error message size on the guest end,
144/// though one exists for the host.
145struct HyperlightAbortWriter;
146impl core::fmt::Write for HyperlightAbortWriter {
147    fn write_str(&mut self, s: &str) -> core::fmt::Result {
148        write_abort(s.as_bytes());
149        Ok(())
150    }
151}
152
153#[inline(always)]
154fn _panic_handler(info: &core::panic::PanicInfo) -> ! {
155    let mut w = HyperlightAbortWriter;
156
157    // begin abort sequence by writing the error code
158    write_abort(&[ErrorCode::UnknownError as u8]);
159
160    let write_res = write!(w, "{}", info);
161    if write_res.is_err() {
162        write_abort("panic: message format failed".as_bytes());
163    }
164
165    // write abort terminator to finish the abort
166    // and signal to the host that the message can now be read
167    write_abort(&[0xFF]);
168    unreachable!();
169}
170
171// === Entrypoint ===
172
173unsafe extern "C" {
174    fn hyperlight_main();
175    fn srand(seed: u32);
176}
177
178/// Architecture-nonspecific initialisation: set up the heap,
179/// coordinate some addresses and configuration with the host, and run
180/// user initialisation
181pub(crate) extern "C" fn generic_init(
182    peb_address: u64,
183    seed: u64,
184    ops: u64,
185    max_log_level: u64,
186) -> u64 {
187    unsafe {
188        GUEST_HANDLE = GuestHandle::init(peb_address as *mut HyperlightPEB);
189        #[allow(static_mut_refs)]
190        let peb_ptr = GUEST_HANDLE.peb().unwrap();
191
192        let heap_start = (*peb_ptr).guest_heap.ptr as usize;
193        let heap_size = (*peb_ptr).guest_heap.size as usize;
194        #[cfg(not(feature = "mem_profile"))]
195        let heap_allocator = &HEAP_ALLOCATOR;
196        #[cfg(feature = "mem_profile")]
197        let heap_allocator = &HEAP_ALLOCATOR.0;
198        heap_allocator
199            .try_lock()
200            .expect("Failed to access HEAP_ALLOCATOR")
201            .init(heap_start, heap_size);
202        peb_ptr
203    };
204
205    // Save the guest start TSC for tracing
206    #[cfg(feature = "trace_guest")]
207    let guest_start_tsc = hyperlight_guest_tracing::invariant_tsc::read_tsc();
208
209    unsafe {
210        let srand_seed = (((peb_address << 8) ^ (seed >> 4)) >> 32) as u32;
211        // Set the seed for the random number generator for C code using rand;
212        srand(srand_seed);
213
214        OS_PAGE_SIZE = ops as u32;
215    }
216
217    // set up the logger
218    let guest_log_level_filter =
219        GuestLogFilter::try_from(max_log_level).expect("Invalid log level");
220    init_logger(guest_log_level_filter.into());
221
222    // It is important that all the tracing events are produced after the tracing is initialized.
223    #[cfg(feature = "trace_guest")]
224    if guest_log_level_filter != GuestLogFilter::Off {
225        hyperlight_guest_tracing::init_guest_tracing(
226            guest_start_tsc,
227            guest_log_level_filter.into(),
228        );
229    }
230
231    // Open a span to partly capture the initialization of the guest.
232    // This is done here because the tracing subscriber is initialized and the guest is in a
233    // well-known state
234    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
235    let _entered = tracing::span!(tracing::Level::INFO, "generic_init").entered();
236
237    #[cfg(feature = "macros")]
238    for registration in __private::GUEST_FUNCTION_INIT {
239        registration();
240    }
241
242    unsafe {
243        hyperlight_main();
244    }
245
246    // All this tracing logic shall be done right before the call to `hlt` which is done after this
247    // function returns
248    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
249    {
250        // NOTE: This is necessary to avoid closing the span twice. Flush closes all the open
251        // spans, when preparing to close a guest function call context.
252        // It is not mandatory, though, but avoids a warning on the host that alerts a spans
253        // that has not been opened but is being closed.
254        _entered.exit();
255
256        // Ensure that any tracing output from the initialisation phase is
257        // flushed to the host, if necessary.
258        hyperlight_guest_tracing::flush();
259    }
260
261    dispatch_function as usize as u64
262}
263
264#[cfg(feature = "macros")]
265#[doc(hidden)]
266pub mod __private {
267    pub use hyperlight_common::func::ResultType;
268    pub use hyperlight_guest::error::HyperlightGuestError;
269    pub use linkme;
270
271    #[linkme::distributed_slice]
272    pub static GUEST_FUNCTION_INIT: [fn()];
273
274    pub trait FromResult {
275        type Output;
276        fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self;
277    }
278
279    use alloc::string::String;
280    use alloc::vec::Vec;
281
282    use hyperlight_common::for_each_return_type;
283
284    macro_rules! impl_maybe_unwrap {
285        ($ty:ty, $enum:ident) => {
286            impl FromResult for $ty {
287                type Output = Self;
288                fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
289                    // Unwrapping here is fine as this would only run in a guest
290                    // and not in the host.
291                    res.unwrap()
292                }
293            }
294
295            impl FromResult for Result<$ty, HyperlightGuestError> {
296                type Output = $ty;
297                fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
298                    res
299                }
300            }
301        };
302    }
303
304    for_each_return_type!(impl_maybe_unwrap);
305}
306
307#[cfg(feature = "macros")]
308pub use hyperlight_guest_macro::{guest_function, host_function};
309
310pub use crate::guest_function::definition::GuestFunc;