Skip to content

Commit

Permalink
Merge branch 'feature/enable_fp_backtracing' into 'master'
Browse files Browse the repository at this point in the history
feat(riscv): implement frame pointer option for backtracing

See merge request espressif/esp-idf!32342
  • Loading branch information
o-marshmallow committed Jan 13, 2025
2 parents f2c3fc2 + d6cd339 commit 52b558d
Show file tree
Hide file tree
Showing 32 changed files with 497 additions and 167 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ if(CONFIG_ESP_SYSTEM_USE_EH_FRAME)
list(APPEND link_options "-Wl,--eh-frame-hdr")
endif()

if(CONFIG_ESP_SYSTEM_USE_FRAME_POINTER)
list(APPEND compile_options "-fno-omit-frame-pointer")
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
list(APPEND compile_options "-mno-omit-leaf-frame-pointer")
endif()
endif()

list(APPEND link_options "-fno-lto")

if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
Expand Down
6 changes: 3 additions & 3 deletions components/app_trace/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ endif()

if(CONFIG_HEAP_TRACING_TOHOST)
list(APPEND srcs "heap_trace_tohost.c")
set_source_files_properties(heap_trace_tohost.c
PROPERTIES COMPILE_FLAGS
-Wno-frame-address)
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
set_source_files_properties(heap_trace_tohost.c PROPERTIES COMPILE_FLAGS -Wno-frame-address)
endif()
endif()

idf_component_register(SRCS "${srcs}"
Expand Down
8 changes: 3 additions & 5 deletions components/app_trace/heap_trace_tohost.c
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sdkconfig.h>
#include "sdkconfig.h"

#define HEAP_TRACE_SRCFILE /* don't warn on inclusion here */
#include "esp_heap_trace.h"
#undef HEAP_TRACE_SRCFILE
#include "esp_heap_caps.h"
#if CONFIG_APPTRACE_SV_ENABLE
#include "esp_app_trace.h"
Expand All @@ -16,7 +14,7 @@

#define STACK_DEPTH CONFIG_HEAP_TRACING_STACK_DEPTH

#ifdef CONFIG_HEAP_TRACING_TOHOST
#if CONFIG_HEAP_TRACING_TOHOST

#if !CONFIG_APPTRACE_SV_ENABLE
#error None of the heap tracing backends is enabled! You must enable SystemView compatible tracing to use this feature.
Expand Down
4 changes: 4 additions & 0 deletions components/esp_system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ else()
list(APPEND srcs "eh_frame_parser.c")
endif()

if(CONFIG_ESP_SYSTEM_USE_FRAME_POINTER)
list(APPEND srcs "fp_unwind.c")
endif()

if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM)
list(APPEND srcs "systick_etm.c")
endif()
Expand Down
39 changes: 30 additions & 9 deletions components/esp_system/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,38 @@ menu "ESP System Settings"
similar to that of DRAM region but without DMA. Speed wise RTC fast memory operates on
APB clock and hence does not have much performance impact.

config ESP_SYSTEM_USE_EH_FRAME
bool "Generate and use eh_frame for backtracing"
default n
choice ESP_BACKTRACING_METHOD
prompt "Backtracing method"
default ESP_SYSTEM_NO_BACKTRACE
depends on IDF_TARGET_ARCH_RISCV
help
Generate DWARF information for each function of the project. These information will parsed and used to
perform backtracing when panics occur. Activating this option will activate asynchronous frame unwinding
and generation of both .eh_frame and .eh_frame_hdr sections, resulting in a bigger binary size (20% to
100% larger). The main purpose of this option is to be able to have a backtrace parsed and printed by
the program itself, regardless of the serial monitor used.
This option shall NOT be used for production.
Configure how backtracing will be performed at runtime when a panic occurs.

config ESP_SYSTEM_NO_BACKTRACE
bool "No backtracing"
help
When selected, no backtracing will be performed at runtime. By using idf.py monitor, it
is still possible to get a backtrace when a panic occurs.

