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!()]
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
44pub struct Backtrace(pub(crate) heapless::Vec<BacktraceFrame, MAX_BACKTRACE_ADDRESSES>);
45
46impl Backtrace {
47    /// Captures a stack backtrace.
48    #[inline]
49    pub fn capture() -> Self {
50        arch::backtrace()
51    }
52
53    /// Returns the backtrace frames as a slice.
54    #[inline]
55    pub fn frames(&self) -> &[BacktraceFrame] {
56        &self.0
57    }
58}
59
60pub struct BacktraceFrame {
61    pub(crate) pc: usize,
62}
63
64impl BacktraceFrame {
65    pub fn program_counter(&self) -> usize {
66        self.pc - crate::arch::RA_OFFSET
67    }
68}
69
70#[cfg(feature = "panic-handler")]
71const RESET: &str = "\u{001B}[0m";
72#[cfg(feature = "panic-handler")]
73const RED: &str = "\u{001B}[31m";
74
75#[cfg(all(feature = "panic-handler", feature = "defmt"))]
76macro_rules! println {
77    ($($arg:tt)*) => {
78        defmt::error!($($arg)*);
79    };
80}
81
82#[cfg(all(feature = "panic-handler", feature = "defmt", stack_dump))]
83pub(crate) use println;
84
85#[cfg(all(feature = "panic-handler", feature = "println"))]
86macro_rules! println {
87    ($($arg:tt)*) => {
88        esp_println::println!($($arg)*);
89    };
90}
91
92#[cfg(feature = "panic-handler")]
93fn set_color_code(_code: &str) {
94    #[cfg(all(feature = "colors", feature = "println"))]
95    {
96        println!("{}", _code);
97    }
98}
99
100#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")]
101#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
102pub(crate) mod arch;
103
104#[cfg(feature = "panic-handler")]
105#[panic_handler]
106fn panic_handler(info: &core::panic::PanicInfo) -> ! {
107    pre_backtrace();
108
109    set_color_code(RED);
110    println!("");
111    println!("====================== PANIC ======================");
112
113    println!("{}", info);
114    set_color_code(RESET);
115
116    cfg_if::cfg_if! {
117        if #[cfg(not(stack_dump))]
118        {
119            println!("");
120            println!("Backtrace:");
121            println!("");
122
123            let backtrace = Backtrace::capture();
124            #[cfg(target_arch = "riscv32")]
125            if backtrace.frames().is_empty() {
126                println!(
127                    "No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)"
128                );
129            }
130            for frame in backtrace.frames() {
131                println!("0x{:x}", frame.program_counter());
132            }
133        } else {
134            arch::dump_stack();
135        }
136    }
137
138    abort()
139}
140
141// Ensure that the address is in DRAM.
142//
143// Address ranges can be found in `esp-metadata/devices/$CHIP.toml` in the `device` table.
144fn is_valid_ram_address(address: u32) -> bool {
145    memory_range!("DRAM").contains(&address)
146}
147
148#[cfg(feature = "halt-cores")]
149fn halt() {
150    #[cfg(any(feature = "esp32", feature = "esp32s3"))]
151    {
152        #[cfg(feature = "esp32")]
153        mod registers {
154            pub(crate) const OPTIONS0: u32 = 0x3ff48000;
155            pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac;
156        }
157
158        #[cfg(feature = "esp32s3")]
159        mod registers {
160            pub(crate) const OPTIONS0: u32 = 0x60008000;
161            pub(crate) const SW_CPU_STALL: u32 = 0x600080bc;
162        }
163
164        let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
165
166        unsafe {
167            // We need to write the value "0x86" to stall a particular core. The write
168            // location is split into two separate bit fields named "c0" and "c1", and the
169            // two fields are located in different registers. Each core has its own pair of
170            // "c0" and "c1" bit fields.
171
172            let options0 = registers::OPTIONS0 as *mut u32;
173
174            options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010);
175
176            sw_cpu_stall.write_volatile(
177                sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26)
178                    | (0x21 << 20)
179                    | (0x21 << 26),
180            );
181        }
182    }
183}
184
185#[cfg(feature = "panic-handler")]
186fn pre_backtrace() {
187    #[cfg(feature = "custom-pre-backtrace")]
188    {
189        unsafe extern "Rust" {
190            fn custom_pre_backtrace();
191        }
192        unsafe { custom_pre_backtrace() }
193    }
194}
195
196#[cfg(feature = "panic-handler")]
197fn abort() -> ! {
198    println!("");
199    println!("");
200    println!("");
201
202    cfg_if::cfg_if! {
203        if #[cfg(feature = "semihosting")] {
204            arch::interrupt_free(|| {
205                semihosting::process::abort();
206            });
207        } else if #[cfg(feature = "halt-cores")] {
208            halt();
209        } else if #[cfg(feature = "custom-halt")] {
210            // call custom code
211            unsafe extern "Rust" {
212                fn custom_halt() -> !;
213            }
214            unsafe { custom_halt() }
215        }
216    }
217
218    #[allow(unreachable_code)]
219    arch::interrupt_free(|| {
220        #[allow(clippy::empty_loop)]
221        loop {}
222    })
223}