Skip to content

Commit d550adc

Browse files
committed
Add test and fix alignement
Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent aa05a84 commit d550adc

File tree

5 files changed

+459
-38
lines changed

5 files changed

+459
-38
lines changed

src/hyperlight_guest_bin/src/exceptions/handler.rs

Lines changed: 149 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,46 +21,171 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
2121
use hyperlight_common::outb::Exception;
2222
use hyperlight_guest::exit::abort_with_code_and_message;
2323

24+
/// Error type for handler installation failures
25+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26+
pub enum InstallError {
27+
/// Exception vector must be in range 0-30 (architecture-defined exceptions only)
28+
InvalidVector,
29+
/// A handler is already installed for this vector
30+
HandlerAlreadyInstalled,
31+
}
32+
33+
/// Exception information pushed onto the stack by the Hyperlight exception handler.
34+
///
2435
/// See AMD64 Architecture Programmer's Manual, Volume 2
2536
/// §8.9.3 Interrupt Stack Frame, pp. 283--284
2637
/// Figure 8-14: Long-Mode Stack After Interrupt---Same Privilege,
2738
/// Figure 8-15: Long-Mode Stack After Interrupt---Higher Privilege
28-
/// Subject to the proviso that we push a dummy error code of 0 for exceptions
29-
/// for which the processor does not provide one
39+
/// Note: For exceptions that don't provide an error code, we push a dummy value of 0.
3040
#[repr(C)]
3141
pub struct ExceptionInfo {
42+
/// Error code provided by the processor (or 0 if not applicable).
3243
pub error_code: u64,
44+
/// Instruction pointer at the time of the exception.
3345
pub rip: u64,
46+
/// Code segment selector.
3447
pub cs: u64,
48+
/// CPU flags register.
3549
pub rflags: u64,
50+
/// Stack pointer at the time of the exception.
3651
pub rsp: u64,
52+
/// Stack segment selector.
3753
pub ss: u64,
3854
}
3955
const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rip) == 8);
4056
const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rsp) == 32);
4157

58+
/// Saved CPU context pushed onto the stack by exception entry code.
59+
///
60+
/// This structure contains all the saved CPU state needed to resume execution
61+
/// after handling an exception. It includes segment registers, floating-point state,
62+
/// and general-purpose registers.
4263
#[repr(C)]
43-
/// Saved context, pushed onto the stack by exception entry code
4464
pub struct Context {
45-
/// in order: gs, fs, es
46-
pub segments: [u64; 3],
65+
/// Segment registers in order: GS, FS, ES, DS.
66+
pub segments: [u64; 4],
67+
/// FPU/SSE state saved via FXSAVE instruction (512 bytes).
4768
pub fxsave: [u8; 512],
48-
pub ds: u64,
49-
/// no `rsp`, since the processor saved it
50-
/// `rax` is at the top, `r15` the bottom
69+
/// General-purpose registers (RAX through R15, excluding RSP).
70+
///
71+
/// The stack pointer (RSP) is not included here since it's saved
72+
/// by the processor in the `ExceptionInfo` structure.
73+
/// RAX is at index 0, R15 is at index 14.
5174
pub gprs: [u64; 15],
75+
/// Padding to ensure 16-byte alignment when combined with ExceptionInfo.
76+
padding: [u64; 1],
5277
}
53-
const _: () = assert!(size_of::<Context>() == 152 + 512);
78+
const _: () = assert!(size_of::<Context>() == 32 + 512 + 120 + 8);
79+
const _: () = assert!((size_of::<Context>() + size_of::<ExceptionInfo>()) % 16 == 0);
5480

