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
122const VERSION_STR: &str = env!("CARGO_PKG_VERSION");
123
124// Embed the hyperlight-guest-bin crate version as a proper ELF note so the
125// host can verify ABI compatibility at load time.
126#[used]
127#[unsafe(link_section = ".note.hyperlight-version")]
128static HYPERLIGHT_VERSION_NOTE: hyperlight_common::version_note::ElfNote<
129    {
130        hyperlight_common::version_note::padded_name_size(
131            hyperlight_common::version_note::HYPERLIGHT_NOTE_NAME.len() + 1,
132        )
133    },
134    { hyperlight_common::version_note::padded_desc_size(VERSION_STR.len() + 1) },
135> = hyperlight_common::version_note::ElfNote::new(
136    hyperlight_common::version_note::HYPERLIGHT_NOTE_NAME,
137    VERSION_STR,
138    hyperlight_common::version_note::HYPERLIGHT_NOTE_TYPE,
139);
140
141/// The size of one page in the host OS, which may have some impacts
142/// on how buffers for host consumption should be aligned. Code only
143/// working with the guest page tables should use
144/// [`hyperlight_common::vm::PAGE_SIZE`] instead.
145pub static mut OS_PAGE_SIZE: u32 = 0;
146
147// === Panic Handler ===
148// It looks like rust-analyzer doesn't correctly manage no_std crates,
149// and so it displays an error about a duplicate panic_handler.
150// See more here: https://github.com/rust-lang/rust-analyzer/issues/4490
151// The cfg_attr attribute is used to avoid clippy failures as test pulls in std which pulls in a panic handler
152#[cfg_attr(not(test), panic_handler)]
153#[allow(clippy::panic)]
154// to satisfy the clippy when cfg == test
155#[allow(dead_code)]
156fn panic(info: &core::panic::PanicInfo) -> ! {
157    _panic_handler(info)
158}
159
160/// A writer that sends all output to the hyperlight host
161/// using output ports. This allows us to not impose a
162/// buffering limit on error message size on the guest end,
163/// though one exists for the host.
164struct HyperlightAbortWriter;
165impl core::fmt::Write for HyperlightAbortWriter {
166    fn write_str(&mut self, s: &str) -> core::fmt::Result {
167        write_abort(s.as_bytes());
168        Ok(())
169    }
170}
171
172#[inline(always)]
173fn _panic_handler(info: &core::panic::PanicInfo) -> ! {
174    let mut w = HyperlightAbortWriter;
175
176    // begin abort sequence by writing the error code
177    write_abort(&[ErrorCode::UnknownError as u8]);
178
179    let write_res = write!(w, "{}", info);
180    if write_res.is_err() {
181        write_abort("panic: message format failed".as_bytes());
182    }
183
184    // write abort terminator to finish the abort
185    // and signal to the host that the message can now be read
186    write_abort(&[0xFF]);
187    unreachable!();
188}
189
190// === Entrypoint ===
191
192unsafe extern "C" {
193    fn hyperlight_main();
194    fn srand(seed: u32);
195}
196
197/// Architecture-nonspecific initialisation: set up the heap,
198/// coordinate some addresses and configuration with the host, and run
199/// user initialisation
200pub(crate) extern "C" fn generic_init(
201    peb_address: u64,
202    seed: u64,
203    ops: u64,
204    max_log_level: u64,
205) -> u64 {
206    unsafe {
207        GUEST_HANDLE = GuestHandle::init(peb_address as *mut HyperlightPEB);
208        #[allow(static_mut_refs)]
209        let peb_ptr = GUEST_HANDLE.peb().unwrap();
210
211        let heap_start = (*peb_ptr).guest_heap.ptr as usize;
212        let heap_size = (*peb_ptr).guest_heap.size as usize;
213        #[cfg(not(feature = "mem_profile"))]
214        let heap_allocator = &HEAP_ALLOCATOR;
215        #[cfg(feature = "mem_profile")]
216        let heap_allocator = &HEAP_ALLOCATOR.0;
217        heap_allocator
218            .try_lock()
219            .expect("Failed to access HEAP_ALLOCATOR")
220            .init(heap_start, heap_size);
221        peb_ptr
222    };
223
224    // Save the guest start TSC for tracing
225    #[cfg(feature = "trace_guest")]
226    let guest_start_tsc = hyperlight_guest_tracing::invariant_tsc::read_tsc();
227
228    unsafe {
229        let srand_seed = (((peb_address << 8) ^ (seed >> 4)) >> 32) as u32;
230        // Set the seed for the random number generator for C code using rand;
231        srand(srand_seed);
232
233        OS_PAGE_SIZE = ops as u32;
234    }
235
236    // set up the logger
237    let guest_log_level_filter =
238        GuestLogFilter::try_from(max_log_level).expect("Invalid log level");
239    init_logger(guest_log_level_filter.into());
240
241    // It is important that all the tracing events are produced after the tracing is initialized.
242    #[cfg(feature = "trace_guest")]
243    if guest_log_level_filter != GuestLogFilter::Off {
244        hyperlight_guest_tracing::init_guest_tracing(
245            guest_start_tsc,
246            guest_log_level_filter.into(),
247        );
248    }
249
250    // Open a span to partly capture the initialization of the guest.
251    // This is done here because the tracing subscriber is initialized and the guest is in a
252    // well-known state
253    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
254    let _entered = tracing::span!(tracing::Level::INFO, "generic_init").entered();
255
256    #[cfg(feature = "macros")]
257    for registration in __private::GUEST_FUNCTION_INIT {
258        registration();
259    }
260
261    unsafe {
262        hyperlight_main();
263    }
264
265    // All this tracing logic shall be done right before the call to `hlt` which is done after this
266    // function returns
267    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
268    {
269        // NOTE: This is necessary to avoid closing the span twice. Flush closes all the open
270        // spans, when preparing to close a guest function call context.
271        // It is not mandatory, though, but avoids a warning on the host that alerts a spans
272        // that has not been opened but is being closed.
273        _entered.exit();
274
275        // Ensure that any tracing output from the initialisation phase is
276        // flushed to the host, if necessary.
277        hyperlight_guest_tracing::flush();
278    }
279
280    dispatch_function as usize as u64
281}
282
283#[cfg(feature = "macros")]
284#[doc(hidden)]
285pub mod __private {
286    pub use hyperlight_common::func::ResultType;
287    pub use hyperlight_guest::error::HyperlightGuestError;
288    pub use linkme;
289
290    #[linkme::distributed_slice]
291    pub static GUEST_FUNCTION_INIT: [fn()];
292
293    pub trait FromResult {
294        type Output;
295        fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self;
296    }
297
298    use alloc::string::String;
299    use alloc::vec::Vec;
300
301    use hyperlight_common::for_each_return_type;
302
303    macro_rules! impl_maybe_unwrap {
304        ($ty:ty, $enum:ident) => {
305            impl FromResult for $ty {
306                type Output = Self;
307                fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
308                    // Unwrapping here is fine as this would only run in a guest
309                    // and not in the host.
310                    res.unwrap()
311                }
312            }
313
314            impl FromResult for Result<$ty, HyperlightGuestError> {
315                type Output = $ty;
316                fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
317                    res
318                }
319            }
320        };
321    }
322
323    for_each_return_type!(impl_maybe_unwrap);
324}
325
326#[cfg(feature = "macros")]
327pub use hyperlight_guest_macro::{guest_function, host_function};
328
329pub use crate::guest_function::definition::GuestFunc;