一般来说接触ESP32的backtrace功能基本都是在固件跑崩的情况下.ESP-IDF自己打印崩溃瞬间的调用堆栈以及寄存器值来分析问题用的.操作方法是先在cmd中进入到工程编译出的.elf的文件目录,然后再使用addr2line命令,将backtrace打印出的地址转换成对应代码所在的文件与行位置.例如这样:
addr2line -e uart_echo.elf -a -f 0x40240894:0x3fffd5800 x40240760:0x3fffd5c0 0x4023ec61:0x3fffd6a0 0x4023f415:0x3fffd6e0 0x400d1d04:0x3fffd8c0 0x4009197d:0x3fffd8e0
但是有时候,固件在正常运行,但是就有查看某一时刻的调用堆栈的需求,这个时候就需要手动让他打印backtrace来给我们分析调用堆栈.对应的函数是esp-idf\components\xtensa\debug_helpers.c里的esp_backtrace_print().
函数的解释是这样的:
/**
* @brief Print the backtrace of the current stack
*
* @param depth The maximum number of stack frames to print (should be > 0)
*
* @return
* - ESP_OK Backtrace successfully printed to completion or to depth limit
* - ESP_FAIL Backtrace is corrupted
*/
esp_err_t esp_backtrace_print(int depth);
在需要查看调用堆栈的地方调用这个函数,例如这样:
在运行到这个地方时,就会打印出当时的调用堆栈,串口log上是这样的.
然后正常用xtensa-esp32-elf-addr2line解析就能看到调用堆栈了.
另外这个功能还有个用途,我们只要把esp_backtrace_print()函数稍微改改,就能让他打印之前某个时间点的调用堆栈.具体应用场景是这样的:
比如说我有两个task需要操作同一个资源,那我肯定得加互斥锁,但是有时候写了bug,可能会导致一个task获取了互斥锁,但是没有释放,导致第二个task没法获取互斥锁,就卡死在那里,这个时候,你可能可以通过log或者崩溃或者其他啥方法定位到第二个没法获取互斥锁的问题,但是不一定能定位到互斥锁到底是在哪里被获取但是没释放的(这里的第一个task).
这里我习惯把获取互斥锁跟释放互斥锁的操作单独包装成两个独立的函数,而不是直接在task里使用FreeRTOS的API,这个时候就很简单了.我可以在每次调用那个单独包装的获取互斥锁函数的时候在这个函数里记录当前的调用堆栈(也就是记录到底是谁调用了这个获取互斥锁函数).
然后再做个逻辑,如果在获取互斥锁函数里获取互斥锁失败了,那就把上一次调用获取互斥锁保存的调用堆栈打印出来,这样就知道上次调用获取互斥锁函数的调用堆栈了.
所以我再加了两个函数用来记录与打印:
uint8_t last_backtrace_data_depth = 0;
uint32_t last_backtrace_data[32][2];
void esp_backtrace_last_backtrace_data_print(void)
{
ets_printf("Last Time Backtrace:");
for (uint8_t i = 0; i < last_backtrace_data_depth; i++)
{
ets_printf("0x%08X:0x%08X ", last_backtrace_data[i][0], last_backtrace_data[i][1]);
}
ets_printf("\r\n\r\n");
}
esp_err_t esp_backtrace_save(int depth)
{
//Check arguments
if (depth <= 0 || depth > 31 ) {
return ESP_ERR_INVALID_ARG;
}
//Initialize stk_frame with first frame of stack
esp_backtrace_frame_t stk_frame;
esp_backtrace_get_start(&(stk_frame.pc), &(stk_frame.sp), &(stk_frame.next_pc));
//esp_cpu_get_backtrace_start(&stk_frame);
last_backtrace_data[0][0] = esp_cpu_process_stack_pc(stk_frame.pc);
last_backtrace_data[0][1] = stk_frame.sp;
//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))) ?
false : true;
last_backtrace_data_depth = 1;
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;
}
last_backtrace_data[last_backtrace_data_depth][0] = esp_cpu_process_stack_pc(stk_frame.pc);
last_backtrace_data[last_backtrace_data_depth][1] = stk_frame.sp;
last_backtrace_data_depth++;
}
return ESP_OK;
}
这样在需要保存调用堆栈的地方调用esp_backtrace_save()保存,然后在需要打印保存的调用堆栈的地方调用esp_backtrace_last_backtrace_data_print()来打印就行了.
Comments | NOTHING