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