esp-idf/components/esp_system/port/arch/xtensa/debug_helpers.c
2024-03-12 11:15:53 +02:00

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%08" PRIX32 ":0x%08" PRIX32, 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;
}