Skip to main content

esp_backtrace/
lib.rs

1//! This is a lightweight crate for obtaining backtraces on Espressif devices.
2//!
3//! It provides an optional panic handler and supports a range of output options, all configurable
4//! through feature flags.
5#![cfg_attr(target_arch = "riscv32", doc = "\n")]
6#![cfg_attr(
7    target_arch = "riscv32",
8    doc = "\nPlease note that you **need** to force frame pointers (i.e. `\"-C\", \"force-frame-pointers\",` in your `.cargo/config.toml`).\n"
9)]
10#![cfg_attr(
11    target_arch = "riscv32",
12    doc = "Otherwise the panic handler will emit a stack dump which needs tooling to decode it.\n\n"
13)]
14#![cfg_attr(target_arch = "riscv32", doc = "\n")]
15//! You can get an array of backtrace addresses (limited to 10 entries by default) via
16//! `arch::backtrace()` if you want to create a backtrace yourself (i.e. not using the panic
17//! handler).
18//!
19//! ## Features
20#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
21//! ## Additional configuration
22//!
23//! We've exposed some configuration options that don't fit into cargo
24//! features. These can be set via environment variables, or via cargo's `[env]`
25//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
26//! for this crate:
27#![doc = ""]
28#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_backtrace_config_table.md"))]
29#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
30#![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))]
31#![no_std]
32
33#[macro_use]
34extern crate esp_metadata_generated;
35
36#[cfg(feature = "defmt")]
37use defmt as _;
38#[cfg(feature = "println")]
39use esp_println as _;
40
41const MAX_BACKTRACE_ADDRESSES: usize =
42    esp_config::esp_config_int!(usize, "ESP_BACKTRACE_CONFIG_BACKTRACE_FRAMES");
43
44#[derive(Clone)]
45pub struct Backtrace(pub(crate) heapless::Vec<BacktraceFrame, MAX_BACKTRACE_ADDRESSES>);
46
47impl Backtrace {
48    /// Captures a stack backtrace.
49    #[inline]
50    pub fn capture() -> Self {
51        arch::backtrace()
52    }
53
54    /// Returns the backtrace frames as a slice.
55    #[inline]
56    pub fn frames(&self) -> &[BacktraceFrame] {
57        &self.0
58    }
59}
60
61#[derive(Clone)]
62pub struct BacktraceFrame {
63    pub(crate) pc: usize,
64}
65
66impl BacktraceFrame {
67    pub fn program_counter(&self) -> usize {
68        self.pc - crate::arch::RA_OFFSET
69    }
70}
71
72#[cfg(feature = "panic-handler")]
73const RESET: &str = "\u{001B}[0m";
74#[cfg(feature = "panic-handler")]
75const RED: &str = "\u{001B}[31m";
76
77#[cfg(all(feature = "panic-handler", feature = "defmt"))]
78macro_rules! println {
79    ($($arg:tt)*) => {
80        defmt::error!($($arg)*);
81    };
82}
83
84#[cfg(all(feature = "panic-handler", feature = "defmt", stack_dump))]
85pub(crate) use println;
86
87#[cfg(all(feature = "panic-handler", feature = "println"))]
88macro_rules! println {
89    ($($arg:tt)*) => {
90        esp_println::println!($($arg)*);
91    };
92}
93
94#[cfg(feature = "panic-handler")]
95fn set_color_code(_code: &str) {
96    #[cfg(all(feature = "colors", feature = "println"))]
97    {
98        println!("{}", _code);
99    }
100}
101
102#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")]
103#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
104pub(crate) mod arch;
105
106#[cfg(feature = "panic-handler")]
107#[panic_handler]
108fn panic_handler(info: &core::panic::PanicInfo) -> ! {
109    pre_backtrace();
110
111    set_color_code(RED);
112    println!("");
113    println!("====================== PANIC ======================");
114
115    println!("{}", info);
116    set_color_code(RESET);
117
118    cfg_if::cfg_if! {
119        if #[cfg(not(stack_dump))]
120        {
121            println!("");
122            println!("Backtrace:");
123            println!("");
124
125            let backtrace = Backtrace::capture();
126            #[cfg(target_arch = "riscv32")]
127            if backtrace.frames().is_empty() {
128                println!(
129                    "No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)"
130                );
131            }
132            for frame in backtrace.frames() {
133                println!("0x{:x}", frame.program_counter());
134            }
135        } else {
136            arch::dump_stack();
137        }
138    }
139
140    abort()
141}
142
143// Ensure that the address is in DRAM.
144//
145// Address ranges can be found in `esp-metadata/devices/$CHIP.toml` in the `device` table.
146fn is_valid_ram_address(address: u32) -> bool {
147    memory_range!("DRAM").contains(&address)
148}
149
150#[cfg(feature = "halt-cores")]
151fn halt() {
152    #[cfg(any(feature = "esp32", feature = "esp32s3"))]
153    {
154        #[cfg(feature = "esp32")]
155        mod registers {
156            pub(crate) const OPTIONS0: u32 = 0x3ff48000;
157            pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac;
158        }
159
160        #[cfg(feature = "esp32s3")]
161        mod registers {
162            pub(crate) const OPTIONS0: u32 = 0x60008000;
163            pub(crate) const SW_CPU_STALL: u32 = 0x600080bc;
164        }
165
166        let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
167
168        unsafe {
169            // We need to write the value "0x86" to stall a particular core. The write
170            // location is split into two separate bit fields named "c0" and "c1", and the
171            // two fields are located in different registers. Each core has its own pair of
172            // "c0" and "c1" bit fields.
173
174            let options0 = registers::OPTIONS0 as *mut u32;
175
176            options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010);
177
178            sw_cpu_stall.write_volatile(
179                sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26)
180                    | (0x21 << 20)
181                    | (0x21 << 26),
182            );
183        }
184    }
185}
186
187#[cfg(feature = "panic-handler")]
188fn pre_backtrace() {
189    #[cfg(feature = "custom-pre-backtrace")]
190    {
191        unsafe extern "Rust" {
192            fn custom_pre_backtrace();
193        }
194        unsafe { custom_pre_backtrace() }
195    }
196}
197
198#[cfg(feature = "panic-handler")]
199fn abort() -> ! {
200    println!("");
201    println!("");
202    println!("");
203
204    cfg_if::cfg_if! {
205        if #[cfg(feature = "semihosting")] {
206            arch::interrupt_free(|| {
207                semihosting::process::abort();
208            });
209        } else if #[cfg(feature = "halt-cores")] {
210            halt();
211        } else if #[cfg(feature = "custom-halt")] {
212            // call custom code
213            unsafe extern "Rust" {
214                fn custom_halt() -> !;
215            }
216            unsafe { custom_halt() }
217        }
218    }
219
220    #[allow(unreachable_code)]
221    arch::interrupt_free(|| {
222        #[allow(clippy::empty_loop)]
223        loop {}
224    })
225}