在 STM32 中打印 crash dump 的函数调用关系需要结合硬件异常处理和调试技术。以下是详细方法:
1. 硬件异常处理基础
异常处理函数
// 在启动文件中重定义默认异常处理
void HardFault_Handler(void)
{
__asm volatile(
"tst lr, #4 \n"
"ite eq \n"
"mrseq r0, msp \n"
"mrsne r0, psp \n"
"b print_call_stack \n"
);
}
2. 调用栈回溯实现
基于帧指针的回溯
#include <stdint.h>
// 栈帧结构
typedef struct stack_frame {
struct stack_frame* fp; // 帧指针
uint32_t lr; // 返回地址
} stack_frame_t;
// 打印调用栈
void print_call_stack(uint32_t* sp)
{
printf("=== Call Stack ===\n");
stack_frame_t* current_frame = (stack_frame_t*)sp;
uint32_t depth = 0;
// 检查栈帧是否在有效范围内
uint32_t stack_base = (uint32_t)&__initial_sp; // 从启动文件获取
uint32_t stack_limit = stack_base - __STACK_SIZE;
while (current_frame != NULL &&
(uint32_t)current_frame >= stack_limit &&
(uint32_t)current_frame < stack_base &&
depth < 32) {
uint32_t return_addr = current_frame->lr;
// 调整返回地址(Thumb模式)
if (return_addr & 1) {
return_addr--; // Thumb模式清除最低位
}
printf("#%d 0x%08lx\n", depth, return_addr);
// 解析函数名(如果有符号表)
const char* func_name = addr2func(return_addr);
if (func_name) {
printf(" %s\n", func_name);
}
current_frame = current_frame->fp;
depth++;
}
printf("==================\n");
}
3. 完整的异常处理
增强的 HardFault 处理
void HardFault_Handler_Enhanced(void)
{
// 保存寄存器状态
uint32_t stacked_r0, stacked_r1, stacked_r2, stacked_r3;
uint32_t stacked_r12, stacked_lr, stacked_pc, stacked_psr;
__asm volatile(
"tst lr, #4 \n"
"ite eq \n"
"mrseq r0, msp \n"
"mrsne r0, psp \n"
"mov %0, r0 \n"
: "=r" (sp)
);
// 从栈中提取寄存器值
stacked_r0 = ((uint32_t*)sp)[0];
stacked_r1 = ((uint32_t*)sp)[1];
stacked_r2 = ((uint32_t*)sp)[2];
stacked_r3 = ((uint32_t*)sp)[3];
stacked_r12 = ((uint32_t*)sp)[4];
stacked_lr = ((uint32_t*)sp)[5];
stacked_pc = ((uint32_t*)sp)[6];
stacked_psr = ((uint32_t*)sp)[7];
// 打印异常信息
printf("\n!!! HardFault !!!\n");
printf("SP: 0x%08lX\n", sp);
printf("PC: 0x%08lX\n", stacked_pc);
printf("LR: 0x%08lX\n", stacked_lr);
printf("PSR: 0x%08lX\n", stacked_psr);
// 分析故障原因
analyze_fault(stacked_psr);
// 打印调用栈
print_call_stack((uint32_t*)sp);
// 停止执行
while(1) {
// 可以添加LED闪烁指示错误
}
}
4. 符号表解析
简单的符号表实现
typedef struct {
uint32_t address;
const char* name;
} symbol_t;
// 从map文件生成的符号表(需要预处理)
symbol_t symbol_table[] = {
{0x08001234, "main"},
{0x08001280, "function_a"},
{0x08001300, "function_b"},
{0, NULL} // 结束标记
};
const char* addr2func(uint32_t addr)
{
for (int i = 0; symbol_table[i].name != NULL; i++) {
if (addr >= symbol_table[i].address &&
addr < symbol_table[i+1].address) {
return symbol_table[i].name;
}
}
return "unknown";
}
5. 使用 GNU Binutils 解析
自动生成符号表脚本
#!/bin/bash
# generate_symbols.sh
arm-none-eabi-nm -n $1.elf | grep " T " | awk '{print " {"$1", \""$3"\"},"}' > symbols.c
在 Makefile 中集成
symbols.c: $(TARGET).elf
./generate_symbols.sh $(TARGET)
$(TARGET).elf: symbols.c
# 编译规则
6. 使用 SEGGER SystemView 或 RTT
通过 J-Link RTT 输出调用栈
#include "SEGGER_RTT.h"
void print_call_stack_rtt(uint32_t* sp)
{
SEGGER_RTT_printf(0, "=== Call Stack ===\n");
stack_frame_t* current_frame = (stack_frame_t*)sp;
uint32_t depth = 0;
while (current_frame != NULL && depth < 16) {
uint32_t return_addr = current_frame->lr & ~1UL; // Thumb模式
SEGGER_RTT_printf(0, "#%d 0x%08lX", depth, return_addr);
const char* func_name = addr2func(return_addr);
if (func_name) {
SEGGER_RTT_printf(0, " %s\n", func_name);
} else {
SEGGER_RTT_printf(0, "\n");
}
current_frame = current_frame->fp;
depth++;
}
}
7. 故障原因分析
void analyze_fault(uint32_t psr)
{
uint32_t cfsr = SCB->CFSR; // Configurable Fault Status Register
uint32_t hfsr = SCB->HFSR; // Hard Fault Status Register
uint32_t mmfar = SCB->MMFAR; // MemManage Fault Address
uint32_t bfar = SCB->BFAR; // Bus Fault Address
printf("CFSR: 0x%08lX\n", cfsr);
if (cfsr & (1 << 0)) printf(" - IACCVIOL: Instruction access violation\n");
if (cfsr & (1 << 1)) printf(" - DACCVIOL: Data access violation\n");
if (cfsr & (1 << 3)) printf(" - MUNSTKERR: MemManage on exception entry\n");
if (cfsr & (1 << 4)) printf(" - MSTKERR: MemManage on exception return\n");
// ... 更多故障标志
}
8. 调试配置
在 IDE 中设置
// 在 main() 开始时启用
void enable_fault_handlers(void)
{
// 启用所有故障异常
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk |
SCB_SHCSR_BUSFAULTENA_Msk |
SCB_SHCSR_USGFAULTENA_Msk;
}
9. 实际应用示例
int main(void)
{
// 初始化调试输出
debug_uart_init();
// 启用故障处理
enable_fault_handlers();
printf("System started\n");
// 你的应用代码
while(1) {
application_code();
}
}
注意事项
-
编译器优化 : 确保帧指针不被优化掉(
-fno-omit-frame-pointer) -
栈保护: 检查栈指针有效性,避免二次崩溃
-
内存布局: 了解你的内存映射,避免访问非法地址
-
实时性: 异常处理应尽量简洁,避免影响实时性要求高的应用
这种方法可以在 STM32 发生崩溃时提供详细的调用栈信息,大大简化调试过程。