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

213 Views Asked by At

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.

1

There are 1 best solutions below

3
Finomnis On BEST ANSWER

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: (Important: 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(())
    }
}