├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── Cargo.lock └── src ├── coredump.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "xtensa-esp32s3-none-elf", 3 | "rust-analyzer.cargo.features": [ 4 | "esp32s3" 5 | ], 6 | "rust-analyzer.cargo.buildScripts.useRustcWrapper": true, 7 | "rust-analyzer.cargo.allTargets": false, 8 | "rust-analyzer.check.allTargets": false, 9 | "rust-analyzer.showUnlinkedFileNotification": false, 10 | } 11 | 12 | // xtensa-esp32s3-none-elf / riscv32imac-unknown-none-elf -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esp-coredump" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | esp-metadata = { version = "0.6.0", default-features = false } 8 | esp-println = { version = "0.13.1", default-features = false } 9 | embedded-io = { version = "0.6.1" } 10 | 11 | [build-dependencies] 12 | esp-build = { version = "0.2.0" } 13 | esp-metadata = { version = "0.6.0" } 14 | 15 | [features] 16 | esp32c2 = [] 17 | esp32c3 = [] 18 | esp32c6 = [] 19 | esp32h2 = [] 20 | esp32 = [] 21 | esp32s2 = [] 22 | esp32s3 = [] 23 | 24 | all = [] 25 | 26 | [patch.crates-io] 27 | esp-metadata = { git = "https://github.com/esp-rs/esp-hal.git", rev = "faf7115b0c4bce01cec2b6cc4e69eccfc697eebf" } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Panic / Exception Handler Generating GDB-compatible Coredumps 2 | 3 | This is more or less a drop-in replacement for `esp-backtrace` which - instead of printing a backtrace - will emit a coredump. 4 | 5 | ## Usage 6 | 7 | Replace `esp-backtrace` with this (note: use a git dependency, it's not on crates.io and for now it's not planned). 8 | 9 | You can use https://github.com/bjoernQ/espflash-coredump and a recent `espflash` commit (or copy paste, convert from hex to binary) 10 | 11 | ## Notes 12 | 13 | Other than `esp-backtrace` this only supports `esp-println` as a backend - when you already connected a debugger you could/should use that instead. 14 | 15 | For now you also need to patch the `esp-metadata` dependency like it's shown in `Cargo.toml` 16 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.97" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 10 | 11 | [[package]] 12 | name = "basic-toml" 13 | version = "0.1.10" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" 16 | dependencies = [ 17 | "serde", 18 | ] 19 | 20 | [[package]] 21 | name = "embedded-io" 22 | version = "0.6.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 25 | 26 | [[package]] 27 | name = "esp-build" 28 | version = "0.2.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8aa1c8f9954c9506699cf1ca10a2adcc226ff10b6ae3cb9e875cf2c6a0b9a372" 31 | dependencies = [ 32 | "quote", 33 | "syn", 34 | "termcolor", 35 | ] 36 | 37 | [[package]] 38 | name = "esp-coredump" 39 | version = "0.1.0" 40 | dependencies = [ 41 | "embedded-io", 42 | "esp-build", 43 | "esp-metadata", 44 | "esp-println", 45 | ] 46 | 47 | [[package]] 48 | name = "esp-metadata" 49 | version = "0.6.0" 50 | source = "git+https://github.com/esp-rs/esp-hal.git?rev=faf7115b0c4bce01cec2b6cc4e69eccfc697eebf#faf7115b0c4bce01cec2b6cc4e69eccfc697eebf" 51 | dependencies = [ 52 | "anyhow", 53 | "basic-toml", 54 | "serde", 55 | "strum", 56 | ] 57 | 58 | [[package]] 59 | name = "esp-println" 60 | version = "0.13.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "960703930f9f3c899ddedd122ea27a09d6a612c22323157e524af5b18876448e" 63 | dependencies = [ 64 | "esp-build", 65 | "log", 66 | ] 67 | 68 | [[package]] 69 | name = "heck" 70 | version = "0.5.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 73 | 74 | [[package]] 75 | name = "log" 76 | version = "0.4.27" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 79 | 80 | [[package]] 81 | name = "proc-macro2" 82 | version = "1.0.94" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 85 | dependencies = [ 86 | "unicode-ident", 87 | ] 88 | 89 | [[package]] 90 | name = "quote" 91 | version = "1.0.40" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 94 | dependencies = [ 95 | "proc-macro2", 96 | ] 97 | 98 | [[package]] 99 | name = "rustversion" 100 | version = "1.0.20" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 103 | 104 | [[package]] 105 | name = "serde" 106 | version = "1.0.219" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 109 | dependencies = [ 110 | "serde_derive", 111 | ] 112 | 113 | [[package]] 114 | name = "serde_derive" 115 | version = "1.0.219" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 118 | dependencies = [ 119 | "proc-macro2", 120 | "quote", 121 | "syn", 122 | ] 123 | 124 | [[package]] 125 | name = "strum" 126 | version = "0.26.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 129 | dependencies = [ 130 | "strum_macros", 131 | ] 132 | 133 | [[package]] 134 | name = "strum_macros" 135 | version = "0.26.4" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 138 | dependencies = [ 139 | "heck", 140 | "proc-macro2", 141 | "quote", 142 | "rustversion", 143 | "syn", 144 | ] 145 | 146 | [[package]] 147 | name = "syn" 148 | version = "2.0.100" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 151 | dependencies = [ 152 | "proc-macro2", 153 | "quote", 154 | "unicode-ident", 155 | ] 156 | 157 | [[package]] 158 | name = "termcolor" 159 | version = "1.4.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 162 | dependencies = [ 163 | "winapi-util", 164 | ] 165 | 166 | [[package]] 167 | name = "unicode-ident" 168 | version = "1.0.18" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 171 | 172 | [[package]] 173 | name = "winapi-util" 174 | version = "0.1.9" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 177 | dependencies = [ 178 | "windows-sys", 179 | ] 180 | 181 | [[package]] 182 | name = "windows-sys" 183 | version = "0.59.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 186 | dependencies = [ 187 | "windows-targets", 188 | ] 189 | 190 | [[package]] 191 | name = "windows-targets" 192 | version = "0.52.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 195 | dependencies = [ 196 | "windows_aarch64_gnullvm", 197 | "windows_aarch64_msvc", 198 | "windows_i686_gnu", 199 | "windows_i686_gnullvm", 200 | "windows_i686_msvc", 201 | "windows_x86_64_gnu", 202 | "windows_x86_64_gnullvm", 203 | "windows_x86_64_msvc", 204 | ] 205 | 206 | [[package]] 207 | name = "windows_aarch64_gnullvm" 208 | version = "0.52.6" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 211 | 212 | [[package]] 213 | name = "windows_aarch64_msvc" 214 | version = "0.52.6" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 217 | 218 | [[package]] 219 | name = "windows_i686_gnu" 220 | version = "0.52.6" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 223 | 224 | [[package]] 225 | name = "windows_i686_gnullvm" 226 | version = "0.52.6" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 229 | 230 | [[package]] 231 | name = "windows_i686_msvc" 232 | version = "0.52.6" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 235 | 236 | [[package]] 237 | name = "windows_x86_64_gnu" 238 | version = "0.52.6" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 241 | 242 | [[package]] 243 | name = "windows_x86_64_gnullvm" 244 | version = "0.52.6" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 247 | 248 | [[package]] 249 | name = "windows_x86_64_msvc" 250 | version = "0.52.6" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 253 | -------------------------------------------------------------------------------- /src/coredump.rs: -------------------------------------------------------------------------------- 1 | const HEADER_SIZE: u32 = 0x34; 2 | const PROGRAM_HEADER_SIZE: u32 = 0x20; 3 | const REG_INFO_HEADER_SIZE: usize = 20; 4 | 5 | enum SegmentType { 6 | Load = 1, 7 | Note = 4, 8 | } 9 | 10 | #[cfg(target_arch = "xtensa")] 11 | #[repr(C, packed)] 12 | #[derive(Clone, Copy)] 13 | pub struct Registers { 14 | pub pc: u32, 15 | pub ps: u32, 16 | pub lbeg: u32, 17 | pub lend: u32, 18 | pub lcount: u32, 19 | pub sar: u32, 20 | pub windowstart: u32, 21 | pub windowbase: u32, 22 | pub reserved: [u32; 8 + 48], 23 | pub ar: [u32; 64], 24 | } 25 | 26 | #[cfg(target_arch = "xtensa")] 27 | impl Default for Registers { 28 | fn default() -> Self { 29 | Self { 30 | pc: Default::default(), 31 | ps: Default::default(), 32 | lbeg: Default::default(), 33 | lend: Default::default(), 34 | lcount: Default::default(), 35 | sar: Default::default(), 36 | windowstart: Default::default(), 37 | windowbase: Default::default(), 38 | reserved: [0u32; 8 + 48], 39 | ar: [0u32; 64], 40 | } 41 | } 42 | } 43 | 44 | #[cfg(target_arch = "riscv32")] 45 | #[repr(C)] 46 | #[derive(Clone, Copy)] 47 | pub struct Registers { 48 | pub pc: u32, 49 | pub x1: u32, 50 | pub x2: u32, 51 | pub x3: u32, 52 | pub x4: u32, 53 | pub x5: u32, 54 | pub x6: u32, 55 | pub x7: u32, 56 | pub x8: u32, 57 | pub x9: u32, 58 | pub x10: u32, 59 | pub x11: u32, 60 | pub x12: u32, 61 | pub x13: u32, 62 | pub x14: u32, 63 | pub x15: u32, 64 | pub x16: u32, 65 | pub x17: u32, 66 | pub x18: u32, 67 | pub x19: u32, 68 | pub x20: u32, 69 | pub x21: u32, 70 | pub x22: u32, 71 | pub x23: u32, 72 | pub x24: u32, 73 | pub x25: u32, 74 | pub x26: u32, 75 | pub x27: u32, 76 | pub x28: u32, 77 | pub x29: u32, 78 | pub x30: u32, 79 | pub x31: u32, 80 | } 81 | 82 | #[cfg(target_arch = "riscv32")] 83 | impl Default for Registers { 84 | fn default() -> Self { 85 | Self { 86 | pc: Default::default(), 87 | x1: Default::default(), 88 | x2: Default::default(), 89 | x3: Default::default(), 90 | x4: Default::default(), 91 | x5: Default::default(), 92 | x6: Default::default(), 93 | x7: Default::default(), 94 | x8: Default::default(), 95 | x9: Default::default(), 96 | x10: Default::default(), 97 | x11: Default::default(), 98 | x12: Default::default(), 99 | x13: Default::default(), 100 | x14: Default::default(), 101 | x15: Default::default(), 102 | x16: Default::default(), 103 | x17: Default::default(), 104 | x18: Default::default(), 105 | x19: Default::default(), 106 | x20: Default::default(), 107 | x21: Default::default(), 108 | x22: Default::default(), 109 | x23: Default::default(), 110 | x24: Default::default(), 111 | x25: Default::default(), 112 | x26: Default::default(), 113 | x27: Default::default(), 114 | x28: Default::default(), 115 | x29: Default::default(), 116 | x30: Default::default(), 117 | x31: Default::default(), 118 | } 119 | } 120 | } 121 | 122 | pub struct Memory<'a> { 123 | pub start: u32, 124 | pub slice: &'a [u8], 125 | } 126 | 127 | #[cfg(target_arch = "xtensa")] 128 | #[repr(C, packed)] 129 | struct RegisterInfo { 130 | // PR status 131 | si_signo: u32, 132 | si_code: u32, 133 | si_errno: u32, 134 | pr_cursig: u16, 135 | pr_pad0: u16, 136 | pr_sigpend: u32, 137 | pr_sighold: u32, 138 | pr_pid: u32, 139 | pr_ppid: u32, 140 | pr_pgrp: u32, 141 | pr_sid: u32, 142 | pr_utime: u64, 143 | pr_stime: u64, 144 | pr_cutime: u64, 145 | pr_cstime: u64, 146 | 147 | registers: Registers, 148 | 149 | reserved_: u32, 150 | } 151 | 152 | #[cfg(target_arch = "xtensa")] 153 | impl Default for RegisterInfo { 154 | fn default() -> Self { 155 | Self { 156 | si_signo: 0, 157 | si_code: 0, 158 | si_errno: 0, 159 | pr_cursig: 0, 160 | pr_pad0: 0, 161 | pr_sigpend: 0, 162 | pr_sighold: 0, 163 | pr_pid: 0, 164 | pr_ppid: 0, 165 | pr_pgrp: 0, 166 | pr_sid: 0, 167 | pr_utime: 0, 168 | pr_stime: 0, 169 | pr_cutime: 0, 170 | pr_cstime: 0, 171 | 172 | registers: Registers::default(), 173 | 174 | reserved_: 0, 175 | } 176 | } 177 | } 178 | 179 | // see ESP-IDF for how this looks like for Xtensa 180 | // see components\espcoredump\src\port\xtensa\core_dump_port.c for 181 | #[cfg(target_arch = "riscv32")] 182 | #[repr(C)] 183 | struct RegisterInfo { 184 | padding1: [u8; 12], 185 | signal: u16, 186 | padding2: [u8; 10], 187 | pid: u32, 188 | padding3: [u8; 44], 189 | 190 | registers: Registers, 191 | 192 | padding4: [u8; 4], 193 | } 194 | 195 | #[cfg(target_arch = "riscv32")] 196 | impl Default for RegisterInfo { 197 | fn default() -> Self { 198 | Self { 199 | padding1: Default::default(), 200 | signal: Default::default(), 201 | padding2: Default::default(), 202 | pid: Default::default(), 203 | padding3: [0u8; 72 - 24 - 4], 204 | registers: Default::default(), 205 | padding4: Default::default(), 206 | } 207 | } 208 | } 209 | 210 | pub fn dump( 211 | writer: &mut W, 212 | regs: &Registers, 213 | mem: Memory, 214 | ) -> Result<(), W::Error> { 215 | let mut writer = CoreDumpWriter::new(writer); 216 | 217 | let reg_info: RegisterInfo = { 218 | let mut reg_info = RegisterInfo::default(); 219 | reg_info.registers = *regs; 220 | reg_info 221 | }; 222 | 223 | writer.elf_header()?; 224 | 225 | // header for the memory region 226 | writer.write_program_header( 227 | SegmentType::Load, 228 | HEADER_SIZE + 2 * PROGRAM_HEADER_SIZE, // we have 2 program header entries 229 | mem.start, 230 | mem.slice.len() as u32, 231 | 6, 232 | 0, 233 | )?; 234 | 235 | // header for the register info 236 | writer.write_program_header( 237 | SegmentType::Note, 238 | // we have 2 program header entries and first data is the memory block 239 | HEADER_SIZE + 2 * PROGRAM_HEADER_SIZE + mem.slice.len() as u32, 240 | 0, 241 | (core::mem::size_of::() + REG_INFO_HEADER_SIZE) as u32, 242 | 6, 243 | 0, 244 | )?; 245 | 246 | // write memory contents 247 | writer.writer.write(mem.slice)?; 248 | 249 | // write register info 250 | let mut reg_info_bytes = [0x0u8; core::mem::size_of::() + REG_INFO_HEADER_SIZE]; 251 | unsafe { 252 | (®_info as *const RegisterInfo) 253 | .copy_to(reg_info_bytes.as_mut_ptr() as *mut RegisterInfo, 1); 254 | } 255 | 256 | // reg-info-header 257 | 258 | // always like this so we can hardcode it 259 | // slightly different for Xtensa b.c. the length of the register info (0xcc) is 260 | // different 261 | #[cfg(target_arch = "riscv32")] 262 | writer.writer.write(&[ 263 | 0x08, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x52, 264 | 0x45, 0x00, 0x00, 0x00, 0x00, 265 | ])?; 266 | 267 | #[cfg(target_arch = "xtensa")] 268 | writer.writer.write(&[ 269 | 0x08, 0x00, 0x00, 0x00, 0x4c, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x52, 270 | 0x45, 0x00, 0x00, 0x00, 0x00, 271 | ])?; 272 | 273 | writer.writer.write(®_info_bytes)?; 274 | 275 | Ok(()) 276 | } 277 | 278 | struct CoreDumpWriter<'a, W: embedded_io::Write> { 279 | writer: &'a mut W, 280 | } 281 | 282 | impl<'a, W: embedded_io::Write> CoreDumpWriter<'a, W> { 283 | pub fn new(writer: &'a mut W) -> Self { 284 | Self { writer } 285 | } 286 | 287 | fn elf_header(&mut self) -> Result<(), W::Error> { 288 | let _ = self.writer.write(&[0x7f, b'E', b'L', b'F'])?; 289 | let _ = self.writer.write(&[0x01])?; // 32 bit 290 | let _ = self.writer.write(&[0x01])?; // little endian 291 | let _ = self.writer.write(&[0x01])?; // version 292 | let _ = self.writer.write(&[0x00])?; // ABI 293 | let _ = self.writer.write(&[0x00])?; // ABI version 294 | let _ = self 295 | .writer 296 | .write(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?; // padding 297 | let _ = self.writer.write(&[0x04, 0x00])?; // CORE file 298 | 299 | #[cfg(target_arch = "riscv32")] 300 | let _ = self.writer.write(&[0xf3, 0x00])?; // machine (RISCV) 301 | 302 | #[cfg(target_arch = "xtensa")] 303 | let _ = self.writer.write(&[0x5e, 0x00])?; // machine (Xtensa) 304 | 305 | let _ = self.writer.write(&[0x01, 0x00, 0x00, 0x00])?; // version 306 | let _ = self.writer.write(&[0x00, 0x00, 0x00, 0x00])?; // entry 307 | let _ = self.writer.write(&[0x34, 0x00, 0x00, 0x00])?; // start of program header 308 | let _ = self.writer.write(&[0x00, 0x00, 0x00, 0x00])?; // start of section header 309 | let _ = self.writer.write(&[0x00, 0x00, 0x00, 0x00])?; // flags 310 | let _ = self.writer.write(&[0x34, 0x00])?; // ehsize 311 | let _ = self.writer.write(&[0x20, 0x00])?; // size of a program header table entry. 312 | let _ = self.writer.write(&[0x02, 0x00])?; // number of entries in the program header table !!!! here 2: one memory block 313 | // and the registers as a note 314 | let _ = self.writer.write(&[0x28, 0x00])?; // size of a section header table entry. 315 | let _ = self.writer.write(&[0x00, 0x00])?; // number of section headers 316 | let _ = self.writer.write(&[0x00, 0x00])?; // index of the section header table 317 | // entry that contains the section 318 | // name 319 | Ok(()) 320 | } 321 | 322 | fn write_program_header( 323 | &mut self, 324 | stype: SegmentType, 325 | offset: u32, 326 | addr: u32, 327 | size: u32, 328 | flags: u32, 329 | align: u32, 330 | ) -> Result<(), W::Error> { 331 | let _ = self.writer.write(&(stype as u32).to_le_bytes())?; // type 332 | let _ = self.writer.write(&offset.to_le_bytes())?; // offset in file 333 | let _ = self.writer.write(&addr.to_le_bytes())?; // vaddr 334 | let _ = self.writer.write(&addr.to_le_bytes())?; // paddr 335 | let _ = self.writer.write(&size.to_le_bytes())?; // file size 336 | let _ = self.writer.write(&size.to_le_bytes())?; // memory size 337 | let _ = self.writer.write(&flags.to_le_bytes())?; // flags 338 | let _ = self.writer.write(&align.to_le_bytes())?; // align 339 | Ok(()) 340 | } 341 | } 342 | 343 | #[derive(Debug)] 344 | pub struct DumpWriter {} 345 | 346 | impl embedded_io::Error for DumpWriter { 347 | fn kind(&self) -> embedded_io::ErrorKind { 348 | embedded_io::ErrorKind::Other 349 | } 350 | } 351 | 352 | impl embedded_io::ErrorType for DumpWriter { 353 | type Error = core::convert::Infallible; 354 | } 355 | 356 | impl embedded_io::Write for DumpWriter { 357 | fn write(&mut self, buf: &[u8]) -> Result { 358 | for _b in buf.iter() { 359 | esp_println::print!("{:02x}", _b); 360 | } 361 | Ok(buf.len()) 362 | } 363 | 364 | fn flush(&mut self) -> Result<(), Self::Error> { 365 | Ok(()) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))] 3 | 4 | use esp_println::println; 5 | 6 | mod coredump; 7 | 8 | extern "C" { 9 | static _stack_start: u32; 10 | static _stack_end: u32; 11 | } 12 | 13 | #[allow(unused)] 14 | const RAM: (usize, usize) = ( 15 | esp_metadata::memory_region_start!("DRAM"), 16 | esp_metadata::memory_region_end!("DRAM"), 17 | ); 18 | 19 | fn halt() -> ! { 20 | loop {} 21 | } 22 | 23 | #[panic_handler] 24 | fn panic_handler(info: &core::panic::PanicInfo) -> ! { 25 | println!(""); 26 | println!("====================== PANIC ======================"); 27 | println!("{}", info); 28 | 29 | unsafe { 30 | #[cfg(target_arch = "riscv32")] 31 | core::arch::asm!("ecall"); 32 | 33 | #[cfg(target_arch = "xtensa")] 34 | core::arch::asm!("syscall"); 35 | } 36 | 37 | halt(); 38 | } 39 | 40 | /// Registers saved in trap handler 41 | #[doc(hidden)] 42 | #[derive(Default, Clone, Copy)] 43 | #[repr(C)] 44 | #[cfg(target_arch = "riscv32")] 45 | pub(crate) struct TrapFrame { 46 | /// Return address, stores the address to return to after a function call or 47 | /// interrupt. 48 | pub ra: usize, 49 | /// Temporary register t0, used for intermediate values. 50 | pub t0: usize, 51 | /// Temporary register t1, used for intermediate values. 52 | pub t1: usize, 53 | /// Temporary register t2, used for intermediate values. 54 | pub t2: usize, 55 | /// Temporary register t3, used for intermediate values. 56 | pub t3: usize, 57 | /// Temporary register t4, used for intermediate values. 58 | pub t4: usize, 59 | /// Temporary register t5, used for intermediate values. 60 | pub t5: usize, 61 | /// Temporary register t6, used for intermediate values. 62 | pub t6: usize, 63 | /// Argument register a0, typically used to pass the first argument to a 64 | /// function. 65 | pub a0: usize, 66 | /// Argument register a1, typically used to pass the second argument to a 67 | /// function. 68 | pub a1: usize, 69 | /// Argument register a2, typically used to pass the third argument to a 70 | /// function. 71 | pub a2: usize, 72 | /// Argument register a3, typically used to pass the fourth argument to a 73 | /// function. 74 | pub a3: usize, 75 | /// Argument register a4, typically used to pass the fifth argument to a 76 | /// function. 77 | pub a4: usize, 78 | /// Argument register a5, typically used to pass the sixth argument to a 79 | /// function. 80 | pub a5: usize, 81 | /// Argument register a6, typically used to pass the seventh argument to a 82 | /// function. 83 | pub a6: usize, 84 | /// Argument register a7, typically used to pass the eighth argument to a 85 | /// function. 86 | pub a7: usize, 87 | /// Saved register s0, used to hold values across function calls. 88 | pub s0: usize, 89 | /// Saved register s1, used to hold values across function calls. 90 | pub s1: usize, 91 | /// Saved register s2, used to hold values across function calls. 92 | pub s2: usize, 93 | /// Saved register s3, used to hold values across function calls. 94 | pub s3: usize, 95 | /// Saved register s4, used to hold values across function calls. 96 | pub s4: usize, 97 | /// Saved register s5, used to hold values across function calls. 98 | pub s5: usize, 99 | /// Saved register s6, used to hold values across function calls. 100 | pub s6: usize, 101 | /// Saved register s7, used to hold values across function calls. 102 | pub s7: usize, 103 | /// Saved register s8, used to hold values across function calls. 104 | pub s8: usize, 105 | /// Saved register s9, used to hold values across function calls. 106 | pub s9: usize, 107 | /// Saved register s10, used to hold values across function calls. 108 | pub s10: usize, 109 | /// Saved register s11, used to hold values across function calls. 110 | pub s11: usize, 111 | /// Global pointer register, holds the address of the global data area. 112 | pub gp: usize, 113 | /// Thread pointer register, holds the address of the thread-local storage 114 | /// area. 115 | pub tp: usize, 116 | /// Stack pointer register, holds the address of the top of the stack. 117 | pub sp: usize, 118 | /// Program counter, stores the address of the next instruction to be 119 | /// executed. 120 | pub pc: usize, 121 | /// Machine status register, holds the current status of the processor, 122 | /// including interrupt enable bits and privilege mode. 123 | pub mstatus: usize, 124 | /// Machine cause register, contains the reason for the trap (e.g., 125 | /// exception or interrupt number). 126 | pub mcause: usize, 127 | /// Machine trap value register, contains additional information about the 128 | /// trap (e.g., faulting address). 129 | pub mtval: usize, 130 | } 131 | 132 | #[cfg(target_arch = "riscv32")] 133 | #[export_name = "ExceptionHandler"] 134 | fn exception_handler(context: &TrapFrame) -> ! { 135 | let mepc = context.pc; 136 | let code = context.mcause & 0xff; 137 | let mtval = context.mtval; 138 | 139 | #[cfg(feature = "colors")] 140 | set_color_code(RED); 141 | 142 | if code == 14 { 143 | println!(""); 144 | println!( 145 | "Stack overflow detected at 0x{:x} called by 0x{:x}", 146 | mepc, context.ra 147 | ); 148 | println!(""); 149 | } else if code != 11 { 150 | let code = match code { 151 | 0 => "Instruction address misaligned", 152 | 1 => "Instruction access fault", 153 | 2 => "Illegal instruction", 154 | 3 => "Breakpoint", 155 | 4 => "Load address misaligned", 156 | 5 => "Load access fault", 157 | 6 => "Store/AMO address misaligned", 158 | 7 => "Store/AMO access fault", 159 | 8 => "Environment call from U-mode", 160 | 9 => "Environment call from S-mode", 161 | 10 => "Reserved", 162 | 11 => "Environment call from M-mode", 163 | 12 => "Instruction page fault", 164 | 13 => "Load page fault", 165 | 14 => "Reserved", 166 | 15 => "Store/AMO page fault", 167 | _ => "UNKNOWN", 168 | }; 169 | 170 | println!( 171 | "Exception '{}' mepc=0x{:08x}, mtval=0x{:08x}", 172 | code, mepc, mtval 173 | ); 174 | } 175 | 176 | let regs = coredump::Registers { 177 | pc: context.pc as u32, 178 | x1: context.ra as u32, 179 | x2: context.sp as u32, 180 | x3: context.gp as u32, 181 | x4: context.tp as u32, 182 | x5: context.t0 as u32, 183 | x6: context.t1 as u32, 184 | x7: context.t2 as u32, 185 | x8: context.s0 as u32, 186 | x9: context.s1 as u32, 187 | x10: context.a0 as u32, 188 | x11: context.a1 as u32, 189 | x12: context.a2 as u32, 190 | x13: context.a3 as u32, 191 | x14: context.a4 as u32, 192 | x15: context.a5 as u32, 193 | x16: context.a6 as u32, 194 | x17: context.a7 as u32, 195 | x18: context.s2 as u32, 196 | x19: context.s3 as u32, 197 | x20: context.s4 as u32, 198 | x21: context.s5 as u32, 199 | x22: context.s6 as u32, 200 | x23: context.s7 as u32, 201 | x24: context.s8 as u32, 202 | x25: context.s9 as u32, 203 | x26: context.s10 as u32, 204 | x27: context.s11 as u32, 205 | x28: context.t3 as u32, 206 | x29: context.t4 as u32, 207 | x30: context.t5 as u32, 208 | x31: context.t6 as u32, 209 | }; 210 | 211 | let mut writer = crate::coredump::DumpWriter {}; 212 | 213 | #[cfg(not(feature = "all"))] 214 | let start = regs.x2 - 256; 215 | #[cfg(feature = "all")] 216 | let start = crate::RAM.0 as u32; 217 | 218 | #[cfg(not(feature = "all"))] 219 | let end = core::ptr::addr_of!(_stack_start) as u32; 220 | #[cfg(feature = "all")] 221 | let end = crate::RAM.1 as u32; 222 | 223 | let len = (end - start) as usize; 224 | 225 | let slice = unsafe { core::slice::from_raw_parts(start as *const u8, len) }; 226 | let mem = coredump::Memory { 227 | start: start as u32, 228 | slice, 229 | }; 230 | 231 | println!("@COREDUMP"); 232 | coredump::dump(&mut writer, ®s, mem).ok(); 233 | println!("@ENDCOREDUMP"); 234 | 235 | halt(); 236 | } 237 | 238 | #[doc(hidden)] 239 | #[allow(non_snake_case)] 240 | #[derive(Clone, Copy)] 241 | #[repr(C)] 242 | #[cfg(target_arch = "xtensa")] 243 | pub struct Context { 244 | /// Program counter, stores the address of the next instruction to be 245 | /// executed. 246 | pub PC: u32, 247 | /// Processor status, holds various status flags for the CPU. 248 | pub PS: u32, 249 | /// General-purpose register A0 used for data storage and manipulation. 250 | pub A0: u32, 251 | /// General-purpose register A1 used for data storage and manipulation. 252 | pub A1: u32, 253 | /// General-purpose register A2 used for data storage and manipulation. 254 | pub A2: u32, 255 | /// General-purpose register A3 used for data storage and manipulation. 256 | pub A3: u32, 257 | /// General-purpose register A4 used for data storage and manipulation. 258 | pub A4: u32, 259 | /// General-purpose register A5 used for data storage and manipulation. 260 | pub A5: u32, 261 | /// General-purpose register A6 used for data storage and manipulation. 262 | pub A6: u32, 263 | /// General-purpose register A7 used for data storage and manipulation. 264 | pub A7: u32, 265 | /// General-purpose register A8 used for data storage and manipulation. 266 | pub A8: u32, 267 | /// General-purpose register A9 used for data storage and manipulation. 268 | pub A9: u32, 269 | /// General-purpose register A10 used for data storage and manipulation. 270 | pub A10: u32, 271 | /// General-purpose register A11 used for data storage and manipulation. 272 | pub A11: u32, 273 | /// General-purpose register A12 used for data storage and manipulation. 274 | pub A12: u32, 275 | /// General-purpose register A13 used for data storage and manipulation. 276 | pub A13: u32, 277 | /// General-purpose register A14 used for data storage and manipulation. 278 | pub A14: u32, 279 | /// General-purpose register A15 used for data storage and manipulation. 280 | pub A15: u32, 281 | /// Shift amount register, used for shift and rotate instructions. 282 | pub SAR: u32, 283 | /// Exception cause, indicates the reason for the last exception. 284 | pub EXCCAUSE: u32, 285 | /// Exception address, holds the address related to the exception. 286 | pub EXCVADDR: u32, 287 | /// Loop start address, used in loop instructions. 288 | pub LBEG: u32, 289 | /// Loop end address, used in loop instructions. 290 | pub LEND: u32, 291 | /// Loop counter, used to count iterations in loop instructions. 292 | pub LCOUNT: u32, 293 | /// Thread pointer, used for thread-local storage. 294 | pub THREADPTR: u32, 295 | /// Compare register, used for certain compare instructions. 296 | pub SCOMPARE1: u32, 297 | /// Break register, used for breakpoint-related operations. 298 | pub BR: u32, 299 | /// Accumulator low register, used for extended arithmetic operations. 300 | pub ACCLO: u32, 301 | /// Accumulator high register, used for extended arithmetic operations. 302 | pub ACCHI: u32, 303 | /// Additional register M0 used for special operations. 304 | pub M0: u32, 305 | /// Additional register M1 used for special operations. 306 | pub M1: u32, 307 | /// Additional register M2 used for special operations. 308 | pub M2: u32, 309 | /// Additional register M3 used for special operations. 310 | pub M3: u32, 311 | /// 64-bit floating-point register (low part) 312 | pub F64R_LO: u32, 313 | /// 64-bit floating-point register (high part) 314 | pub F64R_HI: u32, 315 | /// Floating-point status register 316 | pub F64S: u32, 317 | /// Floating-point control register 318 | pub FCR: u32, 319 | /// Floating-point status register 320 | pub FSR: u32, 321 | /// Floating-point register F0 322 | pub F0: u32, 323 | /// Floating-point register F1 324 | pub F1: u32, 325 | /// Floating-point register F2 326 | pub F2: u32, 327 | /// Floating-point register F3 328 | pub F3: u32, 329 | /// Floating-point register F4 330 | pub F4: u32, 331 | /// Floating-point register F5 332 | pub F5: u32, 333 | /// Floating-point register F6 334 | pub F6: u32, 335 | /// Floating-point register F7 336 | pub F7: u32, 337 | /// Floating-point register F8 338 | pub F8: u32, 339 | /// Floating-point register F9 340 | pub F9: u32, 341 | /// Floating-point register F10 342 | pub F10: u32, 343 | /// Floating-point register F11 344 | pub F11: u32, 345 | /// Floating-point register F12 346 | pub F12: u32, 347 | /// Floating-point register F13 348 | pub F13: u32, 349 | /// Floating-point register F14 350 | pub F14: u32, 351 | /// Floating-point register F15 352 | pub F15: u32, 353 | } 354 | 355 | #[cfg(target_arch = "xtensa")] 356 | #[no_mangle] 357 | #[link_section = ".rwtext"] 358 | unsafe fn __user_exception(cause: u32, context: Context) { 359 | if cause != 1 { 360 | esp_println::println!("\n\nException occurred '{}'", cause); 361 | } 362 | 363 | fn sanitize(addr: u32) -> u32 { 364 | if (addr & 0x8000_0000) != 0 { 365 | (addr & 0x3fff_ffff) | 0x4000_0000 366 | } else { 367 | addr 368 | } 369 | } 370 | 371 | let mut regs = coredump::Registers { 372 | pc: sanitize(context.PC), 373 | ps: context.PS & !0b10000, 374 | lbeg: context.LBEG, 375 | lend: context.LEND, 376 | lcount: context.LCOUNT, 377 | sar: context.SAR, 378 | windowbase: 0b0000, 379 | windowstart: 0b0000, 380 | ..Default::default() 381 | }; 382 | 383 | regs.ar[0] = sanitize(context.A0); 384 | regs.ar[1] = context.A1; 385 | regs.ar[2] = context.A2; 386 | regs.ar[3] = context.A3; 387 | regs.ar[4] = context.A4; 388 | regs.ar[5] = context.A5; 389 | regs.ar[6] = context.A6; 390 | regs.ar[7] = context.A7; 391 | regs.ar[8] = context.A8; 392 | regs.ar[9] = context.A9; 393 | regs.ar[10] = context.A10; 394 | regs.ar[11] = context.A11; 395 | regs.ar[12] = context.A12; 396 | regs.ar[13] = context.A13; 397 | regs.ar[14] = context.A14; 398 | regs.ar[15] = context.A15; 399 | 400 | let mut writer = crate::coredump::DumpWriter {}; 401 | 402 | #[cfg(not(feature = "all"))] 403 | let start = regs.ar[1] - 256; 404 | #[cfg(feature = "all")] 405 | let start = crate::RAM.0 as u32; 406 | 407 | #[cfg(not(feature = "all"))] 408 | let end = core::ptr::addr_of!(_stack_start) as u32; 409 | #[cfg(feature = "all")] 410 | let end = crate::RAM.1 as u32; 411 | 412 | let len = (end - start) as usize; 413 | 414 | let slice = unsafe { core::slice::from_raw_parts(start as *const u8, len) }; 415 | let mem = coredump::Memory { start, slice }; 416 | 417 | println!("@COREDUMP"); 418 | coredump::dump(&mut writer, ®s, mem).ok(); 419 | println!("@ENDCOREDUMP"); 420 | 421 | halt(); 422 | } 423 | --------------------------------------------------------------------------------