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 =
13    esp_config::esp_config_int!(usize, "ESP_BACKTRACE_CONFIG_BACKTRACE_FRAMES");
14
15pub struct Backtrace(pub(crate) heapless::Vec<BacktraceFrame, MAX_BACKTRACE_ADDRESSES>);
16
17impl Backtrace {
18    /// Captures a stack backtrace.
19    #[inline]
20    pub fn capture() -> Self {
21        arch::backtrace()
22    }
23
24    #[inline]
25    #[cfg(feature = "exception-handler")]
26    fn from_sp(sp: u32) -> Self {
27        arch::backtrace_internal(sp, 0)
28    }
29
30    /// Returns the backtrace frames as a slice.
31    #[inline]
32    pub fn frames(&self) -> &[BacktraceFrame] {
33        &self.0
34    }
35}
36
37pub struct BacktraceFrame {
38    pub(crate) pc: usize,
39}
40
41impl BacktraceFrame {
42    pub fn program_counter(&self) -> usize {
43        self.pc - crate::arch::RA_OFFSET
44    }
45}
46
47const RESET: &str = "\u{001B}[0m";
48const RED: &str = "\u{001B}[31m";
49
50#[cfg(feature = "defmt")]
51macro_rules! println {
52    ($($arg:tt)*) => {
53        defmt::error!($($arg)*);
54    };
55}
56
57#[cfg(all(feature = "println", not(feature = "defmt")))]
58macro_rules! println {
59    ($($arg:tt)*) => {
60        esp_println::println!($($arg)*);
61    };
62}
63
64#[allow(unused, unused_variables)]
65fn set_color_code(code: &str) {
66    #[cfg(all(feature = "colors", feature = "println"))]
67    {
68        println!("{}", code);
69    }
70}
71
72#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")]
73#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
74pub mod arch;
75
76#[cfg(feature = "panic-handler")]
77#[panic_handler]
78fn panic_handler(info: &core::panic::PanicInfo) -> ! {
79    pre_backtrace();
80
81    println!("");
82    println!("====================== PANIC ======================");
83
84    println!("{}", info);
85
86    println!("");
87    println!("Backtrace:");
88    println!("");
89
90    let backtrace = Backtrace::capture();
91    #[cfg(target_arch = "riscv32")]
92    if backtrace.frames().is_empty() {
93        println!(
94            "No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)"
95        );
96    }
97    for frame in backtrace.frames() {
98        println!("0x{:x}", frame.program_counter());
99    }
100
101    abort();
102}
103
104#[cfg(all(feature = "exception-handler", target_arch = "xtensa"))]
105#[unsafe(no_mangle)]
106#[unsafe(link_section = ".rwtext")]
107unsafe fn __user_exception(cause: arch::ExceptionCause, context: arch::Context) {
108    pre_backtrace();
109
110    println!("\n\nException occurred '{}'", cause);
111    println!("{:?}", context);
112
113    let backtrace = Backtrace::from_sp(context.A1);
114    for frame in backtrace.frames() {
115        println!("0x{:x}", frame.program_counter());
116    }
117
118    abort();
119}
120
121#[cfg(all(feature = "exception-handler", target_arch = "riscv32"))]
122#[unsafe(export_name = "ExceptionHandler")]
123fn exception_handler(context: &arch::TrapFrame) -> ! {
124    pre_backtrace();
125
126    let mepc = context.pc;
127    let code = context.mcause & 0xff;
128    let mtval = context.mtval;
129
130    if code == 14 {
131        println!("");
132        println!(
133            "Stack overflow detected at 0x{:x} called by 0x{:x}",
134            mepc, context.ra
135        );
136        println!("");
137    } else {
138        let code = match code {
139            0 => "Instruction address misaligned",
140            1 => "Instruction access fault",
141            2 => "Illegal instruction",
142            3 => "Breakpoint",
143            4 => "Load address misaligned",
144            5 => "Load access fault",
145            6 => "Store/AMO address misaligned",
146            7 => "Store/AMO access fault",
147            8 => "Environment call from U-mode",
148            9 => "Environment call from S-mode",
149            10 => "Reserved",
150            11 => "Environment call from M-mode",
151            12 => "Instruction page fault",
152            13 => "Load page fault",
153            14 => "Reserved",
154            15 => "Store/AMO page fault",
155            _ => "UNKNOWN",
156        };
157
158        println!(
159            "Exception '{}' mepc=0x{:08x}, mtval=0x{:08x}",
160            code, mepc, mtval
161        );
162
163        println!("{:?}", context);
164
165        let backtrace = Backtrace::from_sp(context.s0 as u32);
166        let frames = backtrace.frames();
167        if frames.is_empty() {
168            println!(
169                "No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)"
170            );
171        }
172        for frame in backtrace.frames() {
173            println!("0x{:x}", frame.program_counter());
174        }
175    }
176
177    abort();
178}
179
180// Ensure that the address is in DRAM and that it is 16-byte aligned.
181//
182// Based loosely on the `esp_stack_ptr_in_dram` function from
183// `components/esp_hw_support/include/esp_memory_utils.h` in ESP-IDF.
184//
185// Address ranges can be found in `components/soc/$CHIP/include/soc/soc.h` as
186// `SOC_DRAM_LOW` and `SOC_DRAM_HIGH`.
187fn is_valid_ram_address(address: u32) -> bool {
188    if (address & 0xF) != 0 {
189        return false;
190    }
191
192    #[cfg(feature = "esp32")]
193    if !(0x3FFA_E000..=0x4000_0000).contains(&address) {
194        return false;
195    }
196
197    #[cfg(feature = "esp32c2")]
198    if !(0x3FCA_0000..=0x3FCE_0000).contains(&address) {
199        return false;
200    }
201
202    #[cfg(feature = "esp32c3")]
203    if !(0x3FC8_0000..=0x3FCE_0000).contains(&address) {
204        return false;
205    }
206
207    #[cfg(feature = "esp32c6")]
208    if !(0x4080_0000..=0x4088_0000).contains(&address) {
209        return false;
210    }
211
212    #[cfg(feature = "esp32h2")]
213    if !(0x4080_0000..=0x4085_0000).contains(&address) {
214        return false;
215    }
216
217    #[cfg(feature = "esp32p4")]
218    if !(0x4FF0_0000..=0x4FFC_0000).contains(&address) {
219        return false;
220    }
221
222    #[cfg(feature = "esp32s2")]
223    if !(0x3FFB_0000..=0x4000_0000).contains(&address) {
224        return false;
225    }
226
227    #[cfg(feature = "esp32s3")]
228    if !(0x3FC8_8000..=0x3FD0_0000).contains(&address) {
229        return false;
230    }
231
232    true
233}
234
235#[allow(unused)]
236fn halt() -> ! {
237    cfg_if::cfg_if! {
238        if #[cfg(feature = "custom-halt")] {
239            // call custom code
240            unsafe extern "Rust" {
241                fn custom_halt() -> !;
242            }
243            unsafe { custom_halt() }
244        } else if #[cfg(any(feature = "esp32", /*feature = "esp32p4",*/ feature = "esp32s3"))] {
245            // multi-core
246            #[cfg(feature = "esp32")]
247            mod registers {
248                pub(crate) const OPTIONS0: u32 = 0x3ff48000;
249                pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac;
250            }
251
252            #[cfg(feature = "esp32p4")]
253            mod registers {
254                pub(crate) const SW_CPU_STALL: u32 = 0x50115200;
255            }
256
257            #[cfg(feature = "esp32s3")]
258            mod registers {
259                pub(crate) const OPTIONS0: u32 = 0x60008000;
260                pub(crate) const SW_CPU_STALL: u32 = 0x600080bc;
261            }
262
263            let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
264
265            unsafe {
266                // We need to write the value "0x86" to stall a particular core. The write
267                // location is split into two separate bit fields named "c0" and "c1", and the
268                // two fields are located in different registers. Each core has its own pair of
269                // "c0" and "c1" bit fields.
270
271                let options0 = registers::OPTIONS0 as *mut u32;
272
273                options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010);
274
275                sw_cpu_stall.write_volatile(
276                    sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26)
277                        | (0x21 << 20)
278                        | (0x21 << 26),
279                );
280            }
281        }
282    }
283
284    #[allow(clippy::empty_loop)]
285    loop {}
286}
287
288#[allow(unused)]
289fn pre_backtrace() {
290    #[cfg(feature = "custom-pre-backtrace")]
291    {
292        unsafe extern "Rust" {
293            fn custom_pre_backtrace();
294        }
295        unsafe { custom_pre_backtrace() }
296    }
297
298    set_color_code(RED);
299}
300
301#[allow(unused)]
302fn abort() -> ! {
303    println!("");
304    println!("");
305    println!("");
306
307    set_color_code(RESET);
308
309    cfg_if::cfg_if! {
310        if #[cfg(feature = "semihosting")] {
311            semihosting::process::abort();
312        } else {
313            halt();
314        }
315    }
316}