config ESP_SYSTEM_USE_EH_FRAME
bool "Generate and use eh_frame for backtracing"
help
Generate DWARF information for each function of the project. These information will parsed and used to
perform backtracing when panics occur. Activating this option will activate asynchronous frame
unwinding and generation of both .eh_frame and .eh_frame_hdr sections, resulting in a bigger binary
size (20% to 100% larger). The main purpose of this option is to be able to have a backtrace parsed
and printed by the program itself, regardless of the serial monitor used.
This option is not recommended to be used for production.

config ESP_SYSTEM_USE_FRAME_POINTER
bool "Use CPU Frame Pointer register"
help
This configuration allows the compiler to allocate CPU register s0 as the frame pointer. The main usage
of the frame pointer is to be able to generate a backtrace from the panic handler on exception.
Enabling this option results in bigger and slightly slower code since all functions will have
to populate this register and won't be able to use it as a general-purpose register anymore.

endchoice

menu "Memory protection"

Expand Down
117 changes: 117 additions & 0 deletions components/esp_system/fp_unwind.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "sdkconfig.h"
#include <string.h>
#include "esp_private/panic_internal.h"
#include "esp_memory_utils.h"
#include "riscv/libunwind-riscv.h"
#include "esp_private/fp_unwind.h"

#define FP_MAX_CALLERS 64
#define RA_INDEX_IN_FP -1
#define SP_INDEX_IN_FP -2

/**
* @brief When one step of the backtrace is generated, output it to the serial.
* This function can be overridden as it is defined as weak.
*
* @param pc Program counter of the backtrace step.
* @param sp Stack pointer of the backtrace step.
*/
void __attribute__((weak)) esp_fp_generated_step(uint32_t pc, uint32_t sp)
{
panic_print_str(" 0x");
panic_print_hex(pc);
panic_print_str(":0x");
panic_print_hex(sp);
}

/**
* @brief Check if the given pointer is a data pointer that can be dereferenced
*
* @param ptr Data pointer to check
*/
static inline bool esp_fp_ptr_is_data(void* ptr)
{
return esp_ptr_in_dram(ptr) || esp_ptr_external_ram(ptr);
}

/**
* @brief Generate a call stack starting at the given frame pointer.
*
* @note Since this function can be called from RAM (from heap trace), it shall also be put in RAM.
*
* @param frame[in] Frame pointer to start unrolling from.
* @param callers[out] Array of callers where 0 will store the most recent caller. Can be NULL.
* @param stacks[out] Array of callers' stacks where 0 will store the most recent caller's stack. Can be NULL.
* @param depth[in] Number of maximum entries to fill in the callers array.
*
* @returns Number of entries filled in the array.
*/
uint32_t IRAM_ATTR esp_fp_get_callers(uint32_t frame, void** callers, void** stacks, uint32_t depth)
{
uint32_t written = 0;
uint32_t pc = 0;
uint32_t sp = 0;
uint32_t* fp = (uint32_t*) frame;

if (depth == 0 || (callers == NULL && stacks == NULL)) {
return 0;
}

/**
* We continue filling the callers array until we meet a function in ROM (not compiled
* with frame pointer) or the pointer we retrieved is not in RAM (binary libraries
* not compiled with frame pointer configuration, which means what is stored in the register is not a valid pointer)
*/
while (depth > 0 && esp_fp_ptr_is_data(fp) && !esp_ptr_in_rom((void *) pc)) {
/* Dereference the RA register from the frame pointer and store it in PC */
pc = fp[RA_INDEX_IN_FP];
sp = fp[SP_INDEX_IN_FP];
fp = (uint32_t*) sp;

if (callers) {
callers[written] = (void*) pc;
}
if (stacks) {
stacks[written] = (void*) sp;
}

depth--;
written++;
}

return written;
}

