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.
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(())
}
}