panic_serial/lib.rs
1//! Prints panic information via a serial port, then goes into an infinite loop.
2//!
3//! Status: experimental; biased towards Arduino
4//!
5//! This crate implements a panic handler which prints panic information on a serial port (or other type of output - see below).
6//!
7//! ## Why?
8//!
9//! Seeing panic messages (or at least their location) is essential to make sense of what went wrong.
10//!
11//! I don't want to live without it.
12//!
13//! ## What is printed?
14//!
15//! There are three levels of detail at which panics can be printed, depending on how much space you are willing to waste in your firmware.
16//! The level of detail is chosen by selecting **feature flags**:
17//! - `location`: prints location information.
18//! Example:
19//! ```
20//! Panic at src/main.rs:91:9
21//! ```
22//! - `message`: prints the actual full panic message. This uses `core::fmt` under the hood, so expect an increase in firmware size.
23//! Example:
24//! ```
25//! attempt to subtract with overflow
26//! ```
27//! - `full` == `location` & `message`: Combined location and message.
28//! Example:
29//! ```
30//! Panic at src/main.rs:91:9: attempt to subtract with overflow
31//! ```
32//! - (no features): if no features are chosen, a static message is printed.
33//! Example:
34//! ```
35//! PANIC !
36//! ```
37//! This option is easiest on firmware size.
38//!
39//! ## Usage
40//!
41//! An example project for Arduino Uno based on these instructions can be found here: <https://github.com/nilclass/panic-serial-example>.
42//!
43//! 1. Remove any existing panic handler. For example if you are currently using `panic_halt`, remove that dependency & it's usage.
44//! 2. Add `panic-serial` dependency to your project:
45//! ```sh
46//! # Check "What is printed" section above for features to choose
47//! cargo add panic-serial --features full
48//! ```
49//! 3. Within your `main.rs` (or elsewhere at top level) invoke the `impl_panic_handler` macro:
50//! ```
51//! panic_serial::impl_panic_handler!(
52//! // This is the type of the UART port to use for printing the message:
53//! arduino_hal::usart::Usart<
54//! arduino_hal::pac::USART0,
55//! arduino_hal::port::Pin<arduino_hal::port::mode::Input, arduino_hal::hal::port::PD0>,
56//! arduino_hal::port::Pin<arduino_hal::port::mode::Output, arduino_hal::hal::port::PD1>
57//! >
58//! );
59//! ```
60//! This will do two things:
61//! - define the actual panic handler
62//! - define a function called `share_serial_port_with_panic`, which we'll use in the next step
63//! 4. Call `share_serial_port_with_panic` within `main`:
64//! ```
65//! #[arduino_hal::entry]
66//! fn main() -> ! {
67//! // ...
68//! let serial = arduino_hal::default_serial!(dp, pins, 57600);
69//! // this gives ownership of the serial port to panic-serial. We receive a mutable reference to it though, so we can keep using it.
70//! let serial = share_serial_port_with_panic(serial);
71//! // continue using serial:
72//! ufmt::uwriteln!(serial, "Hello there!\r").unwrap();
73//!
74//! // ...
75//! }
76//! ```
77//!
78//! ## How does it work?
79//!
80//! The `impl_panic_handler` macro defines a mutable static `PANIC_PORT: Option<$your_type>`.
81//! When you call `share_serial_port_with_panic`, that option gets filled, and you get back `PANIC_PORT.as_mut().unwrap()`.
82//!
83//! If a panic happens, the panic handler either just loops (if you never called `share_serial_port_with_panic`), or prints
84//! the panic info to the given port.
85//! It does this in two steps:
86//! 1. call `port.flush()`
87//! 2. use `ufmt` (or `core::fmt`) to print the fragments.
88//!
89//! Technically this works with *anything* that implements `ufmt::uWrite` and has a `flush()` method.
90//!
91//! ## How unsafe is this?
92//!
93//! When you find out, please tell me.
94//!
95
96#![no_std]
97#![feature(panic_info_message)]
98
99use ufmt::uWrite;
100use core::panic::PanicInfo;
101use core::fmt::Write;
102
103struct WriteWrapper<'a, W: uWrite>(&'a mut W);
104
105impl<'a, W: uWrite> Write for WriteWrapper<'a, W> {
106 fn write_str(&mut self, s: &str) -> core::fmt::Result {
107 self.0.write_str(s).map_err(|_| core::fmt::Error)
108 }
109}
110
111/// Called internally by the panic handler.
112pub fn _print_panic<W: uWrite>(w: &mut W, info: &PanicInfo) {
113 let location_feature = cfg!(feature="location");
114 let message_feature = cfg!(feature="message");
115
116 if location_feature {
117 if let Some(location) = info.location() {
118 _ = ufmt::uwrite!(w, "Panic at {}:{}:{}", location.file(), location.line(), location.column());
119 _ = w.write_str(if message_feature { ": " } else { "\r\n" });
120 }
121 }
122
123 if message_feature {
124 if let Some(message) = info.message() {
125 _ = core::fmt::write(&mut WriteWrapper(w), *message);
126 _ = w.write_str("\r\n");
127 }
128 }
129
130 if !message_feature && !location_feature {
131 _ = ufmt::uwriteln!(w, "PANIC !\r");
132 }
133}
134
135/// Implements the panic handler. You need to call this for the package to work.
136///
137/// This macro defines the panic handler, as well as a function called `share_serial_port_with_panic`.
138/// That function takes an argument of the given `$type` and returns a `&'static mut $type`.
139///
140#[macro_export]
141macro_rules! impl_panic_handler {
142 ($type:ty) => {
143 static mut PANIC_PORT: Option<$type> = None;
144
145 #[inline(never)]
146 #[panic_handler]
147 fn panic(info: &::core::panic::PanicInfo) -> ! {
148 if let Some(panic_port) = unsafe { PANIC_PORT.as_mut() } {
149 _ = panic_port.flush();
150 ::panic_serial::_print_panic(panic_port, info);
151 }
152 loop {
153 ::core::sync::atomic::compiler_fence(::core::sync::atomic::Ordering::SeqCst);
154 }
155 }
156
157 pub fn share_serial_port_with_panic(port: $type) -> &'static mut $type {
158 unsafe {
159 PANIC_PORT = Some(port);
160 PANIC_PORT.as_mut().unwrap()
161 }
162 }
163 };
164}