/**
* @brief Print backtrace for the given execution frame.
*
* @param frame_or Snapshot of the CPU registers when the CPU stopped its normal execution.
*/
void esp_fp_print_backtrace(const void *frame_or)
{
assert(frame_or != NULL);
ExecutionFrame frame = *((ExecutionFrame*) frame_or);

const uint32_t sp = EXECUTION_FRAME_SP(frame);
const uint32_t pc = EXECUTION_FRAME_PC(frame);
const uint32_t fp = frame.s0;
void* callers[FP_MAX_CALLERS];
void* stacks[FP_MAX_CALLERS];

panic_print_str("Backtrace:");
esp_fp_generated_step(pc, sp);

uint32_t len = esp_fp_get_callers(fp, callers, stacks, FP_MAX_CALLERS);

for (int i = 0; i < len; i++) {
esp_fp_generated_step((uint32_t) callers[i], (uint32_t) stacks[i]);
}

panic_print_str("\r\n");
}
27 changes: 27 additions & 0 deletions components/esp_system/include/esp_private/fp_unwind.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef FP_UNWIND_H
#define FP_UNWIND_H

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Print backtrace for the given execution frame thanks to the frame pointers.
*
* @param frame_or Snapshot of the CPU registers when the program stopped its
* normal execution. This frame is usually generated on the
* stack when an exception or an interrupt occurs.
*/
void esp_fp_print_backtrace(const void *frame_or);

#ifdef __cplusplus
}
#endif

#endif // FP_UNWIND_H
20 changes: 11 additions & 9 deletions components/esp_system/port/arch/riscv/debug_helpers.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -17,19 +17,20 @@

#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
#include "esp_private/eh_frame_parser.h"
#endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME

#if !CONFIG_ESP_SYSTEM_USE_EH_FRAME
#elif CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
extern void esp_fp_print_backtrace(const void*);

#else // !CONFIG_ESP_SYSTEM_USE_EH_FRAME && !
/* Function used to print all the registers pointed by the given frame .*/
extern void panic_print_registers(const void *frame, int core);
#endif // !CONFIG_ESP_SYSTEM_USE_EH_FRAME
#endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME

/* Targets based on a RISC-V CPU cannot perform backtracing that easily.
* We have two options here:
* - Perform backtracing at runtime.
* - Perform backtracing at runtime thanks to the configuration options
* CONFIG_ESP_SYSTEM_USE_EH_FRAME and CONFIG_ESP_SYSTEM_USE_FRAME_POINTER.
* - Let IDF monitor do the backtracing for us. Used during panic already.
* This could be configurable, choosing one or the other depending on
* CONFIG_ESP_SYSTEM_USE_EH_FRAME configuration option.
*
* In both cases, this takes time, and we might be in an ISR, we must
* exit this handler as fast as possible, then we will simply print
Expand All @@ -54,14 +55,15 @@ esp_err_t IRAM_ATTR esp_backtrace_print(int depth)
memcpy(&backtrace_frame, frame, sizeof(esp_cpu_frame_t));

#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
esp_rom_printf("esp_backtrace_print: Print CPU %d (current core) backtrace\n", current_core);
esp_eh_frame_print_backtrace(frame);
#elif CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
esp_fp_print_backtrace(frame);
#else // CONFIG_ESP_SYSTEM_USE_EH_FRAME
esp_rom_printf("esp_backtrace_print: Print CPU %d (current core) registers\n", current_core);
panic_prepare_frame_from_ctx(&backtrace_frame);

panic_print_registers(&backtrace_frame, current_core);
esp_rom_printf("\r\n");
esp_rom_printf("Please enable CONFIG_ESP_SYSTEM_USE_FRAME_POINTER option to have a full backtrace.\r\n");
#endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME

return ESP_OK;
Expand Down
10 changes: 9 additions & 1 deletion components/esp_system/port/arch/riscv/panic_arch.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -24,6 +24,10 @@
#include "esp_private/cache_utils.h"
#endif

#if CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
#include "esp_private/fp_unwind.h"
#endif

#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
Expand Down Expand Up @@ -305,6 +309,7 @@ void panic_arch_fill_info(void *frame, panic_info_t *info)
info->addr = (void *) regs->mepc;
}

#if !CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
static void panic_print_basic_backtrace(const void *frame, int core)
{
// Basic backtrace
Expand All @@ -322,6 +327,7 @@ static void panic_print_basic_backtrace(const void *frame, int core)
}
}
}
#endif

void panic_print_backtrace(const void *frame, int core)
{
Expand All @@ -333,6 +339,8 @@ void panic_print_backtrace(const void *frame, int core)
} else {
esp_eh_frame_print_backtrace(frame);
}
#elif CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
esp_fp_print_backtrace(frame);
#else
panic_print_basic_backtrace(frame, core);
#endif
Expand Down
Loading

0 comments on commit 52b558d

Please sign in to comment.