1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
//! # `panic-persist`
//!
//! Writes panic messages to a section of RAM
//!
//! This crate contains an implementation of `panic_fmt` that logs panic messages to a region of
//! RAM defined by the user, so that these messages can be retrieved on next boot, and handled
//! outside of panic context, by sending to a logging interface, writing to flash, etc.
//!
//! After logging the message to RAM, the device will be soft-reset automatically.
//!
//! Unlike other methods this allows to discover the panic reason post-mortem using normal program
//! control flow.
//!
//! Currently this crate was only tested on ARM Cortex-M architecture but should be easily portable
//! to other platforms as required.
//!
//! ## Usage
//!
//! ### Add a section to your linker script
//!
//! You will need to reserve a section of RAM to be used to persist messages. This section must be
//! large enough to hold the 8 byte header, as well as any panic messages you would like to persist.
//! If there is not suitable space in the section, the panic message will be truncated.
//!
//! This section should be outside of any other sections, to prevent program initialization from
//! zeroing or otherwise modifying these sections on boot.
//!
//! `memory.x` file before modification:
//!
//! ``` ignore
//! MEMORY
//! {
//! /* NOTE K = KiBi = 1024 bytes */
//! FLASH : ORIGIN = 0x00000000, LENGTH = 512K
//! RAM : ORIGIN = 0x20000000, LENGTH = 64K
//! }
//! ```
//!
//! `memory.x` file after modification to hold a 1K region:
//!
//! ``` ignore
//! MEMORY
//! {
//! /* NOTE K = KiBi = 1024 bytes */
//! FLASH : ORIGIN = 0x00000000, LENGTH = 512K
//! RAM : ORIGIN = 0x20000000, LENGTH = 63K
//! PANDUMP: ORIGIN = 0x2000FC00, LENGTH = 1K
//! }
//!
//! _panic_dump_start = ORIGIN(PANDUMP);
//! _panic_dump_end = ORIGIN(PANDUMP) + LENGTH(PANDUMP);
//! ```
//!
//!
//! ### Program Usage Example
//!
//! ``` ignore
//! #![no_std]
//!
//! use panic_persist as _;
//!
//! #[entry]
//! fn main() -> ! {
//! // Normal board setup...
//!
//! // Check if there was a panic message, if so, send to UART
//! if let Some(msg) = get_panic_message_bytes() {
//! board.uart.write(msg);
//! }
//!
//! // ...
//! }
//! ```
//!
//! ## Features
//!
//! There are a few optional features, `utf8` and `custom-panic-handler`.
//!
//! ### utf8
//!
//! This allows the panic message to be returned
//! as a `&str` rather than `&[u8]`, for easier printing. As this requires the ability
//! to validate the UTF-8 string (to ensure it wasn't truncated mid-character), it may
//! increase code size usage, and is by default off.
//!
//! ### custom-panic-handler
//!
//! This disables the panic handler from this library so that any user can implement their own.
//! To persist panic messages, the function `report_panic_info` is made available;
//!
//! ```rust
//! // My custom panic implementation
//! #[panic_handler]
//! fn panic(info: &PanicInfo) -> ! {
//! // ...
//! panic_persist::report_panic_info(info);
//! // ...
//! }
//! ```
//!
//! ### min-panic
//!
//! This prints a smaller, line-number-only message, in order to reduce space needed when persisting panics, at a loss of some context.
#![allow(clippy::empty_loop)]
#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
use core::cmp::min;
use core::fmt::Write;
use core::mem::size_of;
use core::panic::PanicInfo;
struct Ram {
offset: usize,
}
/// Internal Write implementation to output the formatted panic string into RAM
impl core::fmt::Write for Ram {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
// Obtain panic region start and end from linker symbol _panic_dump_start and _panic_dump_end
extern "C" {
static mut _panic_dump_start: u8;
static mut _panic_dump_end: u8;
}
// Get the data about the string that is being written now
let data = s.as_bytes();
let len = data.len();
// Obtain info about the panic dump region
let start_ptr = unsafe { &mut _panic_dump_start as *mut u8 };
let end_ptr = unsafe { &mut _panic_dump_end as *mut u8 };
let max_len = end_ptr as usize - start_ptr as usize;
let max_len_str = max_len - size_of::<usize>() - size_of::<usize>();
// If we have written the full length of the region, we can't write any
// more. This could happen with multiple writes with this implementation
if self.offset >= max_len_str {
return Ok(());
}
// We should write the size of the string, or the amount of space
// we have remaining, whichever is less
let str_len = min(max_len_str - self.offset, len);
unsafe {
// Write the magic word for later detection
start_ptr.cast::<usize>().write_unaligned(0x0FACADE0);
// For now, skip writing the length...
// Write the string to RAM
core::ptr::copy(
data.as_ptr() as *mut u8,
start_ptr.offset(8).offset(self.offset as isize),
str_len,
);
// Increment the offset so later writes will be appended
self.offset += str_len;
// ... and now write the current offset (or total size) to the size location
start_ptr
.offset(4)
.cast::<usize>()
.write_unaligned(self.offset);
};
Ok(())
}
}
/// Get the panic message from the last boot, if any.
/// This method may possibly not return valid UTF-8 if the message
/// was truncated before the end of a full UTF-8 character. Care must
/// be taken before treating this as a proper &str.
///
/// If a message existed, this function will only return the value once
/// (subsequent calls will return None)
pub fn get_panic_message_bytes() -> Option<&'static [u8]> {
// Obtain panic region start and end from linker symbol _panic_dump_start and _panic_dump_end
extern "C" {
static mut _panic_dump_start: u8;
static mut _panic_dump_end: u8;
}
let start_ptr = unsafe { &mut _panic_dump_start as *mut u8 };
if 0x0FACADE0 != unsafe { core::ptr::read_unaligned(start_ptr.cast::<usize>()) } {
return None;
}
// Clear the magic word to prevent this message from "sticking"
// across multiple boots
unsafe {
start_ptr.cast::<usize>().write_unaligned(0x00000000);
}
// Obtain info about the panic dump region
let end_ptr = unsafe { &mut _panic_dump_end as *mut u8 };
let max_len = end_ptr as usize - start_ptr as usize;
let max_len_str = max_len - size_of::<usize>() - size_of::<usize>();
let len = unsafe { core::ptr::read_unaligned(start_ptr.offset(4).cast::<usize>()) };
if len > max_len_str {
return None;
}
// TODO: This is prooooooooobably undefined behavior
let byte_slice = unsafe { core::slice::from_raw_parts(start_ptr.offset(8), len) };
Some(byte_slice)
}
/// Get the panic message from the last boot, if any. If any invalid
/// UTF-8 characters occur, the message will be truncated before the
/// first error.
///
/// If a message existed, this function will only return the value once
/// (subsequent calls will return None)
#[cfg(feature = "utf8")]
pub fn get_panic_message_utf8() -> Option<&'static str> {
let bytes = get_panic_message_bytes()?;
use core::str::from_utf8;
match from_utf8(bytes) {
Ok(stir) => Some(stir),
Err(utf_err) => {
match from_utf8(&bytes[..utf_err.valid_up_to()]) {
Ok(stir) => Some(stir),
Err(_) => {
// This shouldn't be possible...
None
}
}
}
}
}
/// Report the panic so the message is persisted.
///
/// This function is used in custom panic handlers.
#[cfg(feature = "custom-panic-handler")]
pub fn report_panic_info(info: &PanicInfo) {
writeln!(Ram { offset: 0 }, "{}", info).ok();
}
#[cfg(not(feature = "custom-panic-handler"))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
cortex_m::interrupt::disable();
#[cfg(feature = "min-panic")]
if let Some(location) = info.location() {
writeln!(Ram { offset: 0 }, "Panicked at {}", location).ok();
} else {
writeln!(Ram { offset: 0 }, "Panic occured!").ok();
}
#[cfg(not(feature = "min-panic"))]
writeln!(Ram { offset: 0 }, "{}", info).ok();
cortex_m::peripheral::SCB::sys_reset();
}