mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
3686689a2a
This commit adds esp_backtrace_print_all_tasks() which prints the backtraces of all tasks at runtime. Closes https://github.com/espressif/esp-idf/issues/9708 CLoses https://github.com/espressif/esp-idf/pull/11575 [Omar Chebib: Prevent task switching while printing backtraces of tasks.] [Omar Chebib: Ensure all task stacks are flushed from register to RAM.] [Omar Chebib: Removed esp_task_snapshot_to_backtrace_frame() as task snapshot is private API.] [Omar Chebib: Added test case for esp_backtrace_print_all_tasks().] Signed-off-by: Omar Chebib <omar.chebib@espressif.com>
253 lines
9.3 KiB
C
253 lines
9.3 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "sdkconfig.h"
|
|
#include <string.h>
|
|
#include <sys/param.h>
|
|
#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 "esp_cpu_utils.h"
|
|
#include "esp_private/panic_internal.h"
|
|
#include "esp_private/freertos_debug.h"
|
|
#include "esp_rom_sys.h"
|
|
#include "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)
|
|
{
|
|
//Use frame(i-1)'s BS area located below frame(i)'s sp to get frame(i-1)'s sp and frame(i-2)'s pc
|
|
void *base_save = (void *)frame->sp; //Base save area consists of 4 words under SP
|
|
frame->pc = frame->next_pc;
|
|
frame->next_pc = *((uint32_t *)(base_save - 16)); //If next_pc = 0, indicates frame(i-1) is the last frame on the stack
|
|
frame->sp = *((uint32_t *)(base_save - 12));
|
|
|
|
//Return true if both sp and pc of frame(i-1) are sane, false otherwise
|
|
return (esp_stack_ptr_is_sane(frame->sp) && esp_ptr_executable((void*)esp_cpu_process_stack_pc(frame->pc)));
|
|
}
|
|
|
|
static void IRAM_ATTR print_entry(uint32_t pc, uint32_t sp, bool panic)
|
|
{
|
|
if (panic) {
|
|
panic_print_str(" 0x");
|
|
panic_print_hex(pc);
|
|
panic_print_str(":0x");
|
|
panic_print_hex(sp);
|
|
} else {
|
|
esp_rom_printf(" 0x%08X:0x%08X", pc, sp);
|
|
}
|
|
}
|
|
|
|
static void IRAM_ATTR print_str(const char* str, bool panic)
|
|
{
|
|
if (panic) {
|
|
panic_print_str(str);
|
|
} else {
|
|
esp_rom_printf(str);
|
|
}
|
|
}
|
|
|
|
esp_err_t IRAM_ATTR esp_backtrace_print_from_frame(int depth, const esp_backtrace_frame_t* frame, bool panic)
|
|
{
|
|
//Check arguments
|
|
if (depth <= 0) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
//Initialize stk_frame with first frame of stack
|
|
esp_backtrace_frame_t stk_frame = { 0 };
|
|
memcpy(&stk_frame, frame, sizeof(esp_backtrace_frame_t));
|
|
|
|
print_str("\r\n\r\nBacktrace:", panic);
|
|
print_entry(esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp, panic);
|
|
|
|
//Check if first frame is valid
|
|
bool corrupted = !(esp_stack_ptr_is_sane(stk_frame.sp) &&
|
|
(esp_ptr_executable((void *)esp_cpu_process_stack_pc(stk_frame.pc)) ||
|
|
/* Ignore the first corrupted PC in case of InstrFetchProhibited */
|
|
(stk_frame.exc_frame && ((XtExcFrame *)stk_frame.exc_frame)->exccause == EXCCAUSE_INSTR_PROHIBITED)));
|
|
|
|
uint32_t i = (depth <= 0) ? INT32_MAX : depth;
|
|
while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) {
|
|
if (!esp_backtrace_get_next_frame(&stk_frame)) { //Get previous stack frame
|
|
corrupted = true;
|
|
}
|
|
print_entry(esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp, panic);
|
|
}
|
|
|
|
//Print backtrace termination marker
|
|
esp_err_t ret = ESP_OK;
|
|
if (corrupted) {
|
|
print_str(" |<-CORRUPTED", panic);
|
|
ret = ESP_FAIL;
|
|
} else if (stk_frame.next_pc != 0) { //Backtrace continues
|
|
print_str(" |<-CONTINUES", panic);
|
|
}
|
|
print_str("\r\n\r\n", panic);
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t IRAM_ATTR esp_backtrace_print(int depth)
|
|
{
|
|
//Initialize stk_frame with first frame of stack
|
|
esp_backtrace_frame_t start = { 0 };
|
|
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;
|
|
}
|