Skip to content

Commit

Permalink
Backtrace support for the ESP-IDF framework
Browse files Browse the repository at this point in the history
  • Loading branch information
imarkov committed Aug 25, 2021
1 parent 4f925f8 commit 2ede3b0
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
98 changes: 98 additions & 0 deletions src/backtrace/espidf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Implementation of backtracing for ESP-IDF.
use core::ffi::c_void;

#[derive(Clone, Debug)]
pub struct Frame {
ip: *mut c_void,
sp: *mut c_void,
}

// SAFETY: The pointers returned in this struct can be used from any thread.
unsafe impl Send for Frame {}
unsafe impl Sync for Frame {}

impl Frame {
pub fn ip(&self) -> *mut c_void {
self.ip
}

pub fn sp(&self) -> *mut c_void {
self.sp
}

pub fn symbol_address(&self) -> *mut c_void {
0 as *mut _
}

pub fn module_base_address(&self) -> Option<*mut c_void> {
None
}
}

// Reference: https://github.com/espressif/esp-idf/blob/master/components/esp_system/include/esp_debug_helpers.h
#[cfg(target_arch = "xtensa")]
#[inline(always)]
pub fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
#[repr(C)]
struct esp_backtrace_frame_t {
pc: *mut c_void, // PC of the current frame
sp: *mut c_void, // SP of the current frame
next_pc: *mut c_void, // PC of the current frame's caller
exc_frame: *const c_void, // Pointer to the full frame data structure, if applicable
}

impl From<&esp_backtrace_frame_t> for Frame {
#[inline(always)]
fn from(esp_frame: &esp_backtrace_frame_t) -> Self {
let mut pc = esp_frame.pc as u32;

// Reference: https://github.com/espressif/esp-idf/blob/master/components/esp_hw_support/include/soc/cpu.h#L38
if (pc & 0x80000000) != 0 {
// Top two bits of a0 (return address) specify window increment. Overwrite to map to address space.
pc = (pc & 0x3fffffff) | 0x40000000;
}
pc = pc - 3; // Minus 3 to get PC of previous instruction (i.e. instruction executed before return address)

Self {
ip: pc as *mut _,
sp: esp_frame.sp,
}
}
}

extern "C" {
fn esp_backtrace_get_start(
pc: *mut *mut c_void,
sp: *mut *mut c_void,
next_pc: *mut *mut c_void,
);
fn esp_backtrace_get_next_frame(frame: *mut esp_backtrace_frame_t) -> bool;
}

let mut frame: esp_backtrace_frame_t = unsafe { ::core::mem::zeroed() };

unsafe {
esp_backtrace_get_start(
&mut frame.pc as *mut _,
&mut frame.sp as *mut _,
&mut frame.next_pc as *mut _,
)
};

cb(&super::Frame {
inner: (&frame).into(),
});

while unsafe { esp_backtrace_get_next_frame(&mut frame as *mut _) } {
cb(&super::Frame {
inner: (&frame).into(),
});
}
}

#[cfg(not(target_arch = "xtensa"))]
#[inline(always)]
pub fn trace(_cb: &mut dyn FnMut(&super::Frame) -> bool) {
// RiscV is not supported yet
}
5 changes: 5 additions & 0 deletions src/backtrace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ cfg_if::cfg_if! {
all(
unix,
not(target_os = "emscripten"),
not(target_os = "espidf"),
not(all(target_os = "ios", target_arch = "arm")),
),
all(
Expand All @@ -154,6 +155,10 @@ cfg_if::cfg_if! {
pub(crate) use self::dbghelp::Frame as FrameImp;
#[cfg(target_env = "msvc")] // only used in dbghelp symbolize
pub(crate) use self::dbghelp::StackFrame;
} else if #[cfg(target_os = "espidf")] {
pub(crate) mod espidf;
use self::espidf::trace as trace_imp;
pub(crate) use self::espidf::Frame as FrameImp;
} else {
mod noop;
use self::noop::trace as trace_imp;
Expand Down
9 changes: 9 additions & 0 deletions src/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ impl BacktraceFrameFmt<'_, '_, '_> {
// printing addresses in our own format here.
if cfg!(target_os = "fuchsia") {
self.print_raw_fuchsia(frame_ip)?;
}
// ESP-IDF is unable to symbolize because the firmware running on the chip
// does not have any symbolic information.
// The TTY monitor software however is symbolizing all outputted hexadecimal
// addresses which happen to fall within the memory region where the executed
// firmware is mapped.
// Therefore, it is enough to just print the frame instruction pointer value.
else if cfg!(target_os = "espidf") {
write!(self.fmt.fmt, "{:4}: {:?}", self.fmt.frame_index, frame_ip)
} else {
self.print_raw_generic(frame_ip, symbol_name, filename, lineno, colno)?;
}
Expand Down
1 change: 1 addition & 0 deletions src/symbolize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ cfg_if::cfg_if! {
any(unix, windows),
not(target_vendor = "uwp"),
not(target_os = "emscripten"),
not(target_os = "espidf"),
any(not(backtrace_in_libstd), feature = "backtrace"),
))] {
mod gimli;
Expand Down

0 comments on commit 2ede3b0

Please sign in to comment.