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