diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e21ee607cca..190d4c5ffb02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/components/app_trace/CMakeLists.txt b/components/app_trace/CMakeLists.txt index 353b3dc14cce..11dc85b869db 100644 --- a/components/app_trace/CMakeLists.txt +++ b/components/app_trace/CMakeLists.txt @@ -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}" diff --git a/components/app_trace/heap_trace_tohost.c b/components/app_trace/heap_trace_tohost.c index 7a52294f5c0a..3175932a7172 100644 --- a/components/app_trace/heap_trace_tohost.c +++ b/components/app_trace/heap_trace_tohost.c @@ -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 +#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. diff --git a/components/esp_system/CMakeLists.txt b/components/esp_system/CMakeLists.txt index b99131ad99ab..af54c5d2a60c 100644 --- a/components/esp_system/CMakeLists.txt +++ b/components/esp_system/CMakeLists.txt @@ -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() diff --git a/components/esp_system/Kconfig b/components/esp_system/Kconfig index 282eade65fcb..5f3893e3eb0c 100644 --- a/components/esp_system/Kconfig +++ b/components/esp_system/Kconfig @@ -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" diff --git a/components/esp_system/fp_unwind.c b/components/esp_system/fp_unwind.c new file mode 100644 index 000000000000..c105843af68b --- /dev/null +++ b/components/esp_system/fp_unwind.c @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include +#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"); +} diff --git a/components/esp_system/include/esp_private/fp_unwind.h b/components/esp_system/include/esp_private/fp_unwind.h new file mode 100644 index 000000000000..2444609ec4f7 --- /dev/null +++ b/components/esp_system/include/esp_private/fp_unwind.h @@ -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 diff --git a/components/esp_system/port/arch/riscv/debug_helpers.c b/components/esp_system/port/arch/riscv/debug_helpers.c index 6b27451306ce..c33381c31f1f 100644 --- a/components/esp_system/port/arch/riscv/debug_helpers.c +++ b/components/esp_system/port/arch/riscv/debug_helpers.c @@ -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; diff --git a/components/esp_system/port/arch/riscv/panic_arch.c b/components/esp_system/port/arch/riscv/panic_arch.c index 1f4dd57b5405..556e6265960c 100644 --- a/components/esp_system/port/arch/riscv/panic_arch.c +++ b/components/esp_system/port/arch/riscv/panic_arch.c @@ -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 diff --git a/components/esp_system/task_wdt/task_wdt.c b/components/esp_system/task_wdt/task_wdt.c index 0541164a1441..4b374c467d32 100644 --- a/components/esp_system/task_wdt/task_wdt.c +++ b/components/esp_system/task_wdt/task_wdt.c @@ -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 diff --git a/components/esp_system/test_apps/esp_system_unity_tests/main/test_backtrace.c b/components/esp_system/test_apps/esp_system_unity_tests/main/test_backtrace.c index 6ee588ad0095..2d398edb0794 100644 --- a/components/esp_system/test_apps/esp_system_unity_tests/main/test_backtrace.c +++ b/components/esp_system/test_apps/esp_system_unity_tests/main/test_backtrace.c @@ -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 #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 diff --git a/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py b/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py index 03c3ba980b50..f0fc1bb0a48e 100644 --- a/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py +++ b/components/esp_system/test_apps/esp_system_unity_tests/pytest_esp_system_unity_tests.py @@ -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...') diff --git a/components/esp_system/test_apps/esp_system_unity_tests/sdkconfig.ci.framepointer b/components/esp_system/test_apps/esp_system_unity_tests/sdkconfig.ci.framepointer new file mode 100644 index 000000000000..464cba0ee4a2 --- /dev/null +++ b/components/esp_system/test_apps/esp_system_unity_tests/sdkconfig.ci.framepointer @@ -0,0 +1 @@ +CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y diff --git a/components/heap/Kconfig b/components/heap/Kconfig index 6bcc7ab7d168..d40b64fcee72 100644 --- a/components/heap/Kconfig +++ b/components/heap/Kconfig @@ -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 diff --git a/components/heap/heap_trace_standalone.c b/components/heap/heap_trace_standalone.c index 00c66162a164..01e7607bfce9 100644 --- a/components/heap/heap_trace_standalone.c +++ b/components/heap/heap_trace_standalone.c @@ -4,13 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ #include -#include +#include "sdkconfig.h" #include #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 diff --git a/components/heap/include/esp_heap_trace.h b/components/heap/include/esp_heap_trace.h index d6a44310741f..d6430a1fb2fc 100644 --- a/components/heap/include/esp_heap_trace.h +++ b/components/heap/include/esp_heap_trace.h @@ -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 diff --git a/components/heap/include/heap_trace.inc b/components/heap/include/heap_trace.inc index 6ac08a034e54..d409ee832993 100644 --- a/components/heap/include/heap_trace.inc +++ b/components/heap/include/heap_trace.inc @@ -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 -#include +#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 { diff --git a/components/heap/test_apps/heap_tests/pytest_heap.py b/components/heap/test_apps/heap_tests/pytest_heap.py index 6f0c710bc23e..e36de539b1f0 100644 --- a/components/heap/test_apps/heap_tests/pytest_heap.py +++ b/components/heap/test_apps/heap_tests/pytest_heap.py @@ -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: diff --git a/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace b/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_esp32 similarity index 100% rename from components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace rename to components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_esp32 diff --git a/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_esp32c3 b/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_esp32c3 new file mode 100644 index 000000000000..bdaf483919e6 --- /dev/null +++ b/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_esp32c3 @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32c3" +CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y +CONFIG_HEAP_TRACING_STANDALONE=y diff --git a/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap b/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap_esp32 similarity index 100% rename from components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap rename to components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap_esp32 diff --git a/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap_esp32c3 b/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap_esp32c3 new file mode 100644 index 000000000000..a1a6476d86cc --- /dev/null +++ b/components/heap/test_apps/heap_tests/sdkconfig.ci.heap_trace_hashmap_esp32c3 @@ -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 diff --git a/docs/en/api-guides/fatal-errors.rst b/docs/en/api-guides/fatal-errors.rst index 49228163a866..b9920108fa2c 100644 --- a/docs/en/api-guides/fatal-errors.rst +++ b/docs/en/api-guides/fatal-errors.rst @@ -234,7 +234,7 @@ If :doc:`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 `. 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 `. 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 ` 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 `). 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 `). 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. diff --git a/docs/en/api-reference/system/heap_debug.rst b/docs/en/api-reference/system/heap_debug.rst index 472c9d3ccf7c..2f9edf4b7acb 100644 --- a/docs/en/api-reference/system/heap_debug.rst +++ b/docs/en/api-reference/system/heap_debug.rst @@ -269,99 +269,99 @@ 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 +.. only:: CONFIG_IDF_TARGET_ARCH_RISCV .. 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 + 3 bytes (@ 0x3fcb26f8, Internal) allocated CPU 0 ccount 0x1e7af728 freed + 6 bytes (@ 0x3fcb4ff0, Internal) allocated CPU 0 ccount 0x1e7afc38 freed + 9 bytes (@ 0x3fcb5000, Internal) allocated CPU 0 ccount 0x1e7b01d4 freed + 12 bytes (@ 0x3fcb5010, Internal) allocated CPU 0 ccount 0x1e7b0778 freed + 15 bytes (@ 0x3fcb5020, Internal) allocated CPU 0 ccount 0x1e7b0d18 freed + 18 bytes (@ 0x3fcb5034, Internal) allocated CPU 0 ccount 0x1e7b12b8 freed + 21 bytes (@ 0x3fcb504c, Internal) allocated CPU 0 ccount 0x1e7b1858 freed + 24 bytes (@ 0x3fcb5068, Internal) allocated CPU 0 ccount 0x1e7b1dfc freed + ====== Heap Trace Summary ====== + Mode: Heap Trace All + 0 bytes alive in trace (0/8 allocations) + records: 8 (8 capacity, 8 high water mark) + total allocations: 8 + total frees: 8 + ================================ - 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) + Or the following example, when the ``CONFIG_ESP_SYSTEM_USE_FRAME_POINTER`` option is enabled and the stack depth is configured properly: - 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 +.. code-block:: none - 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) + ====== 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 - 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) - 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 - 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) - 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 - 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) - 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 - 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) - 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 - 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) - 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 - 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 + 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) - ====== 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 - ================================ + 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 -.. only:: CONFIG_IDF_TARGET_ARCH_RISCV + 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) - .. code-block:: none + 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: 8 records (8 capacity) ====== - 3 bytes (@ 0x3fcb26f8, Internal) allocated CPU 0 ccount 0x1e7af728 freed - 6 bytes (@ 0x3fcb4ff0, Internal) allocated CPU 0 ccount 0x1e7afc38 freed - 9 bytes (@ 0x3fcb5000, Internal) allocated CPU 0 ccount 0x1e7b01d4 freed - 12 bytes (@ 0x3fcb5010, Internal) allocated CPU 0 ccount 0x1e7b0778 freed - 15 bytes (@ 0x3fcb5020, Internal) allocated CPU 0 ccount 0x1e7b0d18 freed - 18 bytes (@ 0x3fcb5034, Internal) allocated CPU 0 ccount 0x1e7b12b8 freed - 21 bytes (@ 0x3fcb504c, Internal) allocated CPU 0 ccount 0x1e7b1858 freed - 24 bytes (@ 0x3fcb5068, Internal) allocated CPU 0 ccount 0x1e7b1dfc freed - ====== Heap Trace Summary ====== - Mode: Heap Trace All - 0 bytes alive in trace (0/8 allocations) - records: 8 (8 capacity, 8 high water mark) - total allocations: 8 - total frees: 8 - ================================ + ====== 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:: @@ -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 diff --git a/docs/zh_CN/api-guides/fatal-errors.rst b/docs/zh_CN/api-guides/fatal-errors.rst index 7cab6e65dfb9..2ba0b7fbc9b4 100644 --- a/docs/zh_CN/api-guides/fatal-errors.rst +++ b/docs/zh_CN/api-guides/fatal-errors.rst @@ -234,7 +234,7 @@ #5 0x00000000 in ?? () Backtrace stopped: frame did not save the PC - 虽然以上的回溯信息非常方便,但要求用户使用 :doc:`IDF 监视器 `。因此,如果用户希望使用其它的串口监控软件也能显示堆栈回溯信息,则需要在 menuconfig 中启用 :ref:`CONFIG_ESP_SYSTEM_USE_EH_FRAME` 选项。 + 虽然以上的回溯信息非常方便,但要求用户使用 :doc:`IDF 监视器 `。因此,如果用户希望使用其它的串口监控软件也能显示堆栈回溯信息,则需要在 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" 的后面几行,发生严重错误的代码显示在顶行,后续几行显示的是调用堆栈。 diff --git a/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c b/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c index f07b6c821b5a..87b0031d0770 100644 --- a/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c +++ b/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c @@ -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(); } diff --git a/examples/system/sysview_tracing_heap_log/sdkconfig.defaults b/examples/system/sysview_tracing_heap_log/sdkconfig.defaults index 140b5dbac7c4..0d2f661c0ee2 100644 --- a/examples/system/sysview_tracing_heap_log/sdkconfig.defaults +++ b/examples/system/sysview_tracing_heap_log/sdkconfig.defaults @@ -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 diff --git a/tools/test_apps/system/panic/main/include/test_panic.h b/tools/test_apps/system/panic/main/include/test_panic.h index e4cb6de8a0b5..9350857e3621 100644 --- a/tools/test_apps/system/panic/main/include/test_panic.h +++ b/tools/test_apps/system/panic/main/include/test_panic.h @@ -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 diff --git a/tools/test_apps/system/panic/main/test_app_main.c b/tools/test_apps/system/panic/main/test_app_main.c index a51d22f36005..7b63a3440f6b 100644 --- a/tools/test_apps/system/panic/main/test_app_main.c +++ b/tools/test_apps/system/panic/main/test_app_main.c @@ -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); diff --git a/tools/test_apps/system/panic/main/test_panic.c b/tools/test_apps/system/panic/main/test_panic.c index 0c9c0d0f603b..260aeca3241b 100644 --- a/tools/test_apps/system/panic/main/test_panic.c +++ b/tools/test_apps/system/panic/main/test_panic.c @@ -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 diff --git a/tools/test_apps/system/panic/pytest_panic.py b/tools/test_apps/system/panic/pytest_panic.py index 9937d968def3..418bce471db1 100644 --- a/tools/test_apps/system/panic/pytest_panic.py +++ b/tools/test_apps/system/panic/pytest_panic.py @@ -59,6 +59,11 @@ 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] + ) diff --git a/tools/test_apps/system/panic/test_panic_util/panic_dut.py b/tools/test_apps/system/panic/test_panic_util/panic_dut.py index ff8004cb8501..6e488b83669c 100644 --- a/tools/test_apps/system/panic/test_panic_util/panic_dut.py +++ b/tools/test_apps/system/panic/test_panic_util/panic_dut.py @@ -81,7 +81,6 @@ def expect_none(self, pattern, **kwargs) -> None: # type: ignore 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)?') if corrupted: assert match.group('corrupted')