55-
// TODO: This will eventually need to end up in a per-thread context,
56-
// when there are threads.
57-
pub static HANDLERS: [core::sync::atomic::AtomicU64; 31] =
81+
/// Array of installed exception handlers for vectors 0-30.
82+
///
83+
/// TODO: This will eventually need to be part of a per-thread context when threading is implemented.
84+
static HANDLERS: [core::sync::atomic::AtomicU64; 31] =
5885
[const { core::sync::atomic::AtomicU64::new(0) }; 31];
59-
pub type HandlerT = fn(n: u64, info: *mut ExceptionInfo, ctx: *mut Context, pf_addr: u64) -> bool;
6086

61-
/// Exception handler
87+
/// Exception handler function type.
88+
///
89+
/// Handlers receive mutable pointers to the exception information and CPU context,
90+
/// allowing direct access and modification of exception state.
91+
///
92+
/// # Parameters
93+
/// * `exception_number` - Exception vector number (0-30)
94+
/// * `exception_info` - Mutable pointer to exception information (instruction pointer, error code, etc.)
95+
/// * `context` - Mutable pointer to saved CPU context (registers, FPU state, etc.)
96+
/// * `page_fault_address` - Page fault address (only valid for page fault exceptions)
97+
///
98+
/// # Returns
99+
/// * `true` - Suppress the default abort behavior and continue execution
100+
/// * `false` - Allow the default abort to occur
101+
///
102+
/// # Safety
103+
/// This function type uses raw mutable pointers. Handlers must ensure:
104+
/// - Pointers are valid for the duration of the handler
105+
/// - Any modifications to exception state maintain system integrity
106+
/// - Modified values are valid for CPU state (e.g., valid instruction pointers, aligned stack pointers)
107+
pub type ExceptionHandler = fn(
108+
exception_number: u64,
109+
exception_info: *mut ExceptionInfo,
110+
context: *mut Context,
111+
page_fault_address: u64,
112+
) -> bool;
113+
114+
/// Install a custom exception handler for a specific vector.
115+
///
116+
/// # Arguments
117+
/// * `vector` - Exception vector (0-30). Must be an architecture-defined exception.
118+
/// * `handler` - The handler function to invoke when this exception occurs.
119+
///
120+
/// # Returns
121+
/// * `Ok(())` if the handler was successfully installed.
122+
/// * `Err(InstallError::InvalidVector)` if `vector >= 31`.
123+
/// * `Err(InstallError::HandlerAlreadyInstalled)` if a handler is already registered for this vector.
124+
///
125+
/// # Example
126+
/// ```ignore
127+
/// fn my_exception_handler(
128+
/// exception_number: u64,
129+
/// exception_info: *mut ExceptionInfo,
130+
/// context: *mut Context,
131+
/// page_fault_address: u64,
132+
/// ) -> bool {
133+
/// unsafe {
134+
/// // Read the faulting instruction pointer
135+
/// let faulting_rip = core::ptr::read_volatile(&(*exception_info).rip);
136+
///
137+
/// // Save the original RIP to R9 register
138+
/// core::ptr::write_volatile(&mut (*context).gprs[8], faulting_rip);
139+
///
140+
/// // Skip past the faulting instruction
141+
/// core::ptr::write_volatile(&mut (*exception_info).rip, faulting_rip + 2);
142+
/// }
143+
///
144+
/// true // Return true to suppress abort and continue execution
145+
/// }
146+
///
147+
/// install_handler(3, my_exception_handler)?; // Install for INT3 (breakpoint)
148+
/// ```
149+
pub fn install_handler(vector: u8, handler: ExceptionHandler) -> Result<(), InstallError> {
150+
if vector >= 31 {
151+
return Err(InstallError::InvalidVector);
152+
}
153+
154+
// Use compare_exchange to atomically check and set, preventing races
155+
match HANDLERS[vector as usize].compare_exchange(
156+
0,
157+
handler as usize as u64,
158+
core::sync::atomic::Ordering::AcqRel,
159+
core::sync::atomic::Ordering::Acquire,
160+
) {
161+
Ok(_) => Ok(()),
162+
Err(_) => Err(InstallError::HandlerAlreadyInstalled),
163+
}
164+
}
165+
166+
/// Remove a custom exception handler for a specific vector.
167+
///
168+
/// # Arguments
169+
/// * `vector` - Exception vector (0-30).
170+
///
171+
/// # Returns
172+
/// * `Ok(())` if the handler was successfully removed (or was already uninstalled).
173+
/// * `Err(InstallError::InvalidVector)` if `vector >= 31`.
174+
pub fn uninstall_handler(vector: u8) -> Result<(), InstallError> {
175+
if vector >= 31 {
176+
return Err(InstallError::InvalidVector);
177+
}
178+
179+
HANDLERS[vector as usize].store(0, core::sync::atomic::Ordering::Release);
180+
Ok(())
181+
}
182+
183+
/// Internal exception handler invoked by the low-level exception entry code.
184+
///
185+
/// This function is called from assembly when an exception occurs. It checks for
186+
/// registered user handlers and either invokes them or aborts with an error message.
62187
#[unsafe(no_mangle)]
63-
pub extern "C" fn hl_exception_handler(
188+
pub(crate) extern "C" fn hl_exception_handler(
64189
stack_pointer: u64,
65190
exception_number: u64,
66191
page_fault_address: u64,
@@ -82,20 +207,18 @@ pub extern "C" fn hl_exception_handler(
82207
exception_number, saved_rip, page_fault_address, error_code, stack_pointer
83208
);
84209

85-
// We don't presently have any need for user-defined interrupts,
86-
// so we only support handlers for the architecture-defined
87-
// vectors (0-31)
210+
// Check for registered user handlers (only for architecture-defined vectors 0-30)
88211
if exception_number < 31 {
89212
let handler =
90213
HANDLERS[exception_number as usize].load(core::sync::atomic::Ordering::Acquire);
91-
if handler != 0
92-
&& unsafe {
93-
core::mem::transmute::<u64, fn(u64, *mut ExceptionInfo, *mut Context, u64) -> bool>(
94-
handler,
95-
)(exception_number, exn_info, ctx, page_fault_address)
96-
}
97-
{
98-
return;
214+
if handler != 0 {
215+
unsafe {
216+
let handler = core::mem::transmute::<u64, ExceptionHandler>(handler);
217+
if handler(exception_number, exn_info, ctx, page_fault_address) {
218+
return;
219+
}
220+
// Handler returned false, fall through to abort
221+
};
99222
}
100223
}
101224

src/hyperlight_guest_bin/src/exceptions/interrupt_entry.rs

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ unsafe extern "C" {
5151
macro_rules! context_save {
5252
() => {
5353
concat!(
54+
// Push padding to match Context struct (8 bytes)
55+
" push 0\n",
5456
// Save general-purpose registers
5557
" push rax\n",
5658
" push rbx\n",
@@ -67,10 +69,6 @@ macro_rules! context_save {
6769
" push r13\n",
6870
" push r14\n",
6971
" push r15\n",
70-
// Save one of the segment registers to get 16-byte alignment for
71-
// FXSAVE. TODO: consider packing the segment registers
72-
" mov rax, ds\n",
73-
" push rax\n",
7472
// Save floating-point/SSE registers
7573
// TODO: Don't do this unconditionally: get the exn
7674
// handlers compiled without sse
@@ -79,7 +77,9 @@ macro_rules! context_save {
7977
" sub rsp, 512\n",
8078
" mov rax, rsp\n",
8179
" fxsave [rax]\n",
82-
// Save the rest of the segment registers
80+
// Save all segment registers
81+
" mov rax, ds\n",
82+
" push rax\n",
8383
" mov rax, es\n",
8484
" push rax\n",
8585
" mov rax, fs\n",
@@ -93,20 +93,19 @@ macro_rules! context_save {
9393
macro_rules! context_restore {
9494
() => {
9595
concat!(
96-
// Restore most segment registers
96+
// Restore all segment registers
9797
" pop rax\n",
9898
" mov gs, rax\n",
9999
" pop rax\n",
100100
" mov fs, rax\n",
101101
" pop rax\n",
102102
" mov es, rax\n",
103+
" pop rax\n",
104+
" mov ds, rax\n",
103105
// Restore floating-point/SSE registers
104106
" mov rax, rsp\n",
105107
" fxrstor [rax]\n",
106108
" add rsp, 512\n",
107-
// Restore the last segment register
108-
" pop rax\n",
109-
" mov ds, rax\n",
110109
// Restore general-purpose registers
111110
" pop r15\n",
112111
" pop r14\n",
@@ -123,6 +122,8 @@ macro_rules! context_restore {
123122
" pop rcx\n",
124123
" pop rbx\n",
125124
" pop rax\n",
125+
// Skip padding (8 bytes)
126+
" add rsp, 8\n",
126127
)
127128
};
128129
}
@@ -178,6 +179,43 @@ macro_rules! generate_exceptions {
178179
// mov rdx, 0
179180
// jmp _do_excp_common
180181
// ```
182+
//
183+
// Stack layout after context_save!() (from high to low addresses):
184+
// ```
185+
// +------------------+ <-- Higher addresses
186+
// | SS | (Pushed by CPU on exception)
187+
// | RSP | (Pushed by CPU on exception)
188+
// | RFLAGS | (Pushed by CPU on exception)
189+
// | CS | (Pushed by CPU on exception)
190+
// | RIP | (Pushed by CPU on exception)
191+
// | Error Code | (Pushed by CPU or by handler)
192+
// +------------------+ <-- ExceptionInfo struct starts here
193+
// | Padding (8) | (Pushed by context_save!)
194+
// +------------------+
195+
// | R15 | gprs[14]
196+
// | R14 | gprs[13]
197+
// | R13 | gprs[12]
198+
// | R12 | gprs[11]
199+
// | R11 | gprs[10]
200+
// | R10 | gprs[9]
201+
// | R9 | gprs[8]
202+
// | R8 | gprs[7]
203+
// | RBP | gprs[6]
204+
// | RDI | gprs[5]
205+
// | RSI | gprs[4]
206+
// | RDX | gprs[3]
207+
// | RCX | gprs[2]
208+
// | RBX | gprs[1]
209+
// | RAX | gprs[0] (15 GPRs total, 120 bytes)
210+
// +------------------+
211+
// | FXSAVE area | (512 bytes for FPU/SSE state)
212+
// +------------------+
213+
// | GS | segments[3]
214+
// | FS | segments[2]
215+
// | ES | segments[1]
216+
// | DS | segments[0] (4 segment registers, 32 bytes)
217+
// +------------------+ <-- Context struct starts here
218+
// ```
181219
macro_rules! generate_excp {
182220
($num:expr) => {
183221
concat!(

src/hyperlight_host/tests/common/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616
use hyperlight_host::func::HostFunction;
17-
use hyperlight_host::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox};
1817
#[cfg(gdb)]
1918
use hyperlight_host::sandbox::config::DebugInfo;
19+
use hyperlight_host::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox};
2020
use hyperlight_testing::{c_simple_guest_as_string, simple_guest_as_string};
2121

2222
/// Returns a rust/c simpleguest depending on environment variable GUEST.
@@ -37,13 +37,13 @@ pub fn new_uninit_rust() -> Result<UninitializedSandbox> {
3737
let mut cfg = SandboxConfiguration::default();
3838
let debug_info = DebugInfo { port: 8080 };
3939
cfg.set_guest_debug_info(debug_info);
40-
40+
4141
UninitializedSandbox::new(
4242
GuestBinary::FilePath(simple_guest_as_string().unwrap()),
4343
Some(cfg),
4444
)
4545
}
46-
46+
4747
#[cfg(not(gdb))]
4848
UninitializedSandbox::new(
4949
GuestBinary::FilePath(simple_guest_as_string().unwrap()),

0 commit comments

Comments
 (0)