在 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 发生崩溃时提供详细的调用栈信息,大大简化调试过程。