esp_backtrace/
lib.rs

1#![allow(rustdoc::bare_urls, unused_macros)]
2#![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))]
3#![doc = include_str!("../README.md")]
4#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
5#![no_std]
6
7#[cfg(feature = "defmt")]
8use defmt as _;
9#[cfg(feature = "println")]
10use esp_println as _;
11
12const MAX_BACKTRACE_ADDRESSES: usize = 10;
13
14#[cfg(feature = "colors")]
15const RESET: &str = "\u{001B}[0m";
16#[cfg(feature = "colors")]
17const RED: &str = "\u{001B}[31m";
18
19#[cfg(feature = "defmt")]
20macro_rules! println {
21    ("") => {
22        // Do nothing if the string is just a space
23    };
24    ($($arg:tt)*) => {
25        defmt::error!($($arg)*);
26    };
27}
28
29#[cfg(all(feature = "println", not(feature = "defmt")))]
30macro_rules! println {
31    ($($arg:tt)*) => {
32        esp_println::println!($($arg)*);
33    };
34}
35
36#[allow(unused, unused_variables)]
37fn set_color_code(code: &str) {
38    #[cfg(feature = "println")]
39    {
40        println!("{}", code);
41    }
42}
43
44#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")]
45#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
46pub mod arch;
47
48#[cfg(feature = "panic-handler")]
49#[panic_handler]
50fn panic_handler(info: &core::panic::PanicInfo) -> ! {
51    pre_backtrace();
52
53    #[cfg(feature = "colors")]
54    set_color_code(RED);
55
56    println!("");
57    println!("====================== PANIC ======================");
58
59    #[cfg(not(feature = "defmt"))]
60    println!("{}", info);
61
62    #[cfg(feature = "defmt")]
63    println!("{}", info);
64
65    println!("");
66    println!("Backtrace:");
67    println!("");
68
69    let backtrace = crate::arch::backtrace();
70    #[cfg(target_arch = "riscv32")]
71    if backtrace.iter().filter(|e| e.is_some()).count() == 0 {
72        println!("No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)");
73    }
74    for addr in backtrace.into_iter().flatten() {
75        #[cfg(all(feature = "colors", feature = "println"))]
76        println!("{}0x{:x}", RED, addr - crate::arch::RA_OFFSET);
77
78        #[cfg(not(all(feature = "colors", feature = "println")))]
79        println!("0x{:x}", addr - crate::arch::RA_OFFSET);
80    }
81
82    #[cfg(feature = "colors")]
83    set_color_code(RESET);
84
85    #[cfg(feature = "semihosting")]
86    semihosting::process::abort();
87
88    #[cfg(not(feature = "semihosting"))]
89    halt();
90}
91
92#[cfg(all(feature = "exception-handler", target_arch = "xtensa"))]
93#[no_mangle]
94#[link_section = ".rwtext"]
95unsafe fn __user_exception(cause: arch::ExceptionCause, context: arch::Context) {
96    pre_backtrace();
97
98    #[cfg(feature = "colors")]
99    set_color_code(RED);
100
101    // Unfortunately, a different formatter string is used
102    #[cfg(not(feature = "defmt"))]
103    esp_println::println!("\n\nException occurred '{}'", cause);
104
105    #[cfg(feature = "defmt")]
106    defmt::error!("\n\nException occurred '{}'", cause);
107
108    println!("{:?}", context);
109
110    let backtrace = crate::arch::backtrace_internal(context.A1, 0);
111    for e in backtrace {
112        if let Some(addr) = e {
113            println!("0x{:x}", addr);
114        }
115    }
116    println!("");
117    println!("");
118    println!("");
119
120    #[cfg(feature = "colors")]
121    set_color_code(RESET);
122
123    #[cfg(feature = "semihosting")]
124    semihosting::process::abort();
125
126    #[cfg(not(feature = "semihosting"))]
127    halt();
128}
129
130#[cfg(all(feature = "exception-handler", target_arch = "riscv32"))]
131#[export_name = "ExceptionHandler"]
132fn exception_handler(context: &arch::TrapFrame) -> ! {
133    pre_backtrace();
134
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 {
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        #[cfg(not(feature = "defmt"))]
175        println!("{:x?}", context);
176
177        #[cfg(feature = "defmt")]
178        println!("{:?}", context);
179
180        let backtrace = crate::arch::backtrace_internal(context.s0 as u32, 0);
181        if backtrace.iter().filter(|e| e.is_some()).count() == 0 {
182            println!("No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)");
183        }
184        for addr in backtrace.into_iter().flatten() {
185            #[cfg(all(feature = "colors", feature = "println"))]
186            println!("{}0x{:x}", RED, addr - crate::arch::RA_OFFSET);
187
188            #[cfg(not(all(feature = "colors", feature = "println")))]
189            println!("0x{:x}", addr - crate::arch::RA_OFFSET);
190        }
191    }
192
193    println!("");
194    println!("");
195    println!("");
196
197    #[cfg(feature = "colors")]
198    set_color_code(RESET);
199
200    #[cfg(feature = "semihosting")]
201    semihosting::process::abort();
202
203    #[cfg(not(feature = "semihosting"))]
204    halt();
205}
206
207// Ensure that the address is in DRAM and that it is 16-byte aligned.
208//
209// Based loosely on the `esp_stack_ptr_in_dram` function from
210// `components/esp_hw_support/include/esp_memory_utils.h` in ESP-IDF.
211//
212// Address ranges can be found in `components/soc/$CHIP/include/soc/soc.h` as
213// `SOC_DRAM_LOW` and `SOC_DRAM_HIGH`.
214fn is_valid_ram_address(address: u32) -> bool {
215    if (address & 0xF) != 0 {
216        return false;
217    }
218
219    #[cfg(feature = "esp32")]
220    if !(0x3FFA_E000..=0x4000_0000).contains(&address) {
221        return false;
222    }
223
224    #[cfg(feature = "esp32c2")]
225    if !(0x3FCA_0000..=0x3FCE_0000).contains(&address) {
226        return false;
227    }
228
229    #[cfg(feature = "esp32c3")]
230    if !(0x3FC8_0000..=0x3FCE_0000).contains(&address) {
231        return false;
232    }
233
234    #[cfg(feature = "esp32c6")]
235    if !(0x4080_0000..=0x4088_0000).contains(&address) {
236        return false;
237    }
238
239    #[cfg(feature = "esp32h2")]
240    if !(0x4080_0000..=0x4085_0000).contains(&address) {
241        return false;
242    }
243
244    #[cfg(feature = "esp32p4")]
245    if !(0x4FF0_0000..=0x4FFC_0000).contains(&address) {
246        return false;
247    }
248
249    #[cfg(feature = "esp32s2")]
250    if !(0x3FFB_0000..=0x4000_0000).contains(&address) {
251        return false;
252    }
253
254    #[cfg(feature = "esp32s3")]
255    if !(0x3FC8_8000..=0x3FD0_0000).contains(&address) {
256        return false;
257    }
258
259    true
260}
261
262#[cfg(all(
263    any(
264        not(any(feature = "esp32", feature = "esp32p4", feature = "esp32s3")),
265        not(feature = "halt-cores")
266    ),
267    not(feature = "custom-halt")
268))]
269#[allow(unused)]
270fn halt() -> ! {
271    loop {
272        continue;
273    }
274}
275
276#[cfg(feature = "custom-halt")]
277fn halt() -> ! {
278    extern "Rust" {
279        fn custom_halt() -> !;
280    }
281    unsafe { custom_halt() }
282}
283
284// TODO: Enable `halt` function for `esp32p4` feature once implemented
285#[cfg(all(any(feature = "esp32", feature = "esp32s3"), feature = "halt-cores"))]
286#[allow(unused)]
287fn halt() -> ! {
288    #[cfg(feature = "esp32")]
289    mod registers {
290        pub(crate) const OPTIONS0: u32 = 0x3ff48000;
291        pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac;
292    }
293
294    #[cfg(feature = "esp32p4")]
295    mod registers {
296        pub(crate) const SW_CPU_STALL: u32 = 0x50115200;
297    }
298
299    #[cfg(feature = "esp32s3")]
300    mod registers {
301        pub(crate) const OPTIONS0: u32 = 0x60008000;
302        pub(crate) const SW_CPU_STALL: u32 = 0x600080bc;
303    }
304
305    let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
306
307    #[cfg(feature = "esp32p4")]
308    unsafe {}
309
310    #[cfg(not(feature = "esp32p4"))]
311    unsafe {
312        // We need to write the value "0x86" to stall a particular core. The write
313        // location is split into two separate bit fields named "c0" and "c1", and the
314        // two fields are located in different registers. Each core has its own pair of
315        // "c0" and "c1" bit fields.
316
317        let options0 = registers::OPTIONS0 as *mut u32;
318
319        options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010);
320
321        sw_cpu_stall.write_volatile(
322            sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26)
323                | (0x21 << 20)
324                | (0x21 << 26),
325        );
326    }
327
328    loop {}
329}
330
331#[cfg(not(feature = "custom-pre-backtrace"))]
332#[allow(unused)]
333fn pre_backtrace() {}
334
335#[cfg(feature = "custom-pre-backtrace")]
336fn pre_backtrace() {
337    extern "Rust" {
338        fn custom_pre_backtrace();
339    }
340    unsafe { custom_pre_backtrace() }
341}