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
//! # `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 is one optional feature, `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. #![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; use cortex_m::interrupt; 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 } } } } } #[panic_handler] fn panic(info: &PanicInfo) -> ! { interrupt::disable(); writeln!(Ram { offset: 0 }, "{}", info).ok(); cortex_m::peripheral::SCB::sys_reset(); }