diff --git a/components/esp_system/include/esp_debug_helpers.h b/components/esp_system/include/esp_debug_helpers.h index c5d92a50708d..eeb7345570cc 100644 --- a/components/esp_system/include/esp_debug_helpers.h +++ b/components/esp_system/include/esp_debug_helpers.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,8 +13,9 @@ extern "C" { #ifndef __ASSEMBLER__ #include -#include "esp_err.h" +#include "sdkconfig.h" #include "soc/soc.h" // [refactor-todo] IDF-2297 +#include "esp_err.h" #include "esp_cpu.h" /* @@ -109,6 +110,20 @@ esp_err_t esp_backtrace_print_from_frame(int depth, const esp_backtrace_frame_t* */ esp_err_t esp_backtrace_print(int depth); +/** + * @brief Print the backtrace of all tasks + * + * @param depth The maximum number of stack frames to print (must be > 0) + * + * @note Users must ensure that no tasks are created or deleted while this function is running. + * @note This function must be called from a task context. + * + * @return + * - ESP_OK All backtraces successfully printed to completion or to depth limit + * - ESP_FAIL One or more backtraces are corrupt + */ +esp_err_t esp_backtrace_print_all_tasks(int depth); + /** * @brief Set a watchpoint to break/panic when a certain memory range is accessed. * Superseded by esp_cpu_set_watchpoint in esp_cpu.h. diff --git a/components/esp_system/port/arch/xtensa/debug_helpers.c b/components/esp_system/port/arch/xtensa/debug_helpers.c index 9cb55d2eff86..f8bf2da81d7b 100644 --- a/components/esp_system/port/arch/xtensa/debug_helpers.c +++ b/components/esp_system/port/arch/xtensa/debug_helpers.c @@ -1,25 +1,33 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include - #include "sdkconfig.h" +#include +#include +#include "soc/soc_memory_layout.h" #include "esp_types.h" #include "esp_attr.h" #include "esp_err.h" +#include "esp_check.h" +#include "esp_ipc.h" #include "esp_debug_helpers.h" -#include "soc/soc_memory_layout.h" #include "esp_cpu_utils.h" #include "esp_private/panic_internal.h" +#include "esp_private/freertos_debug.h" #include "xtensa/xtensa_context.h" #include "sdkconfig.h" #include "esp_rom_sys.h" +#include "freertos/xtensa_context.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +const char *DEBUG_HELPER_TAG = "DBG HLPR"; bool IRAM_ATTR esp_backtrace_get_next_frame(esp_backtrace_frame_t *frame) { @@ -101,3 +109,149 @@ esp_err_t IRAM_ATTR esp_backtrace_print(int depth) esp_backtrace_get_start(&(start.pc), &(start.sp), &(start.next_pc)); return esp_backtrace_print_from_frame(depth, &start, false); } + +typedef struct { +#if !CONFIG_FREERTOS_UNICORE + volatile bool start_tracing; + volatile bool finished_tracing; +#endif // !CONFIG_FREERTOS_UNICORE + struct { + TaskHandle_t task_hdl; + uint32_t starting_pc; + uint32_t starting_sp; + uint32_t next_pc; + } cur_tasks[configNUMBER_OF_CORES]; +} cur_task_backtrace_ctrl_t; + +#if !CONFIG_FREERTOS_UNICORE +static void backtrace_other_cores_ipc_func(void *arg) +{ + cur_task_backtrace_ctrl_t *ctrl = (cur_task_backtrace_ctrl_t *)arg; + + // Suspend the scheduler to prevent task switching + vTaskSuspendAll(); + /* + Initialize backtracing for this core: + + - Flush current core's register windows back onto current task's stack using esp_backtrace_get_start() + - Get starting frame for backtracing (starting frame is the caller of this function) using esp_backtrace_get_start() + - Save the starting frame details into the control block + */ + BaseType_t core_id = xPortGetCoreID(); // Get core ID now that task switching is disabled + ctrl->cur_tasks[core_id].task_hdl = xTaskGetCurrentTaskHandle(); + esp_backtrace_get_start(&ctrl->cur_tasks[core_id].starting_pc, + &ctrl->cur_tasks[core_id].starting_sp, + &ctrl->cur_tasks[core_id].next_pc); + + // Indicate to backtracing core that this core is ready for backtracing + ctrl->start_tracing = true; + // Wait for backtracing core to indicate completion + while (!ctrl->finished_tracing) { + ; + } + // Resume the scheduler to allow task switching again + xTaskResumeAll(); +} +#endif // !CONFIG_FREERTOS_UNICORE + +esp_err_t IRAM_ATTR esp_backtrace_print_all_tasks(int depth) +{ + esp_err_t ret = ESP_OK; + TaskSnapshot_t *task_snapshots; + cur_task_backtrace_ctrl_t ctrl = {0}; + + /* + Allocate array to store task snapshots. Users are responsible for ensuring + tasks don't get created/deleted while backtracing. + */ + const UBaseType_t num_tasks = uxTaskGetNumberOfTasks(); + task_snapshots = calloc(num_tasks, sizeof(TaskSnapshot_t)); + ESP_GOTO_ON_FALSE(task_snapshots, ESP_ERR_NO_MEM, malloc_err, DEBUG_HELPER_TAG, "Task snapshot alloc failed"); + +#if !CONFIG_FREERTOS_UNICORE + // Use IPC call to prepare other core for backtracing + ESP_GOTO_ON_ERROR(esp_ipc_call(!xPortGetCoreID(), backtrace_other_cores_ipc_func, (void *)&ctrl), + ipc_err, + DEBUG_HELPER_TAG, + "IPC call failed"); + // Wait for other core to confirm its ready for backtracing + while (!ctrl.start_tracing) { + ; + } +#endif // !CONFIG_FREERTOS_UNICORE + + // Suspend the scheduler to prevent task switching + vTaskSuspendAll(); + + /* + Initialize backtracing for this core: + + - Flush current core's register windows back onto current task's stack using esp_backtrace_get_start() + - Get starting frame for backtracing (starting frame is the caller of this function) using esp_backtrace_get_start() + - Save the starting frame details into the control block + */ + BaseType_t core_id = xPortGetCoreID(); // Get core ID now that task switching is disabled + ctrl.cur_tasks[core_id].task_hdl = xTaskGetCurrentTaskHandle(); + esp_backtrace_get_start(&ctrl.cur_tasks[core_id].starting_pc, + &ctrl.cur_tasks[core_id].starting_sp, + &ctrl.cur_tasks[core_id].next_pc); + + // Get snapshot of all tasks in the system + const UBaseType_t num_snapshots = MIN(num_tasks, uxTaskGetSnapshotAll(task_snapshots, num_tasks, NULL)); + // Print the backtrace of every task in the system + for (UBaseType_t task_idx = 0; task_idx < num_snapshots; task_idx++) { + bool cur_running = false; + TaskHandle_t task_hdl = (TaskHandle_t) task_snapshots[task_idx].pxTCB; + esp_backtrace_frame_t stk_frame; + + // Check if the task is one of the currently running tasks + for (BaseType_t core_id = 0; core_id < configNUMBER_OF_CORES; core_id++) { + if (task_hdl == ctrl.cur_tasks[core_id].task_hdl) { + cur_running = true; + break; + } + } + // Initialize the starting backtrace frame of the task + if (cur_running) { + /* + Setting the starting backtrace frame for currently running tasks is different. We cannot + use the current frame of each running task as the starting frame (due to the possibility + of the SP changing). Thus, each currently running task will have initialized their callers + as the starting frame for backtracing, which is saved inside the + cur_task_backtrace_ctrl_t block. + */ + stk_frame.pc = ctrl.cur_tasks[core_id].starting_pc; + stk_frame.sp = ctrl.cur_tasks[core_id].starting_sp; + stk_frame.next_pc = ctrl.cur_tasks[core_id].next_pc; + } else { + // Set the starting backtrace frame using the task's saved stack pointer + XtExcFrame* exc_frame = (XtExcFrame*) task_snapshots[task_idx].pxTopOfStack; + stk_frame.pc = exc_frame->pc; + stk_frame.sp = exc_frame->a1; + stk_frame.next_pc = exc_frame->a0; + } + // Print backtrace + char* name = pcTaskGetName(task_hdl); + print_str(name ? name : "No Name" , false); + esp_err_t bt_ret = esp_backtrace_print_from_frame(depth, &stk_frame, false); + if (bt_ret != ESP_OK) { + ret = bt_ret; + } + } + + // Resume the scheduler to allow task switching again + xTaskResumeAll(); +#if !CONFIG_FREERTOS_UNICORE + // Indicate to the other core that backtracing is complete + ctrl.finished_tracing = true; +#endif // !CONFIG_FREERTOS_UNICORE + free(task_snapshots); + return ret; + +#if !CONFIG_FREERTOS_UNICORE +ipc_err: + free(task_snapshots); +#endif // !CONFIG_FREERTOS_UNICORE +malloc_err: + return ret; +} 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 d1309f906e3c..7b2c9d6ac503 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-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,12 +10,14 @@ */ #include #include "unity.h" +#include "test_utils.h" #if __XTENSA__ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/xtensa_api.h" +#include "esp_debug_helpers.h" #include "esp_intr_alloc.h" #include "esp_rom_sys.h" #include "esp_rom_uart.h" @@ -26,6 +28,8 @@ #define RECUR_DEPTH 3 #define ACTION_ABORT -1 #define ACTION_INT_WDT -2 +#define TASK_PRIORITY 5 + // Set to (-1) for abort(), (-2) for interrupt watchdog static int backtrace_trigger_source; @@ -118,4 +122,32 @@ TEST_CASE_MULTIPLE_STAGES("Test backtrace with a ROM function", "[reset_reason][ do_rom_crash, check_reset_reason_panic) + +#define NUM_TEST_FUNCS 2 + +static void backtrace_suspend_func(void *arg) +{ + // Simply suspend and wait to be deleted + vTaskSuspend(NULL); +} + +TEST_CASE("Test esp_backtrace_print_all_tasks()", "[esp_system]") +{ + TaskHandle_t task_handles[NUM_TEST_FUNCS]; + + for (int i = 0; i < NUM_TEST_FUNCS; i++) { + // Create multiple unpinned tasks at higher priorities + xTaskCreate(backtrace_suspend_func, "trace_func", 2048, NULL, UNITY_FREERTOS_PRIORITY + i + 1, &task_handles[i]); + } + // Short delay to allow tasks to suspend + vTaskDelay(10); + // Print backtraces of all tasks + esp_backtrace_print_all_tasks(3); + + // Clean up tasks + for (int i = 0; i < NUM_TEST_FUNCS; i++) { + vTaskDelete(task_handles[i]); + } +} + #endif diff --git a/components/freertos/esp_additions/freertos_tasks_c_additions.h b/components/freertos/esp_additions/freertos_tasks_c_additions.h index 5abbcc5207b7..c2683c7945f4 100644 --- a/components/freertos/esp_additions/freertos_tasks_c_additions.h +++ b/components/freertos/esp_additions/freertos_tasks_c_additions.h @@ -231,7 +231,10 @@ UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, co pxCurTaskList = pxGetNextTaskList(pxCurTaskList); } - *pxTCBSize = sizeof(TCB_t); + if (pxTCBSize != NULL) { + *pxTCBSize = sizeof( TCB_t ); + } + return uxArrayNumFilled; } #endif // CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT diff --git a/components/freertos/esp_additions/include/esp_private/freertos_debug.h b/components/freertos/esp_additions/include/esp_private/freertos_debug.h new file mode 100644 index 000000000000..40ba028d7e5b --- /dev/null +++ b/components/freertos/esp_additions/include/esp_private/freertos_debug.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +/* + * This header contains private API used by various ESP-IDF debugging features (e.g., esp_gdbstub). + */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus + extern "C" { +#endif +/* *INDENT-ON* */ + +/* -------------------------------------------------- Task Snapshot ------------------------------------------------- */ + +/** + * @brief Task Snapshot structure + * + * - Used with the uxTaskGetSnapshotAll() function to save memory snapshot of each task in the system. + * - We need this structure because TCB_t is defined (hidden) in tasks.c. + */ +typedef struct xTASK_SNAPSHOT +{ + void * pxTCB; /*!< Address of the task control block. */ + StackType_t * pxTopOfStack; /*!< Points to the location of the last item placed on the tasks stack. */ + StackType_t * pxEndOfStack; /*!< Points to the end of the stack. pxTopOfStack < pxEndOfStack, stack grows hi2lo + * pxTopOfStack > pxEndOfStack, stack grows lo2hi*/ +} TaskSnapshot_t; + +/** + * @brief Iterate over all tasks in the system + * + * - This function can be used to iterate over every task in the system + * - The first call to this function must set pxTask to NULL + * - When all functions have been iterated, this function will return NULL. + * + * @note This function should only be called when FreeRTOS is no longer running (e.g., during a panic) as this function + * does not acquire any locks. + * @param pxTask Handle of the previous task (or NULL on the first call of this function) + * @return TaskHandle_t Handle of the next task (or NULL when all tasks have been iterated over) + */ +TaskHandle_t pxTaskGetNext( TaskHandle_t pxTask ); + +/** + * @brief Fill a TaskSnapshot_t structure for specified task. + * + * - This function is used by the panic handler to get the snapshot of a particular task. + * + * @note This function should only be called when FreeRTOS is no longer running (e.g., during a panic) as this function + * does not acquire any locks. + * @param[in] pxTask Task's handle + * @param[out] pxTaskSnapshot Snapshot of the task + * @return pdTRUE if operation was successful else pdFALSE + */ +BaseType_t vTaskGetSnapshot( TaskHandle_t pxTask, + TaskSnapshot_t * pxTaskSnapshot ); + +/** + * @brief Fill an array of TaskSnapshot_t structures for every task in the system + * + * - This function is used by the panic handler to get a snapshot of all tasks in the system + * + * @note This function should only be called when FreeRTOS is no longer running (e.g., during a panic) as this function + * does not acquire any locks. + * @param[out] pxTaskSnapshotArray Array of TaskSnapshot_t structures filled by this function + * @param[in] uxArrayLength Length of the provided array + * @param[out] pxTCBSize Size of the a task's TCB structure (can be set to NULL) + * @return UBaseType_t + */ +UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, + const UBaseType_t uxArrayLength, + UBaseType_t * const pxTCBSize ); + +/* ----------------------------------------------------- Misc ----------------------------------------------------- */ + +/** + * @brief Get a void pointer to the current TCB of a particular core + * + * @note This function provides no guarantee that the return TCB will still be the current task (or that the task still + * exists) when it returns. It is the caller's responsibility to ensure that the task does not get scheduled or deleted. + * @param xCoreID The core to query + * @return Void pointer to current TCB + */ +void * pvTaskGetCurrentTCBForCore( BaseType_t xCoreID ); + +/* *INDENT-OFF* */ +#ifdef __cplusplus + } +#endif +/* *INDENT-ON* */ diff --git a/components/freertos/esp_additions/include/freertos/FreeRTOSConfig.h b/components/freertos/esp_additions/include/freertos/FreeRTOSConfig.h index 3a3c10ea2535..6a3f3f1557cb 100644 --- a/components/freertos/esp_additions/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/esp_additions/include/freertos/FreeRTOSConfig.h @@ -278,6 +278,8 @@ #endif /* CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER */ #endif /* !CONFIG_FREERTOS_SMP */ +#define configNUMBER_OF_CORES configNUM_CORES + /* ------------------------------------------------ ESP-IDF Additions -------------------------------------------------- * - Any other macros required by the rest of ESP-IDF * ------------------------------------------------------------------------------------------------------------------ */