Skip to content

Commit 0dc0bbf

Browse files
committed
[guest/{entrypoint,host_fxn_call,interrupts},host/outb] brought back panic info to abort
In hyperlight-dev#474, I removed the guest panic context region. With that, aborting was split into two steps: (1) printing panic info w/ debug_print, and (2) exiting w/ abort and codes. This PR addresses feedback I got in hyperlight-dev#474 to join the operations by buffering string output when aborting. This PR also cleans up our manual chunking logic w/ outb and leverages out8, out16, and out32 to minimize VM exits. Abort detects it should finishing buffering by a terminator code (0xFF). Edited tests to check for panic message in aborts as they were prior to hyperlight-dev#474 merged. Signed-off-by: danbugs <[email protected]>
1 parent 319b5c4 commit 0dc0bbf

File tree

7 files changed

+137
-80
lines changed

7 files changed

+137
-80
lines changed

src/hyperlight_guest/src/entrypoint.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use spin::Once;
2525
use crate::gdt::load_gdt;
2626
use crate::guest_function_call::dispatch_function;
2727
use crate::guest_logger::init_logger;
28-
use crate::host_function_call::{debug_print, outb};
28+
use crate::host_function_call::outb;
2929
use crate::idtr::load_idt;
3030
use crate::{
3131
__security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS, OS_PAGE_SIZE, OUTB_PTR,
@@ -43,11 +43,12 @@ pub fn halt() {
4343

4444
#[no_mangle]
4545
pub extern "C" fn abort() -> ! {
46-
abort_with_code(&[0])
46+
abort_with_code(&[0, 0xFF])
4747
}
4848

4949
pub fn abort_with_code(code: &[u8]) -> ! {
5050
outb(OutBAction::Abort as u16, code);
51+
outb(OutBAction::Abort as u16, &[0xFF]); // send abort terminator (if not included in code)
5152
unreachable!()
5253
}
5354

@@ -56,13 +57,19 @@ pub fn abort_with_code(code: &[u8]) -> ! {
5657
/// # Safety
5758
/// This function is unsafe because it dereferences a raw pointer.
5859
pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_char) -> ! {
59-
debug_print(
60-
CStr::from_ptr(message_ptr)
61-
.to_str()
62-
.expect("Invalid UTF-8 string"),
63-
);
64-
60+
// Step 1: Send abort code (typically 1 byte, but `code` allows flexibility)
6561
outb(OutBAction::Abort as u16, code);
62+
63+
// Step 2: Convert the C string to bytes
64+
let message_bytes = CStr::from_ptr(message_ptr).to_bytes(); // excludes null terminator
65+
66+
// Step 3: Send the message itself in chunks
67+
outb(OutBAction::Abort as u16, message_bytes);
68+
69+
// Step 4: Send abort terminator to signal completion (e.g., 0xFF)
70+
outb(OutBAction::Abort as u16, &[0xFF]);
71+
72+
// This function never returns
6673
unreachable!()
6774
}
6875

src/hyperlight_guest/src/host_function_call.rs

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
use alloc::format;
1818
use alloc::string::ToString;
1919
use alloc::vec::Vec;
20-
use core::arch::global_asm;
20+
use core::arch;
2121

2222
use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
2323
use hyperlight_common::flatbuffer_wrappers::function_types::{
@@ -79,21 +79,31 @@ pub fn outb(port: u16, data: &[u8]) {
7979
unsafe {
8080
match RUNNING_MODE {
8181
RunMode::Hypervisor => {
82-
for chunk in data.chunks(4) {
83-
// Process the data in chunks of 4 bytes. If a chunk has fewer than 4 bytes,
84-
// pad it with 0x7F to ensure it can be converted into a 4-byte array.
85-
// The choice of 0x7F as the padding value is arbitrary and does not carry
86-
// any special meaning; it simply ensures consistent chunk size.
87-
let val = match chunk {
88-
[a, b, c, d] => u32::from_le_bytes([*a, *b, *c, *d]),
89-
[a, b, c] => u32::from_le_bytes([*a, *b, *c, 0x7F]),
90-
[a, b] => u32::from_le_bytes([*a, *b, 0x7F, 0x7F]),
91-
[a] => u32::from_le_bytes([*a, 0x7F, 0x7F, 0x7F]),
92-
[] => break,
93-
_ => unreachable!(),
94-
};
95-
96-
hloutd(val, port);
82+
let mut i = 0;
83+
while i < data.len() {
84+
let remaining = data.len() - i;
85+
match remaining {
86+
4.. => {
87+
let val = u32::from_le_bytes([
88+
data[i],
89+
data[i + 1],
90+
data[i + 2],
91+
data[i + 3],
92+
]);
93+
out32(port, val);
94+
i += 4;
95+
}
96+
2..=3 => {
97+
let val = u16::from_le_bytes([data[i], data[i + 1]]);
98+
out16(port, val);
99+
i += 2;
100+
}
101+
1 => {
102+
out8(port, data[i]);
103+
i += 1;
104+
}
105+
_ => break,
106+
}
97107
}
98108
}
99109
RunMode::InProcessLinux | RunMode::InProcessWindows => {
@@ -119,11 +129,33 @@ pub fn outb(port: u16, data: &[u8]) {
119129
}
120130
}
121131

122-
extern "win64" {
123-
fn hloutd(value: u32, port: u16);
132+
pub(crate) unsafe fn out8(port: u16, val: u8) {
133+
arch::asm!("out dx, al", in("dx") port, in("al") val, options(preserves_flags, nomem, nostack));
134+
}
135+
136+
pub(crate) unsafe fn out16(port: u16, val: u16) {
137+
arch::asm!("out dx, ax", in("dx") port, in("ax") val, options(preserves_flags, nomem, nostack));
138+
}
139+
140+
pub(crate) unsafe fn out32(port: u16, val: u32) {
141+
arch::asm!("out dx, eax", in("dx") port, in("eax") val, options(preserves_flags, nomem, nostack));
142+
}
143+
144+
/// Prints a message using `OutBAction::DebugPrint`. It transmits bytes of a message
145+
/// through several VMExists and, with such, it is slower than
146+
/// `print_output_with_host_print`.
147+
///
148+
/// This function should be used in debug mode only. This function does not
149+
/// require memory to be setup to be used.
150+
pub fn debug_print(msg: &str) {
151+
outb(OutBAction::DebugPrint as u16, msg.as_bytes());
124152
}
125153

126-
pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result<Vec<u8>> {
154+
/// Print a message using the host's print function.
155+
///
156+
/// This function requires memory to be setup to be used. In particular, the
157+
/// existence of the input and output memory regions.
158+
pub fn print_output_with_host_print(function_call: &FunctionCall) -> Result<Vec<u8>> {
127159
if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() {
128160
call_host_function(
129161
"HostPrint",
@@ -135,20 +167,7 @@ pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result<Ve
135167
} else {
136168
Err(HyperlightGuestError::new(
137169
ErrorCode::GuestError,
138-
"Wrong Parameters passed to print_output_as_guest_function".to_string(),
170+
"Wrong Parameters passed to print_output_with_host_print".to_string(),
139171
))
140172
}
141173
}
142-
143-
pub fn debug_print(msg: &str) {
144-
outb(OutBAction::DebugPrint as u16, msg.as_bytes());
145-
}
146-
147-
global_asm!(
148-
".global hloutd
149-
hloutd:
150-
mov eax, ecx
151-
mov dx, dx
152-
out dx, eax
153-
ret"
154-
);

src/hyperlight_guest/src/interrupt_handlers.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@ pub extern "sysv64" fn hl_exception_handler(
3131
) {
3232
let exception = Exception::try_from(exception_number as u8).expect("Invalid exception number");
3333
let msg = format!(
34-
"EXCEPTION: {:#?}\n\
35-
Page Fault Address: {:#x}\n\
34+
"Page Fault Address: {:#x}\n\
3635
Stack Pointer: {:#x}",
37-
exception, page_fault_address, stack_pointer
36+
page_fault_address, stack_pointer
3837
);
3938

4039
unsafe {

src/hyperlight_guest/src/lib.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,13 @@ limitations under the License.
1717
#![no_std]
1818
// Deps
1919
use alloc::string::ToString;
20-
use core::hint::unreachable_unchecked;
2120

2221
use buddy_system_allocator::LockedHeap;
2322
use guest_function_register::GuestFunctionRegister;
24-
use host_function_call::debug_print;
2523
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
2624
use hyperlight_common::mem::{HyperlightPEB, RunMode};
27-
use hyperlight_common::outb::OutBAction;
2825

29-
use crate::host_function_call::outb;
26+
use crate::entrypoint::abort_with_code_and_message;
3027
extern crate alloc;
3128

3229
// Modules
@@ -73,9 +70,11 @@ pub(crate) static _fltused: i32 = 0;
7370
// to satisfy the clippy when cfg == test
7471
#[allow(dead_code)]
7572
fn panic(info: &core::panic::PanicInfo) -> ! {
76-
debug_print(info.to_string().as_str());
77-
outb(OutBAction::Abort as u16, &[ErrorCode::UnknownError as u8]);
78-
unsafe { unreachable_unchecked() }
73+
let msg = info.to_string();
74+
let c_string = alloc::ffi::CString::new(msg)
75+
.unwrap_or_else(|_| alloc::ffi::CString::new("panic (invalid utf8)").unwrap());
76+
77+
unsafe { abort_with_code_and_message(&[ErrorCode::UnknownError as u8], c_string.as_ptr()) }
7978
}
8079

8180
// Globals

src/hyperlight_host/src/sandbox/outb.rs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::cell::RefCell;
1718
use std::sync::{Arc, Mutex};
1819

1920
use hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue;
@@ -93,6 +94,58 @@ pub(super) fn outb_log(mgr: &mut SandboxMemoryManager<HostSharedMemory>) -> Resu
9394
Ok(())
9495
}
9596

97+
const ABORT_TERMINATOR: u8 = 0xFF;
98+
99+
thread_local! {
100+
static GUEST_ABORT_BUFFER: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) }
101+
}
102+
103+
fn outb_abort(data: &[u8]) -> Result<()> {
104+
GUEST_ABORT_BUFFER.with(|buf| {
105+
let mut buffer = buf.borrow_mut();
106+
107+
for &b in data {
108+
if b == ABORT_TERMINATOR {
109+
let full = &buffer[..];
110+
111+
let guest_error_code = *full.first().unwrap_or(&0);
112+
let guest_error = ErrorCode::from(guest_error_code as u64);
113+
114+
match guest_error {
115+
ErrorCode::StackOverflow => {
116+
buffer.clear();
117+
return Err(HyperlightError::StackOverflow());
118+
}
119+
_ => {
120+
let message = if let Some(&maybe_exception_code) = full.get(1) {
121+
match Exception::try_from(maybe_exception_code) {
122+
Ok(exception) => {
123+
let extra_msg = String::from_utf8_lossy(&full[2..]);
124+
format!("Exception: {:?} | {}", exception, extra_msg)
125+
}
126+
Err(_) => {
127+
// Not a valid exception, so treat full[1..] as message
128+
String::from_utf8_lossy(&full[1..]).into()
129+
}
130+
}
131+
} else {
132+
// Only the code was sent, no additional data
133+
String::new()
134+
};
135+
136+
buffer.clear();
137+
return Err(HyperlightError::GuestAborted(guest_error_code, message));
138+
}
139+
}
140+
} else {
141+
buffer.push(b);
142+
}
143+
}
144+
145+
Ok(())
146+
})
147+
}
148+
96149
/// Handles OutB operations from the guest.
97150
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
98151
fn handle_outb_impl(
@@ -117,27 +170,7 @@ fn handle_outb_impl(
117170

118171
Ok(())
119172
}
120-
OutBAction::Abort => {
121-
let byte = u64::from(data[0]);
122-
let guest_error = ErrorCode::from(byte);
123-
124-
match guest_error {
125-
ErrorCode::StackOverflow => Err(HyperlightError::StackOverflow()),
126-
_ => {
127-
let message = match data.get(1) {
128-
Some(&exception_code) => match Exception::try_from(exception_code) {
129-
Ok(exception) => format!("Exception: {:?}", exception),
130-
Err(e) => {
131-
format!("Unknown exception code: {:#x} ({})", exception_code, e)
132-
}
133-
},
134-
None => "See stderr for panic context".into(),
135-
};
136-
137-
Err(HyperlightError::GuestAborted(byte as u8, message))
138-
}
139-
}
140-
}
173+
OutBAction::Abort => outb_abort(&data),
141174
OutBAction::DebugPrint => {
142175
let s = String::from_utf8_lossy(&data);
143176
eprint!("{}", s);

src/hyperlight_host/tests/integration_test.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fn guest_abort() {
6363
.unwrap_err();
6464
println!("{:?}", res);
6565
assert!(
66-
matches!(res, HyperlightError::GuestAborted(code, message) if (code == error_code && message.contains("NoException")) )
66+
matches!(res, HyperlightError::GuestAborted(code, message) if (code == error_code && message.is_empty()))
6767
);
6868
}
6969

@@ -83,7 +83,7 @@ fn guest_abort_with_context1() {
8383
.unwrap_err();
8484
println!("{:?}", res);
8585
assert!(
86-
matches!(res, HyperlightError::GuestAborted(code, context) if (code == 25 && context.contains("NoException")))
86+
matches!(res, HyperlightError::GuestAborted(code, context) if (code == 25 && context == "Oh no"))
8787
);
8888
}
8989

@@ -135,7 +135,7 @@ fn guest_abort_with_context2() {
135135
.unwrap_err();
136136
println!("{:?}", res);
137137
assert!(
138-
matches!(res, HyperlightError::GuestAborted(_, context) if context.contains("NoException"))
138+
matches!(res, HyperlightError::GuestAborted(_, context) if context.contains(&abort_message[..400]))
139139
);
140140
}
141141

@@ -161,7 +161,7 @@ fn guest_abort_c_guest() {
161161
.unwrap_err();
162162
println!("{:?}", res);
163163
assert!(
164-
matches!(res, HyperlightError::GuestAborted(code, message) if (code == 75 && message.contains("NoException")) )
164+
matches!(res, HyperlightError::GuestAborted(code, message) if (code == 75 && message == "This is a test error message") )
165165
);
166166
}
167167

@@ -181,7 +181,7 @@ fn guest_panic() {
181181
.unwrap_err();
182182
println!("{:?}", res);
183183
assert!(
184-
matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("NoException"))
184+
matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("\nError... error..."))
185185
)
186186
}
187187

@@ -262,7 +262,7 @@ fn guest_malloc_abort() {
262262
assert!(matches!(
263263
res.unwrap_err(),
264264
// OOM memory errors in rust allocator are panics. Our panic handler returns ErrorCode::UnknownError on panic
265-
HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("NoException")
265+
HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("memory allocation of ")
266266
));
267267
}
268268

src/tests/rust_guests/callbackguest/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use hyperlight_guest::error::{HyperlightGuestError, Result};
3535
use hyperlight_guest::guest_function_definition::GuestFunctionDefinition;
3636
use hyperlight_guest::guest_function_register::register_function;
3737
use hyperlight_guest::host_function_call::{
38-
call_host_function, get_host_return_value, print_output_as_guest_function,
38+
call_host_function, get_host_return_value, print_output_with_host_print,
3939
};
4040
use hyperlight_guest::logging::log_message;
4141

@@ -167,7 +167,7 @@ pub extern "C" fn hyperlight_main() {
167167
"PrintOutput".to_string(),
168168
Vec::from(&[ParameterType::String]),
169169
ReturnType::Int,
170-
print_output_as_guest_function as usize,
170+
print_output_with_host_print as usize,
171171
);
172172
register_function(print_output_def);
173173

0 commit comments

Comments
 (0)