hyperlight_guest/exit.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use core::ffi::{CStr, c_char};
5
6use hyperlight_common::outb::OutBAction;
7
8#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/exit.rs")]
9#[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/exit.rs")]
10mod arch;
11pub(crate) use arch::out32;
12
13/// Exits the VM with an Abort OUT action and code 0.
14#[unsafe(no_mangle)]
15pub extern "C" fn abort() -> ! {
16 abort_with_code(&[0, 0xFF])
17}
18
19/// Exits the VM with an Abort OUT action and a specific code.
20pub fn abort_with_code(code: &[u8]) -> ! {
21 // End any ongoing trace before aborting
22 #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
23 hyperlight_guest_tracing::end_trace();
24 outb(OutBAction::Abort as u16, code);
25 outb(OutBAction::Abort as u16, &[0xFF]); // send abort terminator (if not included in code)
26 unreachable!()
27}
28
29/// Aborts the program with a code and a message.
30///
31/// # Safety
32/// This function is unsafe because it dereferences a raw pointer.
33pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_char) -> ! {
34 // End any ongoing trace before aborting
35 #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
36 hyperlight_guest_tracing::end_trace();
37 unsafe {
38 // Step 1: Send abort code (typically 1 byte, but `code` allows flexibility)
39 outb(OutBAction::Abort as u16, code);
40
41 // Step 2: Convert the C string to bytes
42 let message_bytes = CStr::from_ptr(message_ptr).to_bytes(); // excludes null terminator
43
44 // Step 3: Send the message itself in chunks
45 outb(OutBAction::Abort as u16, message_bytes);
46
47 // Step 4: Send abort terminator to signal completion (e.g., 0xFF)
48 outb(OutBAction::Abort as u16, &[0xFF]);
49
50 // This function never returns
51 unreachable!()
52 }
53}
54
55/// This function exists to give the guest more manual control
56/// over the abort sequence. For example, in `hyperlight_guest_bin`'s panic handler,
57/// we have a message of unknown length that we want to stream
58/// to the host, which requires sending the message in chunks
59pub fn write_abort(code: &[u8]) {
60 outb(OutBAction::Abort as u16, code);
61}
62
63/// OUT bytes to the host through multiple exits.
64pub(crate) fn outb(port: u16, data: &[u8]) {
65 // Ensure all tracing data is flushed before sending OUT bytes
66 unsafe {
67 let mut i = 0;
68 while i < data.len() {
69 let remaining = data.len() - i;
70 let chunk_len = remaining.min(3);
71 let mut chunk = [0u8; 4];
72 chunk[0] = chunk_len as u8;
73 chunk[1..1 + chunk_len].copy_from_slice(&data[i..i + chunk_len]);
74 let val = u32::from_le_bytes(chunk);
75 out32(port, val);
76 i += chunk_len;
77 }
78 }
79}
80
81/// Prints a message using `OutBAction::DebugPrint`. It transmits bytes of a message
82/// through several VMExists and, with such, it is slower than
83/// `print_output_with_host_print`.
84///
85/// This function should be used in debug mode only. This function does not
86/// require memory to be setup to be used.
87pub fn debug_print(msg: &str) {
88 for byte in msg.bytes() {
89 unsafe {
90 out32(OutBAction::DebugPrint as u16, byte as u32);
91 }
92 }
93}