Next Generation WASM Microkernel Operating System
at trap_handler 249 lines 9.4 kB view raw
1// Copyright 2025 Jonas Kruckenberg 2// 3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or 4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or 5// http://opensource.org/licenses/MIT>, at your option. This file may not be 6// copied, modified, or distributed except according to those terms. 7 8#![no_std] // this is crate is fully incompatible with `std` due to clashing lang item definitions 9#![cfg(target_os = "none")] 10#![expect(internal_features, reason = "lang items")] 11#![feature( 12 core_intrinsics, 13 rustc_attrs, 14 used_with_arg, 15 lang_items, 16 naked_functions, 17 never_type 18)] 19 20extern crate alloc; 21 22mod arch; 23mod eh_action; 24mod eh_info; 25mod error; 26mod exception; 27mod frame; 28mod lang_items; 29mod utils; 30 31use abort::abort; 32use alloc::boxed::Box; 33use core::any::Any; 34use core::intrinsics; 35use core::mem::ManuallyDrop; 36use core::panic::UnwindSafe; 37use core::ptr::addr_of_mut; 38use eh_action::{EHAction, find_eh_action}; 39pub use eh_info::EhInfo; 40pub use error::Error; 41use exception::Exception; 42use fallible_iterator::FallibleIterator; 43pub use frame::{Frame, FrameIter}; 44use lang_items::ensure_personality_stub; 45pub use utils::with_context; 46 47pub use arch::Registers; 48 49pub(crate) type Result<T> = core::result::Result<T, Error>; 50 51/// Begin unwinding the stack. 52/// 53/// Unwinding will walk up the stack, calling [`Drop`] handlers along the way to perform cleanup until 54/// it reaches a [`catch_unwind`] handler. 55/// 56/// When reached, control is transferred to the [`catch_unwind`] handler with the `payload` argument 57/// returned in the `Err` variant of the [`catch_unwind`] return. In that case, this function will *not* 58/// return. 59/// 60/// # Errors 61/// 62/// If there is no [`catch_unwind`] handler anywhere in the call chain then this function returns 63/// `Err(Error::EndOfStack)`. This roughly equivalent to an uncaught exception in C++ and should 64/// be treated as a fatal error. 65pub fn begin_unwind(payload: Box<dyn Any + Send>) -> Result<!> { 66 with_context(|regs, pc| { 67 let frames = FrameIter::from_registers(regs.clone(), pc); 68 69 raise_exception_phase2(frames, Exception::wrap(payload)) 70 }) 71} 72 73/// Begin unwinding *a* stack. The specific stack location at which unwinding will begin is determined 74/// by the register set and program counter provided. 75/// 76/// Unwinding will walk up the stack, calling [`Drop`] handlers along the way to perform cleanup until 77/// it reaches a [`catch_unwind`] handler. 78/// 79/// When reached, control is transferred to the [`catch_unwind`] handler with the `payload` argument 80/// returned in the `Err` variant of the [`catch_unwind`] return. In that case, this function will *not* 81/// return. 82/// 83/// # Errors 84/// 85/// If there is no [`catch_unwind`] handler anywhere in the call chain then this function returns 86/// `Err(Error::EndOfStack)`. This roughly equivalent to an uncaught exception in C++ and should 87/// be treated as a fatal error. 88/// 89/// # Safety 90/// 91/// This function does not perform any checking of the provided register values, if they are incorrect 92/// this might lead to segfaults. 93pub unsafe fn begin_unwind_with( 94 payload: Box<dyn Any + Send>, 95 regs: Registers, 96 pc: usize, 97) -> Result<!> { 98 let frames = FrameIter::from_registers(regs, pc); 99 100 raise_exception_phase2(frames, Exception::wrap(payload)) 101} 102 103/// Walk up the stack until either a landing pad is encountered or we reach the end of the stack. 104/// 105/// If a landing pad is found control is transferred to it and this function will not return, if there 106/// is no landing pad, this function will return `Err(Error::EndOfStack)`. 107/// 108/// Note that the traditional unwinding process has 2 phases, the first where the landing pad is discovered 109/// and the second where the stack is actually unwound up to that landing pad. 110/// In `unwind2` we can get away with one phase because we bypass the language personality routine: 111/// Traditional unwinders call the personality routine on each frame to discover a landing pad, and 112/// then during cleanup call the personality routine again to determine if control should actually be 113/// transferred. This is done so that languages have maximum flexibility in how they treat exceptions. 114/// 115/// `unwind2` - being Rust-only - doesn't need that flexibility since Rust landing pads are called 116/// unconditionally. Furthermore, `unwind2` never actually calls the personality routine, instead 117/// parsing the [`EHAction`] for each frame directly. 118/// 119/// The name `raise_exception_phase2` is kept though to make it easier to understand what this function 120/// does when coming from traditional unwinders. 121fn raise_exception_phase2(mut frames: FrameIter, exception: *mut Exception) -> Result<!> { 122 while let Some(mut frame) = frames.next()? { 123 if frame 124 .personality() 125 .map(ensure_personality_stub) 126 .transpose()? 127 .is_none() 128 { 129 continue; 130 } 131 132 let Some(mut lsda) = frame.language_specific_data() else { 133 continue; 134 }; 135 136 let eh_action = find_eh_action(&mut lsda, &frame)?; 137 138 match eh_action { 139 EHAction::None => continue, 140 // Safety: As long as the Rust compiler works correctly lpad is the correct instruction 141 // pointer. 142 EHAction::Cleanup(lpad) | EHAction::Catch(lpad) => { 143 frame.set_reg(arch::UNWIND_DATA_REG.0, exception as usize); 144 frame.set_reg(arch::UNWIND_DATA_REG.1, 0); 145 frame.set_reg(arch::RA, usize::try_from(lpad).unwrap()); 146 frame.adjust_stack_for_args(); 147 148 // Safety: this will set up the frame context necessary to transfer control to the 149 // landing pad. Since that landing pad is generated by the Rust compiler there isn't 150 // much we can do except hope and pray that the instruction pointer is correct. 151 unsafe { frame.restore() } 152 } 153 } 154 } 155 156 Err(Error::EndOfStack) 157} 158 159/// Invokes the closure, capturing an unwind if one occurs. 160/// 161/// This function returns `Ok` if no unwind occurred or `Err` with the payload passed to [`begin_unwind`]. 162/// 163/// You can think of this function as a `try-catch` expression and [`begin_unwind`] as the `throw` 164/// counterpart. 165/// 166/// The closure provided is required to adhere to the [`UnwindSafe`] trait to ensure that all captured 167/// variables are safe to cross this boundary. The purpose of this bound is to encode the concept 168/// of [exception safety] in the type system. Most usage of this function should not need to worry about 169/// this bound as programs are naturally unwind safe without unsafe code. If it becomes a problem the 170/// [`AssertUnwindSafe`] wrapper struct can be used to quickly assert that the usage here is indeed 171/// unwind safe. 172/// 173/// # Errors 174/// 175/// Returns an error with the boxed panic payload when the provided closure panicked. 176/// 177/// [exception safety]: https://github.com/rust-lang/rfcs/blob/master/text/1236-stabilize-catch-panic.md 178/// [`UnwindSafe`]: core::panic::UnwindSafe 179/// [`AssertUnwindSafe`]: core::panic::AssertUnwindSafe 180pub fn catch_unwind<F, R>(f: F) -> core::result::Result<R, Box<dyn Any + Send + 'static>> 181where 182 F: FnOnce() -> R + UnwindSafe, 183{ 184 union Data<F, R> { 185 // when we start, this field holds the closure 186 f: ManuallyDrop<F>, 187 // when the closure completed successfully, this will hold the return 188 r: ManuallyDrop<R>, 189 // when the closure panicked this will hold the panic payload 190 p: ManuallyDrop<Box<dyn Any + Send>>, 191 } 192 193 #[inline] 194 fn do_call<F: FnOnce() -> R, R>(data: *mut u8) { 195 // SAFETY: this is the responsibility of the caller, see above. 196 unsafe { 197 let data = data.cast::<Data<F, R>>(); 198 let data = &mut (*data); 199 let f = ManuallyDrop::take(&mut data.f); 200 data.r = ManuallyDrop::new(f()); 201 } 202 } 203 204 #[cold] 205 #[rustc_nounwind] // `intrinsic::catch_unwind` requires catch fn to be nounwind 206 fn do_catch<F: FnOnce() -> R, R>(data: *mut u8, exception: *mut u8) { 207 let data = data.cast::<Data<F, R>>(); 208 // Safety: data is correctly initialized 209 let data = unsafe { &mut (*data) }; 210 211 // Safety: exception comes from the Rust intrinsic, not much we do other than trust it 212 match unsafe { Exception::unwrap(exception.cast()) } { 213 Ok(p) => data.p = ManuallyDrop::new(p), 214 Err(err) => { 215 tracing::error!("Failed to catch exception: {err:?}"); 216 abort(); 217 } 218 } 219 } 220 221 let mut data = Data { 222 f: ManuallyDrop::new(f), 223 }; 224 let data_ptr = addr_of_mut!(data).cast::<u8>(); 225 226 // Safety: intrinsic call 227 unsafe { 228 if intrinsics::catch_unwind(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 { 229 Ok(ManuallyDrop::into_inner(data.r)) 230 } else { 231 Err(ManuallyDrop::into_inner(data.p)) 232 } 233 } 234} 235 236#[cfg(test)] 237mod tests { 238 use super::*; 239 use alloc::boxed::Box; 240 241 #[test] 242 fn begin_and_catch_roundtrip() { 243 let res = catch_unwind(|| { 244 begin_unwind(Box::new(42)).unwrap(); 245 }) 246 .map_err(|err| *err.downcast_ref::<i32>().unwrap()); 247 assert_eq!(res, Err(42)); 248 } 249}