mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-11-04 14:14:11 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			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%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;
 | 
						|
}
 |