mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
Merge branch 'feature/enable_fp_backtracing' into 'master'
feat(riscv): implement frame pointer option for backtracing See merge request espressif/esp-idf!32342
This commit is contained in:
commit
52b558d218
@ -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")
|
||||
|
@ -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}"
|
||||
|
@ -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"
|
||||
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
||||
|
117
components/esp_system/fp_unwind.c
Normal file
117
components/esp_system/fp_unwind.c
Normal file
@ -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
components/esp_system/include/esp_private/fp_unwind.h
Normal file
27
components/esp_system/include/esp_private/fp_unwind.h
Normal file
@ -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
|
@ -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
|
||||
*/
|
||||
@ -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
|
||||
@ -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;
|
||||
|
@ -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
|
||||
*/
|
||||
@ -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"
|
||||
@ -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
|
||||
@ -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)
|
||||
{
|
||||
@ -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
|
||||
|
@ -29,14 +29,12 @@
|
||||
#include "riscv/rvruntime-frames.h"
|
||||
#endif //CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
|
||||
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||
#include "esp_private/eh_frame_parser.h"
|
||||
#endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV && !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_IDF_TARGET_ARCH_RISCV && !CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||
#if CONFIG_ESP_SYSTEM_NO_BACKTRACE
|
||||
/* If the target doesn't support backtrace, we will show CPU registers*/
|
||||
#define BACKTRACE_MSG "registers"
|
||||
#else // !CONFIG_ESP_SYSTEM_NO_BACKTRACE
|
||||
#define BACKTRACE_MSG "backtrace"
|
||||
#endif
|
||||
|
||||
/* We will use this function in order to simulate an `abort()` occurring in
|
||||
* a different context than the one it's called from. */
|
||||
@ -390,13 +388,11 @@ void task_wdt_timeout_abort(bool current_core)
|
||||
g_twdt_isr = true;
|
||||
void *frame = (void *) snapshot.pxTopOfStack;
|
||||
|
||||
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME | CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
if (current_core) {
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", xPortGetCoreID());
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) " BACKTRACE_MSG, xPortGetCoreID());
|
||||
} else {
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", xPortGetCoreID());
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d " BACKTRACE_MSG, xPortGetCoreID());
|
||||
}
|
||||
#endif
|
||||
|
||||
xt_unhandled_exception(frame);
|
||||
}
|
||||
@ -412,7 +408,7 @@ static void task_wdt_timeout_handling(int cores_fail, bool panic)
|
||||
if ((cores_fail & BIT(0)) && (cores_fail & BIT(1))) {
|
||||
/* In the case where both CPUs have failing tasks, print the current CPU backtrace and then let the
|
||||
* other core fail. */
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) " BACKTRACE_MSG, current_core);
|
||||
esp_backtrace_print(100);
|
||||
/* TODO: the interrupt we send should have the highest priority */
|
||||
esp_crosscore_int_send_twdt_abort(other_core);
|
||||
@ -430,13 +426,13 @@ static void task_wdt_timeout_handling(int cores_fail, bool panic)
|
||||
} else {
|
||||
/* Print backtrace of the core that failed to reset the watchdog */
|
||||
if (cores_fail & BIT(current_core)) {
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) " BACKTRACE_MSG, current_core);
|
||||
esp_backtrace_print(100);
|
||||
}
|
||||
#if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
|
||||
const int other_core = !current_core;
|
||||
if (cores_fail & BIT(other_core)) {
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", other_core);
|
||||
ESP_EARLY_LOGE(TAG, "Print CPU %d " BACKTRACE_MSG, other_core);
|
||||
esp_crosscore_int_send_print_backtrace(other_core);
|
||||
}
|
||||
#endif // !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -11,16 +11,16 @@
|
||||
#include <stdlib.h>
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_rom_uart.h"
|
||||
|
||||
#if __XTENSA__
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "xtensa_api.h" // Replace with interrupt allocator API (IDF-3891)
|
||||
#include "esp_debug_helpers.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_rom_uart.h"
|
||||
#include "hal/misc.h"
|
||||
|
||||
#define SW_ISR_LEVEL_1 7
|
||||
@ -145,4 +145,55 @@ TEST_CASE("Test esp_backtrace_print_all_tasks()", "[esp_system]")
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
|
||||
#if CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
|
||||
|
||||
void my_putc(char c)
|
||||
{
|
||||
static bool first_exec = 1;
|
||||
esp_rom_output_putc(c);
|
||||
|
||||
if (first_exec) {
|
||||
first_exec = false;
|
||||
*((int*) 1) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE("Test backtrace detects corrupted frames", "[esp_system]")
|
||||
TEST_CASE("Backtrace detects ROM functions", "[esp_system]")
|
||||
{
|
||||
esp_rom_install_channel_putc(1, my_putc);
|
||||
esp_rom_printf("foo");
|
||||
}
|
||||
|
||||
static void __attribute__((naked)) non_framed_function(void)
|
||||
{
|
||||
asm volatile(
|
||||
"add sp, sp, -16\n"
|
||||
/* Save anything on the top of the stack, not the frame pointer nor RA */
|
||||
"sw zero, 12(sp)\n"
|
||||
"sw s0, 8(sp)\n"
|
||||
/* Set s0 to a constant that cannot be interpreted as an address */
|
||||
"li s0, 0x42\n"
|
||||
/* Fail to trigger an exception */
|
||||
"sw s0, (s0)\n"
|
||||
"ret\n"
|
||||
::
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that backtracing detects when the frame is it unwinding encounters a function (or routine)
|
||||
* that was not compiles with frame pointer option. This does NOT guarantee that all the call stack
|
||||
* will be valid (some data pointers may be interpreted as frames), but it guarantees that no
|
||||
* exception will be triggered.
|
||||
*/
|
||||
TEST_CASE("Backtrace detects corrupted frames", "[esp_system]")
|
||||
{
|
||||
/* Add some prints to make sure the compiler doesn't optimize it with a tail call */
|
||||
non_framed_function();
|
||||
printf("ERROR: must not reach this point\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
@ -32,3 +32,27 @@ def test_stack_smash_protection(dut: Dut) -> None:
|
||||
dut.write('"stack smashing protection"')
|
||||
dut.expect_exact('Stack smashing protect failure!')
|
||||
dut.expect_exact('Rebooting...')
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
# Testing this feature on a single RISC-V target is enough
|
||||
pytest.param('framepointer', marks=[pytest.mark.esp32c3]),
|
||||
]
|
||||
)
|
||||
def test_frame_pointer_backtracing(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('"Backtrace detects corrupted frames"')
|
||||
dut.expect_exact('Guru Meditation Error')
|
||||
# The backtrace should be composed of a single entry
|
||||
dut.expect(r'Backtrace: 0x[0-9a-f]{8}:0x[0-9a-f]{8}\s*[\r]?\n')
|
||||
dut.expect_exact('Rebooting...')
|
||||
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('"Backtrace detects ROM functions"')
|
||||
dut.expect_exact('Guru Meditation Error')
|
||||
# The backtrace should have two entries
|
||||
dut.expect(r'Backtrace: 0x[0-9a-f]{8}:0x[0-9a-f]{8} 0x[0-9a-f]{8}:0x[0-9a-f]{8}\s*[\r]?\n')
|
||||
dut.expect_exact('Rebooting...')
|
||||
|
@ -0,0 +1 @@
|
||||
CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y
|
@ -31,15 +31,14 @@ menu "Heap memory debugging"
|
||||
bool "Disabled"
|
||||
config HEAP_TRACING_STANDALONE
|
||||
bool "Standalone"
|
||||
select HEAP_TRACING
|
||||
config HEAP_TRACING_TOHOST
|
||||
bool "Host-based"
|
||||
select HEAP_TRACING
|
||||
endchoice
|
||||
|
||||
config HEAP_TRACING
|
||||
bool
|
||||
default F
|
||||
default n if HEAP_TRACING_OFF
|
||||
default y if !HEAP_TRACING_OFF
|
||||
help
|
||||
Enables/disables heap tracing API.
|
||||
|
||||
@ -76,8 +75,8 @@ menu "Heap memory debugging"
|
||||
|
||||
config HEAP_TRACING_STACK_DEPTH
|
||||
int "Heap tracing stack depth"
|
||||
range 0 0 if IDF_TARGET_ARCH_RISCV # Disabled for RISC-V due to `__builtin_return_address` limitation
|
||||
default 0 if IDF_TARGET_ARCH_RISCV
|
||||
range 0 0 if IDF_TARGET_ARCH_RISCV && !ESP_SYSTEM_USE_FRAME_POINTER
|
||||
default 0 if IDF_TARGET_ARCH_RISCV && !ESP_SYSTEM_USE_FRAME_POINTER
|
||||
range 0 32
|
||||
default 2
|
||||
depends on HEAP_TRACING
|
||||
|
@ -4,13 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <sdkconfig.h>
|
||||
#include "sdkconfig.h"
|
||||
#include <inttypes.h>
|
||||
#include "esp_log.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"
|
||||
#include "esp_attr.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@ -22,8 +20,6 @@ static __attribute__((unused)) const char* TAG = "heaptrace";
|
||||
|
||||
#define STACK_DEPTH CONFIG_HEAP_TRACING_STACK_DEPTH
|
||||
|
||||
#if CONFIG_HEAP_TRACING_STANDALONE
|
||||
|
||||
typedef enum {
|
||||
TRACING_STARTED, // start recording allocs and free
|
||||
TRACING_STOPPED, // stop recording allocs and free
|
||||
@ -672,5 +668,3 @@ static HEAP_IRAM_ATTR void list_find_and_remove(void* p)
|
||||
}
|
||||
|
||||
#include "heap_trace.inc"
|
||||
|
||||
#endif // CONFIG_HEAP_TRACING_STANDALONE
|
||||
|
@ -15,10 +15,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_HEAP_TRACING) && !defined(HEAP_TRACE_SRCFILE)
|
||||
#warning "esp_heap_trace.h is included but heap tracing is disabled in menuconfig, functions are no-ops"
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_HEAP_TRACING_STACK_DEPTH
|
||||
#define CONFIG_HEAP_TRACING_STACK_DEPTH 0
|
||||
#endif
|
||||
|
@ -1,18 +1,10 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <sdkconfig.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_memory_layout.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_cpu.h"
|
||||
@ -31,11 +23,8 @@ inline static uint32_t get_ccount(void)
|
||||
/* Architecture-specific return value of __builtin_return_address which
|
||||
* should be interpreted as an invalid address.
|
||||
*/
|
||||
#ifdef __XTENSA__
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
#define HEAP_ARCH_INVALID_PC 0x40000000
|
||||
#else
|
||||
#define HEAP_ARCH_INVALID_PC 0x00000000
|
||||
#endif
|
||||
|
||||
// Caller is 2 stack frames deeper than we care about
|
||||
#define STACK_OFFSET 2
|
||||
@ -94,6 +83,27 @@ static HEAP_IRAM_ATTR __attribute__((noinline)) void get_call_stack(void **calle
|
||||
TEST_STACK(31);
|
||||
}
|
||||
|
||||
#else // !CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
|
||||
extern uint32_t esp_fp_get_callers(uint32_t frame, void** callers, void** stacks, uint32_t depth);
|
||||
|
||||
static HEAP_IRAM_ATTR __attribute__((noinline)) void get_call_stack(void **callers)
|
||||
{
|
||||
uint32_t fp = (uint32_t) __builtin_frame_address(0);
|
||||
memset(callers, 0, sizeof(void *) * STACK_DEPTH);
|
||||
|
||||
#if CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
|
||||
/* We can skip the current return address since this function won't be inlined */
|
||||
esp_fp_get_callers(fp, callers, NULL, STACK_DEPTH);
|
||||
#else
|
||||
/* RISC-V compiler doesn't support `__builtin_frame_address` with a parameter bigger than 0 */
|
||||
callers[0] = (void*) fp;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ESP_STATIC_ASSERT(STACK_DEPTH >= 0 && STACK_DEPTH <= 32, "CONFIG_HEAP_TRACING_STACK_DEPTH must be in range 0-32");
|
||||
|
||||
typedef enum {
|
||||
|
@ -94,17 +94,13 @@ def test_heap_misc_options(dut: Dut) -> None:
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'target',
|
||||
[
|
||||
'esp32',
|
||||
]
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'heap_trace',
|
||||
'heap_trace_hashmap'
|
||||
pytest.param('heap_trace_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('heap_trace_hashmap_esp32', marks=[pytest.mark.esp32]),
|
||||
pytest.param('heap_trace_esp32c3', marks=[pytest.mark.esp32c3]),
|
||||
pytest.param('heap_trace_hashmap_esp32c3', marks=[pytest.mark.esp32c3])
|
||||
]
|
||||
)
|
||||
def test_heap_trace_dump(dut: Dut) -> None:
|
||||
|
@ -0,0 +1,3 @@
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y
|
||||
CONFIG_HEAP_TRACING_STANDALONE=y
|
@ -0,0 +1,5 @@
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y
|
||||
CONFIG_HEAP_TRACING_STANDALONE=y
|
||||
CONFIG_HEAP_TRACE_HASH_MAP=y
|
||||
CONFIG_HEAP_TRACE_HASH_MAP_SIZE=10
|
@ -234,7 +234,7 @@ If :doc:`IDF Monitor <tools/idf-monitor>` is used, Program Counter values will b
|
||||
#5 0x00000000 in ?? ()
|
||||
Backtrace stopped: frame did not save the PC
|
||||
|
||||
While the backtrace above is very handy, it requires the user to use :doc:`IDF Monitor <tools/idf-monitor>`. Thus, in order to generate and print a backtrace while using another monitor program, it is possible to activate :ref:`CONFIG_ESP_SYSTEM_USE_EH_FRAME` option from the menuconfig.
|
||||
While the backtrace above is very handy, it requires the user to use :doc:`IDF Monitor <tools/idf-monitor>`. Thus, in order to generate and print a backtrace while using another monitor program, it is possible to activate ``CONFIG_ESP_SYSTEM_USE_EH_FRAME`` option from the menuconfig, under the "Backtracing method" menu.
|
||||
|
||||
This option will let the compiler generate DWARF information for each function of the project. Then, when a CPU exception occurs, the panic handler will parse these data and determine the backtrace of the task that failed. The output looks like this:
|
||||
|
||||
@ -245,7 +245,13 @@ If :doc:`IDF Monitor <tools/idf-monitor>` is used, Program Counter values will b
|
||||
These ``PC:SP`` pairs represent the PC (Program Counter) and SP (Stack Pointer) for each stack frame of the current task.
|
||||
|
||||
|
||||
The main benefit of the :ref:`CONFIG_ESP_SYSTEM_USE_EH_FRAME` option is that the backtrace is generated by the board itself (without the need for :doc:`IDF Monitor <tools/idf-monitor>`). However, the option's drawback is that it results in an increase of the compiled binary's size (ranging from 20% to 100% increase in size). Furthermore, this option causes debug information to be included within the compiled binary. Therefore, users are strongly advised not to enable this option in mass/final production builds.
|
||||
The main benefit of the ``CONFIG_ESP_SYSTEM_USE_EH_FRAME`` option is that the backtrace is generated by the board itself (without the need for :doc:`IDF Monitor <tools/idf-monitor>`). However, the option's drawback is that it results in an increase of the compiled binary's size (ranging from 20% to 100% increase in size). Furthermore, this option causes debug information to be included within the compiled binary. Therefore, users are strongly advised not to enable this option in mass/final production builds.
|
||||
|
||||
Another option to generate such backtrace on the device itself is to enable ``CONFIG_ESP_SYSTEM_USE_FRAME_POINTER`` option from the menuconfig, under the "Backtracing method" menu.
|
||||
|
||||
This option will let the compiler reserve a CPU register that keeps track of the frame of each routine of the program. This registers makes it possible for the panic handler to unwind the call stack at any given time, and more importantly, when a CPU exception occurs.
|
||||
|
||||
Enabling ``CONFIG_ESP_SYSTEM_USE_FRAME_POINTER`` option will result in an increase of the compiled binary's size of around +5-6% and a performance decrease of around 1%. Contrarily to the ``CONFIG_ESP_SYSTEM_USE_EH_FRAME`` option, the compiler won't generate debug information in the generated binary, so it is possible to use this feature in mass/final production builds.
|
||||
|
||||
To find the location where a fatal error has happened, look at the lines which follow the "Backtrace" line. Fatal error location is the top line, and subsequent lines show the call stack.
|
||||
|
||||
|
@ -269,79 +269,6 @@ The following code snippet demonstrates how application code would typically ini
|
||||
|
||||
The output from the heap trace has a similar format to the following example:
|
||||
|
||||
.. only:: CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
====== Heap Trace: 8 records (8 capacity) ======
|
||||
6 bytes (@ 0x3fc9f620, Internal) allocated CPU 0 ccount 0x1a31ac84 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
9 bytes (@ 0x3fc9f630, Internal) allocated CPU 0 ccount 0x1a31b618 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
12 bytes (@ 0x3fc9f640, Internal) allocated CPU 0 ccount 0x1a31bfac caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
15 bytes (@ 0x3fc9f650, Internal) allocated CPU 0 ccount 0x1a31c940 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
18 bytes (@ 0x3fc9f664, Internal) allocated CPU 0 ccount 0x1a31d2d4 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
21 bytes (@ 0x3fc9f67c, Internal) allocated CPU 0 ccount 0x1a31dc68 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
24 bytes (@ 0x3fc9f698, Internal) allocated CPU 0 ccount 0x1a31e600 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
6 bytes (@ 0x3fc9f6b4, Internal) allocated CPU 0 ccount 0x1a320698 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
====== Heap Trace Summary ======
|
||||
Mode: Heap Trace All
|
||||
6 bytes alive in trace (1/8 allocations)
|
||||
records: 8 (8 capacity, 8 high water mark)
|
||||
total allocations: 9
|
||||
total frees: 8
|
||||
================================
|
||||
|
||||
.. only:: CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
|
||||
.. code-block:: none
|
||||
@ -363,6 +290,79 @@ The output from the heap trace has a similar format to the following example:
|
||||
total frees: 8
|
||||
================================
|
||||
|
||||
Or the following example, when the ``CONFIG_ESP_SYSTEM_USE_FRAME_POINTER`` option is enabled and the stack depth is configured properly:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
====== Heap Trace: 8 records (8 capacity) ======
|
||||
6 bytes (@ 0x3fc9f620, Internal) allocated CPU 0 ccount 0x1a31ac84 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
9 bytes (@ 0x3fc9f630, Internal) allocated CPU 0 ccount 0x1a31b618 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
12 bytes (@ 0x3fc9f640, Internal) allocated CPU 0 ccount 0x1a31bfac caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
15 bytes (@ 0x3fc9f650, Internal) allocated CPU 0 ccount 0x1a31c940 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
18 bytes (@ 0x3fc9f664, Internal) allocated CPU 0 ccount 0x1a31d2d4 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
21 bytes (@ 0x3fc9f67c, Internal) allocated CPU 0 ccount 0x1a31dc68 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
24 bytes (@ 0x3fc9f698, Internal) allocated CPU 0 ccount 0x1a31e600 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
freed by 0x403839e4:0x42008096
|
||||
0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40
|
||||
0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)
|
||||
|
||||
6 bytes (@ 0x3fc9f6b4, Internal) allocated CPU 0 ccount 0x1a320698 caller 0x40376321:0x40376379
|
||||
0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84
|
||||
0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110
|
||||
|
||||
====== Heap Trace Summary ======
|
||||
Mode: Heap Trace All
|
||||
6 bytes alive in trace (1/8 allocations)
|
||||
records: 8 (8 capacity, 8 high water mark)
|
||||
total allocations: 9
|
||||
total frees: 8
|
||||
================================
|
||||
|
||||
.. note::
|
||||
|
||||
The above example output uses :doc:`IDF Monitor </api-guides/tools/idf-monitor>` to automatically decode PC addresses to their source files and line numbers.
|
||||
@ -396,6 +396,10 @@ In ``HEAP_TRACE_ALL``:
|
||||
|
||||
The depth of the call stack recorded for each trace entry can be configured in the project configuration menu, under ``Heap Memory Debugging`` > ``Enable heap tracing`` > :ref:`CONFIG_HEAP_TRACING_STACK_DEPTH`. Up to 32 stack frames can be recorded for each allocation (the default is 2). Each additional stack frame increases the memory usage of each ``heap_trace_record_t`` record by eight bytes.
|
||||
|
||||
.. only:: CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
|
||||
By default, the depth of the call stack recorded for each trace entry is 0, which means that only the direct caller of the memory allocation function can be retrieve. However, when the ``CONFIG_ESP_SYSTEM_USE_FRAME_POINTER`` option is enabled, this call stack depth can be configured in the project configuration menu, under ``Heap Memory Debugging`` > ``Enable heap tracing`` > :ref:`CONFIG_HEAP_TRACING_STACK_DEPTH`. Up to 32 stack frames can be recorded for each allocation (the default is 2). Each additional stack frame increases the memory usage of each ``heap_trace_record_t`` record by eight bytes.
|
||||
|
||||
Finally, the total number of the 'leaked' bytes (bytes allocated but not freed while the trace is running) is printed together with the total number of allocations it represents.
|
||||
|
||||
Using hashmap for increased performance
|
||||
|
@ -234,7 +234,7 @@
|
||||
#5 0x00000000 in ?? ()
|
||||
Backtrace stopped: frame did not save the PC
|
||||
|
||||
虽然以上的回溯信息非常方便,但要求用户使用 :doc:`IDF 监视器 <tools/idf-monitor>`。因此,如果用户希望使用其它的串口监控软件也能显示堆栈回溯信息,则需要在 menuconfig 中启用 :ref:`CONFIG_ESP_SYSTEM_USE_EH_FRAME` 选项。
|
||||
虽然以上的回溯信息非常方便,但要求用户使用 :doc:`IDF 监视器 <tools/idf-monitor>`。因此,如果用户希望使用其它的串口监控软件也能显示堆栈回溯信息,则需要在 menuconfig 中启用 ``CONFIG_ESP_SYSTEM_USE_EH_FRAME`` 选项。
|
||||
|
||||
该选项会让编译器为项目的每个函数生成 DWARF 信息。然后,当 CPU 异常发生时,紧急处理程序将解析这些数据并生成出错任务的堆栈回溯信息。输出结果如下:
|
||||
|
||||
@ -245,7 +245,7 @@
|
||||
这些 ``PC:SP`` 对代表当前任务每一个栈帧的程序计数器值 (Program Counter) 和栈顶地址 (Stack Pointer)。
|
||||
|
||||
|
||||
:ref:`CONFIG_ESP_SYSTEM_USE_EH_FRAME` 选项的主要优点是,回溯信息可以由程序自己解析生成并打印(而不依靠 :doc:`tools/idf-monitor`)。但是该选项会导致编译后的二进制文件更大(增幅可达 20% 甚至 100%)。此外,该选项会将调试信息也保存在二进制文件里。因此,强烈不建议用户在量产/生产版本中启用该选项。
|
||||
``CONFIG_ESP_SYSTEM_USE_EH_FRAME`` 选项的主要优点是,回溯信息可以由程序自己解析生成并打印(而不依靠 :doc:`tools/idf-monitor`)。但是该选项会导致编译后的二进制文件更大(增幅可达 20% 甚至 100%)。此外,该选项会将调试信息也保存在二进制文件里。因此,强烈不建议用户在量产/生产版本中启用该选项。
|
||||
|
||||
若要查找发生严重错误的代码位置,请查看 "Backtrace" 的后面几行,发生严重错误的代码显示在顶行,后续几行显示的是调用堆栈。
|
||||
|
||||
|
@ -53,7 +53,7 @@ static void alloc_task(void *p)
|
||||
snprintf(task_name, sizeof(task_name), "free%d", task_args->idx);
|
||||
xTaskCreatePinnedToCore(free_task, task_name, 2500, queue, 5, NULL, CONFIG_FREERTOS_NUMBER_OF_CORES-1);
|
||||
|
||||
// here GDB will stop at brekpoint and execute OpenOCD command to start tracing
|
||||
// here GDB will stop at breakpoint and execute OpenOCD command to start tracing
|
||||
for(int i = 1; i < 10; i++) {
|
||||
uint32_t sz = 2*i*(task_args->idx + 1);
|
||||
void *p = malloc(sz/2);
|
||||
@ -100,6 +100,6 @@ void app_main(void)
|
||||
uint32_t val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Got notify val %"PRIu32, val);
|
||||
}
|
||||
// here GDB will stop at brekpoint and execute OpenOCD command to stop tracing
|
||||
// here GDB will stop at breakpoint and execute OpenOCD command to stop tracing
|
||||
heap_trace_stop();
|
||||
}
|
||||
|
@ -24,3 +24,5 @@ CONFIG_APPTRACE_SV_EVT_TIMER_EXIT_ENABLE=y
|
||||
CONFIG_LOG_COLORS=n
|
||||
# Enable heap tracing to host
|
||||
CONFIG_HEAP_TRACING_TOHOST=y
|
||||
# For RISC-V targets
|
||||
CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y
|
||||
|
@ -75,6 +75,8 @@ void test_setup_coredump_summary(void);
|
||||
void test_coredump_summary(void);
|
||||
#endif
|
||||
|
||||
void test_panic_print_backtrace(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -119,6 +119,9 @@ void app_main(void)
|
||||
HANDLE_TEST(test_name, test_assert_cache_disabled);
|
||||
HANDLE_TEST(test_name, test_assert_cache_write_back_error_can_print_backtrace);
|
||||
HANDLE_TEST(test_name, test_tcb_corrupted);
|
||||
#if CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
|
||||
HANDLE_TEST(test_name, test_panic_print_backtrace);
|
||||
#endif
|
||||
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH && CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF
|
||||
HANDLE_TEST(test_name, test_setup_coredump_summary);
|
||||
HANDLE_TEST(test_name, test_coredump_summary);
|
||||
|
@ -344,3 +344,34 @@ void test_capture_dram(void)
|
||||
assert(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if CONFIG_ESP_SYSTEM_USE_FRAME_POINTER
|
||||
|
||||
static NOINLINE_ATTR void step3(void) {
|
||||
printf("Step 3\n");
|
||||
/* For some reason, the compiler doesn't generate the proper sequence for `panic_abort` function:
|
||||
* the `ra` register is not saved on the stack upon entry */
|
||||
abort();
|
||||
}
|
||||
|
||||
static NOINLINE_ATTR void step2(void) {
|
||||
step3();
|
||||
printf("Step 2\n");
|
||||
}
|
||||
|
||||
static NOINLINE_ATTR void step1(void) {
|
||||
step2();
|
||||
printf("Step 1\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a stack trace of several functions that will be shown at runtime,
|
||||
* The functions must not be inlined!
|
||||
*/
|
||||
void test_panic_print_backtrace(void)
|
||||
{
|
||||
step1();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -59,6 +59,11 @@ CONFIGS = [
|
||||
pytest.param('panic', marks=TARGETS_ALL),
|
||||
]
|
||||
|
||||
CONFIGS_BACKTRACE = [
|
||||
# One single-core target and one dual-core target is enough
|
||||
pytest.param('framepointer', marks=[pytest.mark.esp32c3, pytest.mark.esp32p4]),
|
||||
]
|
||||
|
||||
CONFIGS_DUAL_CORE = [
|
||||
pytest.param('coredump_flash_bin_crc', marks=TARGETS_DUAL_CORE),
|
||||
pytest.param('coredump_flash_elf_sha', marks=TARGETS_DUAL_CORE),
|
||||
@ -1090,3 +1095,22 @@ def test_tcb_corrupted(dut: PanicTestDut, target: str, config: str, test_func_na
|
||||
expected_backtrace=None,
|
||||
expected_coredump=coredump_pattern
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('config', CONFIGS_BACKTRACE, indirect=True)
|
||||
@pytest.mark.generic
|
||||
def test_panic_print_backtrace(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
||||
dut.run_test_func(test_func_name)
|
||||
regex_pattern = rb'abort\(\) was called at PC [0-9xa-f]+ on core 0'
|
||||
dut.expect(regex_pattern)
|
||||
dut.expect_backtrace()
|
||||
dut.expect_elf_sha256()
|
||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
||||
|
||||
coredump_pattern = re.compile(PANIC_ABORT_PREFIX + regex_pattern.decode('utf-8'))
|
||||
common_test(
|
||||
dut,
|
||||
config,
|
||||
expected_backtrace=None,
|
||||
expected_coredump=[coredump_pattern]
|
||||
)
|
||||
|
@ -81,7 +81,6 @@ class PanicTestDut(IdfDut):
|
||||
pass
|
||||
|
||||
def expect_backtrace(self, corrupted: bool = False) -> None:
|
||||
assert self.is_xtensa, 'Backtrace can be printed only on Xtensa'
|
||||
match = self.expect(r'Backtrace:( 0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+(?P<corrupted> \|<-CORRUPTED)?')
|
||||
if corrupted:
|
||||
assert match.group('corrupted')
|
||||
|
Loading…
x
Reference in New Issue
Block a user