hyperlight_guest_bin/exceptions/handler.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 alloc::format;
18use core::ffi::c_char;
19
20use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
21use hyperlight_common::outb::Exception;
22use hyperlight_guest::exit::abort_with_code_and_message;
23
24/// Exception information pushed onto the stack by the CPU during an excpection.
25///
26/// See AMD64 Architecture Programmer's Manual, Volume 2
27/// ยง8.9.3 Interrupt Stack Frame, pp. 283--284
28/// Figure 8-14: Long-Mode Stack After Interrupt---Same Privilege,
29/// Figure 8-15: Long-Mode Stack After Interrupt---Higher Privilege
30/// Note: For exceptions that don't provide an error code, we push a dummy value of 0.
31#[repr(C)]
32pub struct ExceptionInfo {
33 /// Error code provided by the processor (or 0 if not applicable).
34 pub error_code: u64,
35 /// Instruction pointer at the time of the exception.
36 pub rip: u64,
37 /// Code segment selector.
38 pub cs: u64,
39 /// CPU flags register.
40 pub rflags: u64,
41 /// Stack pointer at the time of the exception.
42 pub rsp: u64,
43 /// Stack segment selector.
44 pub ss: u64,
45}
46const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rip) == 8);
47const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rsp) == 32);
48
49/// Saved CPU context pushed onto the stack by exception entry code.
50///
51/// This structure contains all the saved CPU state needed to resume execution
52/// after handling an exception. It includes segment registers, floating-point state,
53/// and general-purpose registers.
54#[repr(C)]
55pub struct Context {
56 /// Segment registers in order: GS, FS, ES, DS.
57 pub segments: [u64; 4],
58 /// FPU/SSE state saved via FXSAVE instruction (512 bytes).
59 pub fxsave: [u8; 512],
60 /// General-purpose registers (RAX through R15, excluding RSP).
61 ///
62 /// The stack pointer (RSP) is not included here since it's saved
63 /// by the processor in the `ExceptionInfo` structure.
64 /// R15 is at index 0, RAX is at index 14.
65 pub gprs: [u64; 15],
66 /// Padding to ensure 16-byte alignment when combined with ExceptionInfo.
67 padding: [u64; 1],
68}
69const _: () = assert!(size_of::<Context>() == 32 + 512 + 120 + 8);
70// The combination of the ExceptionInfo (pushed by the CPU) and the register Context
71// that we save to the stack must be 16byte aligned before calling the hl_exception_handler
72// as specified in the x86-64 ELF System V psABI specification, Section 3.2.2:
73//
74// https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build
75const _: () = assert!((size_of::<Context>() + size_of::<ExceptionInfo>()) % 16 == 0);
76
77/// Array of installed exception handlers for vectors 0-30.
78///
79/// TODO: This will eventually need to be part of a per-thread context when threading is implemented.
80pub static HANDLERS: [core::sync::atomic::AtomicU64; 31] =
81 [const { core::sync::atomic::AtomicU64::new(0) }; 31];
82
83/// Exception handler function type.
84///
85/// Handlers receive mutable pointers to the exception information and CPU context,
86/// allowing direct access and modification of exception state.
87///
88/// # Parameters
89/// * `exception_number` - Exception vector number (0-30)
90/// * `exception_info` - Mutable pointer to exception information (instruction pointer, error code, etc.)
91/// * `context` - Mutable pointer to saved CPU context (registers, FPU state, etc.)
92/// * `page_fault_address` - Page fault address (only valid for page fault exceptions)
93///
94/// # Returns
95/// * `true` - Suppress the default abort behavior and continue execution
96/// * `false` - Allow the default abort to occur
97///
98/// # Safety
99/// This function type uses raw mutable pointers. Handlers must ensure:
100/// - Pointers are valid for the duration of the handler
101/// - Any modifications to exception state maintain system integrity
102/// - Modified values are valid for CPU state (e.g., valid instruction pointers, aligned stack pointers)
103pub type ExceptionHandler = fn(
104 exception_number: u64,
105 exception_info: *mut ExceptionInfo,
106 context: *mut Context,
107 page_fault_address: u64,
108) -> bool;
109
110/// Internal exception handler invoked by the low-level exception entry code.
111///
112/// This function is called from assembly when an exception occurs. It checks for
113/// registered user handlers and either invokes them or aborts with an error message.
114#[unsafe(no_mangle)]
115pub(crate) extern "C" fn hl_exception_handler(
116 stack_pointer: u64,
117 exception_number: u64,
118 page_fault_address: u64,
119) {
120 let ctx = stack_pointer as *mut Context;
121 let exn_info = (stack_pointer + size_of::<Context>() as u64) as *mut ExceptionInfo;
122
123 let exception = Exception::try_from(exception_number as u8).expect("Invalid exception number");
124
125 let saved_rip = unsafe { (&raw const (*exn_info).rip).read_volatile() };
126 let error_code = unsafe { (&raw const (*exn_info).error_code).read_volatile() };
127
128 let msg = format!(
129 "Exception vector: {:#}\n\
130 Faulting Instruction: {:#x}\n\
131 Page Fault Address: {:#x}\n\
132 Error code: {:#x}\n\
133 Stack Pointer: {:#x}",
134 exception_number, saved_rip, page_fault_address, error_code, stack_pointer
135 );
136
137 // Check for registered user handlers (only for architecture-defined vectors 0-30)
138 if exception_number < 31 {
139 let handler =
140 HANDLERS[exception_number as usize].load(core::sync::atomic::Ordering::Acquire);
141 if handler != 0 {
142 unsafe {
143 let handler = core::mem::transmute::<u64, ExceptionHandler>(handler);
144 if handler(exception_number, exn_info, ctx, page_fault_address) {
145 return;
146 }
147 // Handler returned false, fall through to abort
148 };
149 }
150 }
151
152 unsafe {
153 abort_with_code_and_message(
154 &[ErrorCode::GuestError as u8, exception as u8],
155 msg.as_ptr() as *const c_char,
156 );
157 }
158}