Skip to content

Guest panic context refactor #474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/hyperlight_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ pub mod flatbuffer_wrappers;
mod flatbuffers;
/// cbindgen:ignore
pub mod mem;

/// cbindgen:ignore
pub mod outb;
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ pub struct GuestStackData {
pub bootStackAddress: u64,
}

#[repr(C)]
pub struct GuestPanicContextData {
pub guestPanicContextDataSize: u64,
pub guestPanicContextDataBuffer: *mut c_void,
}

#[repr(C)]
pub struct HyperlightPEB {
pub security_cookie_seed: u64,
Expand All @@ -90,7 +84,6 @@ pub struct HyperlightPEB {
pub runMode: RunMode,
pub inputdata: InputData,
pub outputdata: OutputData,
pub guestPanicContextData: GuestPanicContextData,
pub guestheapData: GuestHeapData,
pub gueststackData: GuestStackData,
}
95 changes: 95 additions & 0 deletions src/hyperlight_common/src/outb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use core::convert::TryFrom;

use anyhow::{anyhow, Error};

/// Exception codes for the x86 architecture.
/// These are helpful to identify the type of exception that occurred
/// together with OutBAction::Abort.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum Exception {
DivideByZero = 0,
Debug = 1,
NonMaskableInterrupt = 2,
Breakpoint = 3,
Overflow = 4,
BoundRangeExceeded = 5,
InvalidOpcode = 6,
DeviceNotAvailable = 7,
DoubleFault = 8,
CoprocessorSegmentOverrun = 9,
InvalidTSS = 10,
SegmentNotPresent = 11,
StackSegmentFault = 12,
GeneralProtectionFault = 13,
PageFault = 14,
Reserved = 15,
X87FloatingPointException = 16,
AlignmentCheck = 17,
MachineCheck = 18,
SIMDFloatingPointException = 19,
VirtualizationException = 20,
SecurityException = 30,
NoException = 0xFF,
}

impl TryFrom<u8> for Exception {
type Error = Error;

fn try_from(value: u8) -> Result<Self, Self::Error> {
use Exception::*;
let exception = match value {
0 => DivideByZero,
1 => Debug,
2 => NonMaskableInterrupt,
3 => Breakpoint,
4 => Overflow,
5 => BoundRangeExceeded,
6 => InvalidOpcode,
7 => DeviceNotAvailable,
8 => DoubleFault,
9 => CoprocessorSegmentOverrun,
10 => InvalidTSS,
11 => SegmentNotPresent,
12 => StackSegmentFault,
13 => GeneralProtectionFault,
14 => PageFault,
15 => Reserved,
16 => X87FloatingPointException,
17 => AlignmentCheck,
18 => MachineCheck,
19 => SIMDFloatingPointException,
20 => VirtualizationException,
30 => SecurityException,
0x7F => NoException,
_ => return Err(anyhow!("Unknown exception code: {:#x}", value)),
};

Ok(exception)
}
}

/// Supported actions when issuing an OUTB actions by Hyperlight.
/// - Log: for logging,
/// - CallFunction: makes a call to a host function,
/// - Abort: aborts the execution of the guest,
/// - DebugPrint: prints a message to the host
pub enum OutBAction {
Log = 99,
CallFunction = 101,
Abort = 102,
DebugPrint = 103,
}

impl TryFrom<u16> for OutBAction {
type Error = anyhow::Error;
fn try_from(val: u16) -> anyhow::Result<Self> {
match val {
99 => Ok(OutBAction::Log),
101 => Ok(OutBAction::CallFunction),
102 => Ok(OutBAction::Abort),
103 => Ok(OutBAction::DebugPrint),
_ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)),
}
}
}
34 changes: 19 additions & 15 deletions src/hyperlight_guest/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ limitations under the License.

use core::arch::asm;
use core::ffi::{c_char, c_void, CStr};
use core::ptr::copy_nonoverlapping;

use hyperlight_common::mem::{HyperlightPEB, RunMode};
use hyperlight_common::outb::OutBAction;
use log::LevelFilter;
use spin::Once;

