hyperlight_guest/
exit.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
17use core::arch::asm;
18use core::ffi::{CStr, c_char};
19
20use hyperlight_common::outb::OutBAction;
21use tracing::instrument;
22
23/// Halt the execution of the guest and returns control to the host.
24/// Halt is generally called for a successful completion of the guest's work,
25/// this means we can instrument it as a trace point because the trace state
26/// shall not be locked at this point (we are not in an exception context).
27#[inline(never)]
28#[instrument(skip_all, level = "Trace")]
29pub fn halt() {
30    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
31    {
32        // Send data before halting
33        // If there is no data, this doesn't do anything
34        // The reason we do this here instead of in `hlt` asm function
35        // is to avoid allocating before halting, which leaks memory
36        // because the guest is not expected to resume execution after halting.
37        hyperlight_guest_tracing::flush();
38    }
39
40    unsafe { asm!("hlt", options(nostack)) };
41}
42
43/// Exits the VM with an Abort OUT action and code 0.
44#[unsafe(no_mangle)]
45pub extern "C" fn abort() -> ! {
46    abort_with_code(&[0, 0xFF])
47}
48
49/// Exits the VM with an Abort OUT action and a specific code.
50pub fn abort_with_code(code: &[u8]) -> ! {
51    // End any ongoing trace before aborting
52    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
53    hyperlight_guest_tracing::end_trace();
54    outb(OutBAction::Abort as u16, code);
55    outb(OutBAction::Abort as u16, &[0xFF]); // send abort terminator (if not included in code)
56    unreachable!()
57}
58
59/// Aborts the program with a code and a message.
60///
61/// # Safety
62/// This function is unsafe because it dereferences a raw pointer.
63pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_char) -> ! {
64    // End any ongoing trace before aborting
65    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
66    hyperlight_guest_tracing::end_trace();
67    unsafe {
68        // Step 1: Send abort code (typically 1 byte, but `code` allows flexibility)
69        outb(OutBAction::Abort as u16, code);
70
71        // Step 2: Convert the C string to bytes
72        let message_bytes = CStr::from_ptr(message_ptr).to_bytes(); // excludes null terminator
73
74        // Step 3: Send the message itself in chunks
75        outb(OutBAction::Abort as u16, message_bytes);
76
77        // Step 4: Send abort terminator to signal completion (e.g., 0xFF)
78        outb(OutBAction::Abort as u16, &[0xFF]);
79
80        // This function never returns
81        unreachable!()
82    }
83}
84
85/// This function exists to give the guest more manual control
86/// over the abort sequence. For example, in `hyperlight_guest_bin`'s panic handler,
87/// we have a message of unknown length that we want to stream
88/// to the host, which requires sending the message in chunks
89pub fn write_abort(code: &[u8]) {
90    outb(OutBAction::Abort as u16, code);
91}
92
93/// OUT bytes to the host through multiple exits.
94pub(crate) fn outb(port: u16, data: &[u8]) {
95    // Ensure all tracing data is flushed before sending OUT bytes
96    unsafe {
97        let mut i = 0;
98        while i < data.len() {
99            let remaining = data.len() - i;
100            let chunk_len = remaining.min(3);
101            let mut chunk = [0u8; 4];
102            chunk[0] = chunk_len as u8;
103            chunk[1..1 + chunk_len].copy_from_slice(&data[i..i + chunk_len]);
104            let val = u32::from_le_bytes(chunk);
105            out32(port, val);
106            i += chunk_len;
107        }
108    }
109}
110
111/// OUT function for sending a 32-bit value to the host.
112/// `out32` can be called from an exception context, so we must be careful
113/// with the tracing state that might be locked at that time.
114/// The tracing state calls `try_lock` internally to avoid deadlocks.
115/// Furthermore, the instrument macro is not used here to avoid creating spans
116/// in exception contexts. Because if the trace state is already locked, trying to create a span
117/// would cause a panic, which is undesirable in exception handling.
118pub(crate) unsafe fn out32(port: u16, val: u32) {
119    #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]
120    {
121        if let Some((ptr, len)) = hyperlight_guest_tracing::serialized_data() {
122            // If tracing is enabled and there is data to send, send it along with the OUT action
123            unsafe {
124                asm!("out dx, eax",
125                    in("dx") port,
126                    in("eax") val,
127                    in("r8") OutBAction::TraceBatch as u64,
128                    in("r9") ptr,
129                    in("r10") len,
130                    options(preserves_flags, nomem, nostack)
131                )
132            };
133
134            // Reset the trace state after sending the batch
135            // This clears all existing spans/events ensuring a clean state for the next operations
136            // The trace state is expected to be flushed before this call
137            hyperlight_guest_tracing::reset();
138        } else {
139            // If tracing is not enabled, just send the value
140            unsafe {
141                asm!("out dx, eax", in("dx") port, in("eax") val, options(preserves_flags, nomem, nostack))
142            };
143        }
144    }
145    #[cfg(not(all(feature = "trace_guest", target_arch = "x86_64")))]
146    unsafe {
147        asm!("out dx, eax", in("dx") port, in("eax") val, options(preserves_flags, nomem, nostack));
148    }
149}
150
151/// Prints a message using `OutBAction::DebugPrint`. It transmits bytes of a message
152/// through several VMExists and, with such, it is slower than
153/// `print_output_with_host_print`.
154///
155/// This function should be used in debug mode only. This function does not
156/// require memory to be setup to be used.
157pub fn debug_print(msg: &str) {
158    for byte in msg.bytes() {
159        unsafe {
160            out32(OutBAction::DebugPrint as u16, byte as u32);
161        }
162    }
163}