Next Generation WASM Microkernel Operating System
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}