rustrust-no-std

How do I get the panic message in Rust when using #[no_std]?


I am writing embedded Rust code that uses #[no_std] (with panic = "abort").

I have a custom panic handler like this:

#[inline(never)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    let mut serial = open_serial_port();
    if let Some(location) = info.location() {
        uwriteln!(&mut serial, "Panic occurred in file '{}' at line {}", location.file(), location.line());
    } else {
        uwriteln!(&mut serial, "Panic occurred");
    }

    // I want to to get info.payload(), info.message(), info.to_string() or any other way
    // to get some info about why the panic happened

    loop {
        // blink LED
    }
}

This works great for showing the location of the panic, but it doesn't show the reason the panic happened.

I've seen a pattern like:

if let Some(s) = info.payload().downcast_ref::<String>() {
    // print s
}

but it seems like I don't have access to String in no_std. Is there another way to extract that information?

I saw someone recommending that I use a custom payload type that I can print, but I can't figure out how. I don't really care about the panic!() macro. I'm more concerned with catching rust's debug checks like arithmetic overflow, etc. Is there a way to tell the compiler how to generate panic payloads for those? I have been looking but haven't been able to find any examples of that.


Solution

  • PanicHandler implements Display. You can use that to print the normal panic message as seen on an std target.

    This is taken from imxrt-uart-panic (disclaimer: I wrote that crate):

    #[panic_handler]
    fn panic(info: &::core::panic::PanicInfo) -> ! {
        use ::core::fmt::Write as _;
    
        /* ... */
    
        struct UartWriter<P, const N: u8> {
            uart: hal::lpuart::Lpuart<P, N>,
        }
        impl<P, const N: u8> ::core::fmt::Write for UartWriter<P, N> {
            fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
                for &b in s.as_bytes() {
                    if b == b'\n' {
                        let _ = block!(self.uart.write(b'\r'));
                    }
                    let _ = block!(self.uart.write(b));
                }
                Ok(())
            }
        }
    
        let mut uart = UartWriter { uart };
    
        ::core::writeln!(uart).ok();
        ::core::writeln!(uart, "{}", info).ok();
        ::core::writeln!(uart).ok();
    
        let _ = block!(uart.uart.flush());
            
        /* ... */
    }
    

    The gist is that you create a struct that implements core::fmt::Write. With it, you can then core::writeln!() the error message into whatever peripheral you desire, like UART.

    An example of what this would print:

    fn main() -> ! {
        panic!("Foo!");
    }
    
    panicked at examples\minimal.rs:11:5:
    Foo!
    

    Just for reference, if customization is required, this is the current implementation of the Display impl of PanicInfo: (Disclaimer: This might change in the future.)

    impl fmt::Display for PanicInfo<'_> {
        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            formatter.write_str("panicked at ")?;
            self.location.fmt(formatter)?;
            if let Some(message) = self.message {
                formatter.write_str(":\n")?;
                formatter.write_fmt(*message)?;
            } else if let Some(payload) = self.payload.downcast_ref::<&'static str>() {
                formatter.write_str(":\n")?;
                formatter.write_str(payload)?;
            }
            // NOTE: we cannot use downcast_ref::<String>() here
            // since String is not available in core!
            // The payload is a String when `std::panic!` is called with multiple arguments,
            // but in that case the message is also available.
            Ok(())
        }
    }