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}