Skip to content

Commit c8408e4

Browse files
authored
Update the interrupt handler for 16byte alignment (#1037)
* Make the tests debuggable Signed-off-by: James Sturtevant <[email protected]> * Add test and fix alignement Signed-off-by: James Sturtevant <[email protected]> --------- Signed-off-by: James Sturtevant <[email protected]>
1 parent 5a0e259 commit c8408e4

File tree

5 files changed

+277
-34
lines changed

5 files changed

+277
-34
lines changed

src/hyperlight_guest_bin/src/exceptions/handler.rs

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,46 +21,98 @@ 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+
/// Exception information pushed onto the stack by the CPU during an excpection.
25+
///
2426
/// See AMD64 Architecture Programmer's Manual, Volume 2
2527
/// §8.9.3 Interrupt Stack Frame, pp. 283--284
2628
/// Figure 8-14: Long-Mode Stack After Interrupt---Same Privilege,
2729
/// 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
30+
/// Note: For exceptions that don't provide an error code, we push a dummy value of 0.
3031
#[repr(C)]
3132
pub struct ExceptionInfo {
33+
/// Error code provided by the processor (or 0 if not applicable).
3234
pub error_code: u64,
35+
/// Instruction pointer at the time of the exception.
3336
pub rip: u64,
37+
/// Code segment selector.
3438
pub cs: u64,
39+
/// CPU flags register.
3540
pub rflags: u64,
41+
/// Stack pointer at the time of the exception.
3642
pub rsp: u64,
43+
/// Stack segment selector.
3744
pub ss: u64,
3845
}
3946
const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rip) == 8);
4047
const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rsp) == 32);
4148

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.
4254
#[repr(C)]
43-
/// Saved context, pushed onto the stack by exception entry code
4455
pub struct Context {
45-
/// in order: gs, fs, es
46-
pub segments: [u64; 3],
56+
/// Segment registers in order: GS, FS, ES, DS.
57+
pub segments: [u64; 4],
58+
/// FPU/SSE state saved via FXSAVE instruction (512 bytes).
4759
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
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.
5165
pub gprs: [u64; 15],
66+
/// Padding to ensure 16-byte alignment when combined with ExceptionInfo.
67+
padding: [u64; 1],
5268
}
53-
const _: () = assert!(size_of::<Context>() == 152 + 512);
69+
const _: () = 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
75+
const _: () = assert!((size_of::<Context>() + size_of::<ExceptionInfo>()) % 16 == 0);
5476

55-
// TODO: This will eventually need to end up in a per-thread context,
56-
// when there are threads.
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.
5780
pub static HANDLERS: [core::sync::atomic::AtomicU64; 31] =
5881
[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;
6082

61-
/// Exception handler
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)
103+
pub 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.
62114
#[unsafe(no_mangle)]
63-
pub extern "C" fn hl_exception_handler(
115+
pub(crate) extern "C" fn hl_exception_handler(
64116
stack_pointer: u64,
65117
exception_number: u64,
66118
page_fault_address: u64,
@@ -82,20 +134,18 @@ pub extern "C" fn hl_exception_handler(
82134
exception_number, saved_rip, page_fault_address, error_code, stack_pointer
83135
);
84136

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)
137+
// Check for registered user handlers (only for architecture-defined vectors 0-30)
88138
if exception_number < 31 {
89139
let handler =
90140
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;
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+
};
99149
}
100150
}
101151

src/hyperlight_guest_bin/src/exceptions/interrupt_entry.rs

Lines changed: 46 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,42 @@ 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) <-- ExceptionInfo struct starts here
192+
// +------------------+
193+
// | Padding (8) | (Pushed by context_save!)
194+
// +------------------+
195+
// | RAX | gprs[14]
196+
// | RBX | gprs[13]
197+
// | RCX | gprs[12]
198+
// | RDX | gprs[11]
199+
// | RSI | gprs[10]
200+
// | RDI | gprs[9]
201+
// | RBP | gprs[8]
202+
// | R8 | gprs[7]
203+
// | R9 | gprs[6]
204+
// | R10 | gprs[5]
205+
// | R11 | gprs[4]
206+
// | R12 | gprs[3]
207+
// | R13 | gprs[2]
208+
// | R14 | gprs[1]
209+
// | R15 | 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) <-- Context struct starts here
217+
// ```
181218
macro_rules! generate_excp {
182219
($num:expr) => {
183220
concat!(

src/hyperlight_host/tests/common/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616
use hyperlight_host::func::HostFunction;
17+
#[cfg(gdb)]
18+
use hyperlight_host::sandbox::config::DebugInfo;
1719
use hyperlight_host::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox};
1820
use hyperlight_testing::{c_simple_guest_as_string, simple_guest_as_string};
1921

@@ -29,6 +31,20 @@ pub fn new_uninit() -> Result<UninitializedSandbox> {
2931

3032
/// Use this instead of the `new_uninit` if you want your test to only run with the rust guest, not the c guest
3133
pub fn new_uninit_rust() -> Result<UninitializedSandbox> {
34+
#[cfg(gdb)]
35+
{
36+
use hyperlight_host::sandbox::SandboxConfiguration;
37+
let mut cfg = SandboxConfiguration::default();
38+
let debug_info = DebugInfo { port: 8080 };
39+
cfg.set_guest_debug_info(debug_info);
40+
41+
UninitializedSandbox::new(
42+
GuestBinary::FilePath(simple_guest_as_string().unwrap()),
43+
Some(cfg),
44+
)
45+
}
46+
47+
#[cfg(not(gdb))]
3248
UninitializedSandbox::new(
3349
GuestBinary::FilePath(simple_guest_as_string().unwrap()),
3450
None,

src/hyperlight_host/tests/integration_test.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,3 +1633,34 @@ fn interrupt_infinite_moving_loop_stress_test() {
16331633
handle.join().unwrap();
16341634
}
16351635
}
1636+
1637+
#[test]
1638+
fn exception_handler_installation_and_validation() {
1639+
let mut sandbox: MultiUseSandbox = new_uninit_rust().unwrap().evolve().unwrap();
1640+
1641+
// Verify handler count starts at 0
1642+
let count: i32 = sandbox.call("GetExceptionHandlerCallCount", ()).unwrap();
1643+
assert_eq!(count, 0, "Handler should not have been called yet");
1644+
1645+
// Install handler for vector
1646+
sandbox.call::<()>("InstallHandler", 3i32).unwrap();
1647+
1648+
// Try to install again - should be able to overwrite
1649+
sandbox.call::<()>("InstallHandler", 3i32).unwrap();
1650+
1651+
// Trigger int3 exception
1652+
let trigger_result: i32 = sandbox.call("TriggerInt3", ()).unwrap();
1653+
assert_eq!(trigger_result, 0, "Exception should be handled gracefully");
1654+
1655+
// Verify handler was invoked
1656+
let count: i32 = sandbox.call("GetExceptionHandlerCallCount", ()).unwrap();
1657+
assert_eq!(count, 1, "Handler should have been called once");
1658+
1659+
// Trigger int3 exception
1660+
let trigger_result: i32 = sandbox.call("TriggerInt3", ()).unwrap();
1661+
assert_eq!(trigger_result, 0, "Exception should be handled gracefully");
1662+
1663+
// Verify handler was invoked a second time
1664+
let count: i32 = sandbox.call("GetExceptionHandlerCallCount", ()).unwrap();
1665+
assert_eq!(count, 2, "Handler should have been called twice");
1666+
}

0 commit comments

Comments
 (0)