use crate::gdt::load_gdt;
use crate::guest_function_call::dispatch_function;
use crate::guest_logger::init_logger;
use crate::host_function_call::{outb, OutBAction};
use crate::host_function_call::{debug_print, outb};
use crate::idtr::load_idt;
use crate::{
__security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS, OS_PAGE_SIZE, OUTB_PTR,
Expand All @@ -43,26 +43,26 @@ pub fn halt() {

#[no_mangle]
pub extern "C" fn abort() -> ! {
abort_with_code(0)
abort_with_code(&[0])
}

pub fn abort_with_code(code: i32) -> ! {
outb(OutBAction::Abort as u16, code as u8);
pub fn abort_with_code(code: &[u8]) -> ! {
outb(OutBAction::Abort as u16, code);
unreachable!()
}

/// Aborts the program with a code and a message.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer.
pub unsafe fn abort_with_code_and_message(code: i32, message_ptr: *const c_char) -> ! {
let peb_ptr = P_PEB.unwrap();
copy_nonoverlapping(
message_ptr,
(*peb_ptr).guestPanicContextData.guestPanicContextDataBuffer as *mut c_char,
CStr::from_ptr(message_ptr).count_bytes() + 1, // +1 for null terminator
pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_char) -> ! {
debug_print(
CStr::from_ptr(message_ptr)
.to_str()
.expect("Invalid UTF-8 string"),
);
outb(OutBAction::Abort as u16, code as u8);

outb(OutBAction::Abort as u16, code);
unreachable!()
}

Expand Down Expand Up @@ -114,7 +114,7 @@ pub extern "win64" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_
RUNNING_MODE = (*peb_ptr).runMode;

OUTB_PTR = {
let outb_ptr: extern "win64" fn(u16, u8) =
let outb_ptr: extern "win64" fn(u16, *const u8, u64) =
core::mem::transmute((*peb_ptr).pOutb);
Some(outb_ptr)
};
Expand All @@ -124,8 +124,12 @@ pub extern "win64" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_
}

OUTB_PTR_WITH_CONTEXT = {
let outb_ptr_with_context: extern "win64" fn(*mut c_void, u16, u8) =
core::mem::transmute((*peb_ptr).pOutb);
let outb_ptr_with_context: extern "win64" fn(
*mut c_void,
u16,
*const u8,
u64,
) = core::mem::transmute((*peb_ptr).pOutb);
Some(outb_ptr_with_context)
};
}
Expand Down
5 changes: 3 additions & 2 deletions src/hyperlight_guest/src/guest_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ use alloc::vec::Vec;
use core::ffi::{c_char, CStr};

use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError};
use hyperlight_common::outb::OutBAction;

use crate::entrypoint::halt;
use crate::host_function_call::{outb, OutBAction};
use crate::host_function_call::outb;
use crate::shared_output_data::push_shared_output_data;

pub(crate) fn write_error(error_code: ErrorCode, message: Option<&str>) {
Expand All @@ -48,7 +49,7 @@ pub(crate) fn set_error_and_halt(error_code: ErrorCode, message: &str) {

#[no_mangle]
pub(crate) extern "win64" fn set_stack_allocate_error() {
outb(OutBAction::Abort as u16, ErrorCode::StackOverflow as u8);
outb(OutBAction::Abort as u16, &[ErrorCode::StackOverflow as u8]);
}

#[no_mangle]
Expand Down
57 changes: 37 additions & 20 deletions src/hyperlight_guest/src/host_function_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,13 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result;
use hyperlight_common::mem::RunMode;
use hyperlight_common::outb::OutBAction;

use crate::error::{HyperlightGuestError, Result};
use crate::shared_input_data::try_pop_shared_input_data_into;
use crate::shared_output_data::push_shared_output_data;
use crate::{OUTB_PTR, OUTB_PTR_WITH_CONTEXT, P_PEB, RUNNING_MODE};

pub enum OutBAction {
Log = 99,
CallFunction = 101,
Abort = 102,
}

/// Get a return value from a host function call.
/// This usually requires a host function to be called first using `call_host_function`.
pub fn get_host_return_value<T: TryFrom<ReturnValue>>() -> Result<T> {
Expand Down Expand Up @@ -75,24 +70,44 @@ pub fn call_host_function(

push_shared_output_data(host_function_call_buffer)?;

outb(OutBAction::CallFunction as u16, 0);
outb(OutBAction::CallFunction as u16, &[0]);

Ok(())
}

pub fn outb(port: u16, value: u8) {
pub fn outb(port: u16, data: &[u8]) {
unsafe {
match RUNNING_MODE {
RunMode::Hypervisor => {
hloutb(port, value);
for chunk in data.chunks(4) {
// Process the data in chunks of 4 bytes. If a chunk has fewer than 4 bytes,
// pad it with 0x7F to ensure it can be converted into a 4-byte array.
// The choice of 0x7F as the padding value is arbitrary and does not carry
// any special meaning; it simply ensures consistent chunk size.
let val = match chunk {
[a, b, c, d] => u32::from_le_bytes([*a, *b, *c, *d]),
[a, b, c] => u32::from_le_bytes([*a, *b, *c, 0x7F]),
[a, b] => u32::from_le_bytes([*a, *b, 0x7F, 0x7F]),
[a] => u32::from_le_bytes([*a, 0x7F, 0x7F, 0x7F]),
[] => break,
_ => unreachable!(),
};

hloutd(val, port);
}
}
RunMode::InProcessLinux | RunMode::InProcessWindows => {
if let Some(outb_func) = OUTB_PTR_WITH_CONTEXT {
if let Some(peb_ptr) = P_PEB {
outb_func((*peb_ptr).pOutbContext, port, value);
outb_func(
(*peb_ptr).pOutbContext,
port,
data.as_ptr(),
data.len() as u64,
);
}
} else if let Some(outb_func) = OUTB_PTR {
outb_func(port, value);
outb_func(port, data.as_ptr(), data.len() as u64);
} else {
panic!("Tried to call outb without hypervisor and without outb function ptrs");
}
Expand All @@ -105,7 +120,7 @@ pub fn outb(port: u16, value: u8) {
}

extern "win64" {
fn hloutb(port: u16, value: u8);
fn hloutd(value: u32, port: u16);
}

pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result<Vec<u8>> {
Expand All @@ -125,13 +140,15 @@ pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result<Ve
}
}

// port: RCX(cx), value: RDX(dl)
pub fn debug_print(msg: &str) {
outb(OutBAction::DebugPrint as u16, msg.as_bytes());
}

global_asm!(
".global hloutb
hloutb:
xor rax, rax
mov al, dl
mov dx, cx
out dx, al
ret"
".global hloutd
hloutd:
mov eax, ecx
mov dx, dx
out dx, eax
ret"
);
46 changes: 24 additions & 22 deletions src/hyperlight_guest/src/idt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

use hyperlight_common::outb::Exception;

use crate::interrupt_entry::{
_do_excp0, _do_excp1, _do_excp10, _do_excp11, _do_excp12, _do_excp13, _do_excp14, _do_excp15,
_do_excp16, _do_excp17, _do_excp18, _do_excp19, _do_excp2, _do_excp20, _do_excp3, _do_excp30,
Expand Down Expand Up @@ -70,28 +72,28 @@ impl IdtEntry {
pub(crate) static mut IDT: [IdtEntry; 256] = unsafe { core::mem::zeroed() };

pub(crate) fn init_idt() {
set_idt_entry(0, _do_excp0); // Divide by zero
set_idt_entry(1, _do_excp1); // Debug
set_idt_entry(2, _do_excp2); // Non-maskable interrupt
set_idt_entry(3, _do_excp3); // Breakpoint
set_idt_entry(4, _do_excp4); // Overflow
set_idt_entry(5, _do_excp5); // Bound Range Exceeded
set_idt_entry(6, _do_excp6); // Invalid Opcode
set_idt_entry(7, _do_excp7); // Device Not Available
set_idt_entry(8, _do_excp8); // Double Fault
set_idt_entry(9, _do_excp9); // Coprocessor Segment Overrun
set_idt_entry(10, _do_excp10); // Invalid TSS
set_idt_entry(11, _do_excp11); // Segment Not Present
set_idt_entry(12, _do_excp12); // Stack-Segment Fault
set_idt_entry(13, _do_excp13); // General Protection Fault
set_idt_entry(14, _do_excp14); // Page Fault
set_idt_entry(15, _do_excp15); // Reserved
set_idt_entry(16, _do_excp16); // x87 Floating-Point Exception
set_idt_entry(17, _do_excp17); // Alignment Check
set_idt_entry(18, _do_excp18); // Machine Check
set_idt_entry(19, _do_excp19); // SIMD Floating-Point Exception
set_idt_entry(20, _do_excp20); // Virtualization Exception
set_idt_entry(30, _do_excp30); // Security Exception
set_idt_entry(Exception::DivideByZero as usize, _do_excp0); // Divide by zero
set_idt_entry(Exception::Debug as usize, _do_excp1); // Debug
set_idt_entry(Exception::NonMaskableInterrupt as usize, _do_excp2); // Non-maskable interrupt
set_idt_entry(Exception::Breakpoint as usize, _do_excp3); // Breakpoint
set_idt_entry(Exception::Overflow as usize, _do_excp4); // Overflow
set_idt_entry(Exception::BoundRangeExceeded as usize, _do_excp5); // Bound Range Exceeded
set_idt_entry(Exception::InvalidOpcode as usize, _do_excp6); // Invalid Opcode
set_idt_entry(Exception::DeviceNotAvailable as usize, _do_excp7); // Device Not Available
set_idt_entry(Exception::DoubleFault as usize, _do_excp8); // Double Fault
set_idt_entry(Exception::CoprocessorSegmentOverrun as usize, _do_excp9); // Coprocessor Segment Overrun
set_idt_entry(Exception::InvalidTSS as usize, _do_excp10); // Invalid TSS
set_idt_entry(Exception::SegmentNotPresent as usize, _do_excp11); // Segment Not Present
set_idt_entry(Exception::StackSegmentFault as usize, _do_excp12); // Stack-Segment Fault
set_idt_entry(Exception::GeneralProtectionFault as usize, _do_excp13); // General Protection Fault
set_idt_entry(Exception::PageFault as usize, _do_excp14); // Page Fault
set_idt_entry(Exception::Reserved as usize, _do_excp15); // Reserved
set_idt_entry(Exception::X87FloatingPointException as usize, _do_excp16); // x87 Floating-Point Exception
set_idt_entry(Exception::AlignmentCheck as usize, _do_excp17); // Alignment Check
set_idt_entry(Exception::MachineCheck as usize, _do_excp18); // Machine Check
set_idt_entry(Exception::SIMDFloatingPointException as usize, _do_excp19); // SIMD Floating-Point Exception
set_idt_entry(Exception::VirtualizationException as usize, _do_excp20); // Virtualization Exception
set_idt_entry(Exception::SecurityException as usize, _do_excp30); // Security Exception
}

fn set_idt_entry(index: usize, handler: unsafe extern "sysv64" fn()) {
Expand Down
Loading
Loading