Next Generation WASM Microkernel Operating System
at trap_handler 208 lines 6.7 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#![feature(panic_can_unwind)] 11#![expect(internal_features, reason = "")] 12#![feature(std_internals)] 13#![feature(formatting_options)] 14#![feature(never_type)] 15#![feature(thread_local)] 16 17extern crate alloc; 18 19mod hook; 20mod panic_count; 21 22use crate::hook::{HOOK, Hook, PanicHookInfo, default_hook}; 23use crate::panic_count::MustAbort; 24use abort::abort; 25use alloc::boxed::Box; 26use alloc::string::String; 27use core::any::Any; 28use core::panic::{PanicPayload, UnwindSafe}; 29use core::{fmt, mem}; 30pub use hook::{set_hook, take_hook}; 31 32/// Determines whether the current thread is unwinding because of panic. 33#[inline] 34pub fn panicking() -> bool { 35 !panic_count::count_is_zero() 36} 37 38/// Invokes a closure, capturing the cause of an unwinding panic if one occurs. 39/// 40/// # Errors 41/// 42/// If the given closure panics, the panic cause will be returned in the Err variant. 43pub fn catch_unwind<F, R>(f: F) -> Result<R, Box<dyn Any + Send + 'static>> 44where 45 F: FnOnce() -> R + UnwindSafe, 46{ 47 unwind2::catch_unwind(f).inspect_err(|_| { 48 panic_count::decrease(); // decrease the panic count, since we caught it 49 }) 50} 51 52/// Resume an unwind previously caught with [`catch_unwind`]. 53pub fn resume_unwind(payload: Box<dyn Any + Send>) -> ! { 54 debug_assert!(panic_count::increase(false).is_none()); 55 unwind2::with_context(|regs, pc| rust_panic(payload, regs.clone(), pc)) 56} 57 58/// Begin unwinding from an externally captured set of registers (such as from a trap handler). 59/// 60/// # Safety 61/// 62/// This will start walking the stack and calling `Drop` implementations starting the the `pc` and 63/// register set you provided. Be VERY careful that it is actually correctly captured. 64pub unsafe fn begin_unwind(payload: Box<dyn Any + Send>, regs: unwind2::Registers, pc: usize) -> ! { 65 debug_assert!(panic_count::increase(false).is_none()); 66 rust_panic(payload, regs, pc) 67} 68 69#[panic_handler] 70fn panic(info: &core::panic::PanicInfo) -> ! { 71 if let Some(must_abort) = panic_count::increase(true) { 72 match must_abort { 73 MustAbort::PanicInHook => { 74 tracing::error!("{info}"); 75 } 76 } 77 78 tracing::error!("cpu panicked while processing panic. aborting."); 79 abort(); 80 } 81 82 let loc = info.location().unwrap(); // Currently always returns Some 83 let payload = construct_panic_payload(info); 84 85 let info = &PanicHookInfo::new(loc, payload.as_ref(), info.can_unwind()); 86 match *HOOK.read() { 87 Hook::Default => { 88 default_hook(info); 89 } 90 Hook::Custom(hook) => { 91 hook(info); 92 } 93 } 94 95 panic_count::finished_panic_hook(); 96 97 if !info.can_unwind() { 98 // If a thread panics while running destructors or tries to unwind 99 // through a nounwind function (e.g. extern "C") then we cannot continue 100 // unwinding and have to abort immediately. 101 tracing::error!("cpu caused non-unwinding panic. aborting."); 102 abort(); 103 } 104 105 unwind2::with_context(|regs, pc| rust_panic(payload, regs.clone(), pc)) 106} 107 108/// Mirroring std, this is an unmangled function on which to slap 109/// yer breakpoints for backtracing panics. 110#[inline(never)] 111#[unsafe(no_mangle)] 112fn rust_panic(payload: Box<dyn Any + Send>, regs: unwind2::Registers, pc: usize) -> ! { 113 // Safety: `begin_unwind` will either return an error or not return at all 114 match unsafe { unwind2::begin_unwind_with(payload, regs, pc).unwrap_err_unchecked() } { 115 unwind2::Error::EndOfStack => { 116 tracing::error!( 117 "unwinding completed without finding a `catch_unwind` make sure there is at least a root level catch unwind wrapping the main function. aborting." 118 ); 119 abort(); 120 } 121 err => { 122 tracing::error!("unwinding failed with error {}. aborting.", err); 123 abort() 124 } 125 } 126} 127 128fn construct_panic_payload(info: &core::panic::PanicInfo) -> Box<dyn Any + Send> { 129 struct FormatStringPayload<'a> { 130 inner: &'a core::panic::PanicMessage<'a>, 131 string: Option<String>, 132 } 133 134 impl FormatStringPayload<'_> { 135 fn fill(&mut self) -> &mut String { 136 let inner = self.inner; 137 // Lazily, the first time this gets called, run the actual string formatting. 138 self.string.get_or_insert_with(|| { 139 let mut s = String::new(); 140 let mut fmt = fmt::Formatter::new(&mut s, fmt::FormattingOptions::new()); 141 let _err = fmt::Display::fmt(&inner, &mut fmt); 142 s 143 }) 144 } 145 } 146 147 // Safety: TODO 148 unsafe impl PanicPayload for FormatStringPayload<'_> { 149 fn take_box(&mut self) -> *mut (dyn Any + Send) { 150 let contents = mem::take(self.fill()); 151 Box::into_raw(Box::new(contents)) 152 } 153 154 fn get(&mut self) -> &(dyn Any + Send) { 155 self.fill() 156 } 157 } 158 159 impl fmt::Display for FormatStringPayload<'_> { 160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 161 if let Some(s) = &self.string { 162 f.write_str(s) 163 } else { 164 fmt::Display::fmt(&self.inner, f) 165 } 166 } 167 } 168 169 struct StaticStrPayload(&'static str); 170 171 // Safety: TODO 172 unsafe impl PanicPayload for StaticStrPayload { 173 fn take_box(&mut self) -> *mut (dyn Any + Send) { 174 Box::into_raw(Box::new(self.0)) 175 } 176 177 fn get(&mut self) -> &(dyn Any + Send) { 178 &self.0 179 } 180 181 fn as_str(&mut self) -> Option<&str> { 182 Some(self.0) 183 } 184 } 185 186 impl fmt::Display for StaticStrPayload { 187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 188 f.write_str(self.0) 189 } 190 } 191 192 let msg = info.message(); 193 if let Some(s) = msg.as_str() { 194 // Safety: take_box returns an unwrapped box 195 unsafe { Box::from_raw(StaticStrPayload(s).take_box()) } 196 } else { 197 // Safety: take_box returns an unwrapped box 198 unsafe { 199 Box::from_raw( 200 FormatStringPayload { 201 inner: &msg, 202 string: None, 203 } 204 .take_box(), 205 ) 206 } 207 } 208}