use core::fmt::Write;
use hyperlight_common::outb::Exception;
use hyperlight_common::vmem::{
BasicMapping, CowMapping, MappingKind, PAGE_SIZE, PhysAddr, VirtAddr,
};
use hyperlight_guest::exit::write_abort;
use hyperlight_guest::layout::{MAIN_STACK_LIMIT_GVA, MAIN_STACK_TOP_GVA};
use super::super::context::Context;
use super::super::machine::ExceptionInfo;
use crate::{ErrorCode, HyperlightAbortWriter};
pub static HANDLERS: [core::sync::atomic::AtomicU64; 31] =
[const { core::sync::atomic::AtomicU64::new(0) }; 31];
pub type ExceptionHandler = fn(
exception_number: u64,
exception_info: *mut ExceptionInfo,
context: *mut Context,
page_fault_address: u64,
) -> bool;
fn handle_stack_pagefault(gva: u64) {
unsafe {
let new_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1);
crate::paging::map_region(
new_page,
(gva & !0xfff) as *mut u8,
PAGE_SIZE as u64,
MappingKind::Basic(BasicMapping {
readable: true,
writable: true,
executable: false,
}),
);
}
}
fn handle_cow_pagefault(_phys: PhysAddr, virt: VirtAddr, perms: CowMapping) {
unsafe {
let new_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1);
let target_virt = virt as *mut u8;
let Some(scratch_mapping_access) = crate::paging::phys_to_virt(new_page) else {
write_abort(&[ErrorCode::GuestError as u8]);
write_abort(
"impossible: phys_to_virt returned page not mapped into scratch region".as_bytes(),
);
write_abort(&[0xFF]);
unreachable!();
};
core::ptr::copy(target_virt, scratch_mapping_access, PAGE_SIZE);
crate::paging::map_region(
new_page,
target_virt,
PAGE_SIZE as u64,
MappingKind::Basic(BasicMapping {
readable: perms.readable,
writable: true,
executable: perms.executable,
}),
);
core::arch::asm!("invlpg [{}]", in(reg) target_virt, options(readonly, nostack, preserves_flags));
}
}
fn try_handle_internal_pagefault(
exn_info: *mut ExceptionInfo,
_ctx: *mut Context,
gva: u64,
) -> bool {
let error_code = unsafe { (&raw const (*exn_info).error_code).read_volatile() };
let present = (error_code & (1 << 0)) != 0; if !present {
if (MAIN_STACK_LIMIT_GVA..MAIN_STACK_TOP_GVA).contains(&gva) {
handle_stack_pagefault(gva);
return true;
}
return false;
}
let mut orig_mappings = crate::paging::virt_to_phys(gva);
let fault_was_rsvd_entry = (error_code & (1 << 3)) != 0;
if fault_was_rsvd_entry {
return false;
}
let access_was_write = (error_code & (1 << 1)) != 0;
let access_was_user = (error_code & (1 << 2)) != 0;
let access_was_insn = (error_code & (1 << 4)) != 0;
if access_was_write && !access_was_user && !access_was_insn {
if let Some(mapping) = orig_mappings.next()
&& let None = orig_mappings.next()
&& let MappingKind::Cow(cm) = mapping.kind
{
handle_cow_pagefault(mapping.phys_base, mapping.virt_base, cm);
return true;
}
return false;
};
false
}
#[unsafe(no_mangle)]
pub(crate) extern "C" fn hl_exception_handler(
stack_pointer: u64,
exception_number: u64,
page_fault_address: u64,
) {
let ctx = stack_pointer as *mut Context;
let exn_info = (stack_pointer + size_of::<Context>() as u64) as *mut ExceptionInfo;
let exception = Exception::try_from(exception_number as u8).expect("Invalid exception number");
if exception_number == 14 && try_handle_internal_pagefault(exn_info, ctx, page_fault_address) {
return;
}
if exception_number < 31 {
let handler =
HANDLERS[exception_number as usize].load(core::sync::atomic::Ordering::Acquire);
if handler != 0 {
unsafe {
let handler = core::mem::transmute::<u64, ExceptionHandler>(handler);
if handler(exception_number, exn_info, ctx, page_fault_address) {
return;
}
};
}
}
let saved_rip = unsafe { (&raw const (*exn_info).rip).read_volatile() };
let error_code = unsafe { (&raw const (*exn_info).error_code).read_volatile() };
let bytes_at_rip = unsafe { (saved_rip as *const [u8; 8]).read_volatile() };
let mut w = HyperlightAbortWriter;
write_abort(&[ErrorCode::GuestError as u8, exception as u8]);
let write_res = write!(
w,
"Exception vector: {}\n\
Faulting Instruction: {:#x}\n\
Bytes At Faulting Instruction: {:?}\n\
Page Fault Address: {:#x}\n\
Error code: {:#x}\n\
Stack Pointer: {:#x}",
exception_number, saved_rip, bytes_at_rip, page_fault_address, error_code, stack_pointer
);
if write_res.is_err() {
write_abort("exception message format failed".as_bytes());
}
write_abort(&[0xFF]);
unreachable!();
}