HardFault 是 ARM Cortex-M 处理器中的一种异常。当处理器遇到无法处理的错误,或者配置为处理特定类型错误(如总线错误、内存管理错误、用法错误)的异常处理程序被禁用,或者在处理这些特定错误的过程中又发生了其他错误时,就会触发 HardFault。它是一个“兜底”的异常,表明系统遇到了严重问题。
调试 HardFault 需要耐心和系统的方法。关键在于:
发生 HardFault 时,处理器会自动将一些关键的寄存器压入当前使用的堆栈(MSP 或 PSP),并跳转到 HardFault 处理程序。
我们的首要任务就是编写一个有效的 HardFault 处理程序,从中提取有用的信息。
1
步骤 1: 实现一个有效的 HardFault 处理程序
默认的 HardFault_Handler 通常是一个无限循环 while(1);。我们需要替换它,使其能够捕获并报告故障信息。
在你的项目中(通常在 stm32xxxx_it.c 或类似文件中)找到 HardFault_Handler 函数,并用以下代码替换或修改:
// 定义一个结构体来存储从堆栈中提取的寄存器值
typedefstruct {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr; // Link Register
uint32_t pc; // Program Counter
uint32_t psr;// Program Status Register
} HardFaultRegs_t;
// 全局变量,用于在调试器中查看
volatile HardFaultRegs_t stacked_regs;
volatileuint32_t cfsr_val;
volatileuint32_t hfsr_val;
volatileuint32_t dfsr_val;
volatileuint32_t afsr_val;
volatileuint32_t mmfar_val;
volatileuint32_t bfar_val;
volatileuint32_t stacked_sp; // 保存堆栈指针本身的值
// HardFault 处理函数
// 使用 __attribute__((naked)) 避免编译器生成额外的栈操作代码
voidHardFault_Handler(void) __attribute__((naked));
voidHardFault_Handler(void)
{
// 获取当前使用的堆栈指针 (MSP 或 PSP)
// TST LR, #4 测试 LR 的 bit 2 (EXC_RETURN 的 bit 2)
// 如果 bit 2 为 1,表示异常返回时使用 PSP;否则使用 MSP
__asm volatile(
" TST LR, #4\n" // Test bit 2 of LR: 0 = MSP, 1 = PSP
" ITE EQ\n" // If-Then-Else based on EQ flag (result of TST)
" MRSEQ R0, MSP\n" // EQ=1 (bit 2 is 0): Use MSP, move MSP to R0
" MRSNE R0, PSP\n" // NE=0 (bit 2 is 1): Use PSP, move PSP to R0
" MOV %0, R0\n" // Move the selected stack pointer to the C variable 'stacked_sp'
: "=r" (stacked_sp) // Output operand: stacked_sp C variable
: // Input operands: none
: "r0" // Clobbered registers: R0 is used internally
);
// 从获取的堆栈指针处加载寄存器值到结构体
// stacked_sp 现在指向 R0 的位置
stacked_regs.r0 = *((volatileuint32_t*)(stacked_sp + 0));
stacked_regs.r1 = *((volatileuint32_t*)(stacked_sp + 4));
stacked_regs.r2 = *((volatileuint32_t*)(stacked_sp + 8));
stacked_regs.r3 = *((volatileuint32_t*)(stacked_sp + 12));
stacked_regs.r12= *((volatileuint32_t*)(stacked_sp + 16));
stacked_regs.lr = *((volatileuint32_t*)(stacked_sp + 20));
stacked_regs.pc = *((volatileuint32_t*)(stacked_sp + 24));
stacked_regs.psr= *((volatileuint32_t*)(stacked_sp + 28));
// 读取故障状态寄存器
cfsr_val = (*((volatileuint32_t*)0xE000ED28));
hfsr_val = (*((volatileuint32_t*)0xE000ED2C)); // 注意:HFSR 地址是 0xE000ED2C
dfsr_val = (*((volatileuint32_t*)0xE000ED30));
afsr_val = (*((volatileuint32_t*)0xE000ED3C));
// 检查 MMFAR 和 BFAR 是否有效并读取
if (cfsr_val & (1 << 7)) { // MMARVALID bit in MMFSR
mmfar_val = (*((volatileuint32_t*)0xE000ED34));
} else {
mmfar_val = 0xFFFFFFFF; // 无效
}
if (cfsr_val & (1 << 15)) { // BFARVALID bit in BFSR
bfar_val = (*((volatileuint32_t*)0xE000ED38));
} else {
bfar_val = 0xFFFFFFFF; // 无效
}
// 在这里可以添加代码将这些变量的值通过串口、SWO 或其他方式打印出来
// printf("HardFault!\n");
// printf("SP = 0x%08X\n", stacked_sp);
// printf("R0 = 0x%08X\n", stacked_regs.r0);
// printf("R1 = 0x%08X\n", stacked_regs.r1);
// ... (打印其他寄存器)
// printf("PC = 0x%08X\n", stacked_regs.pc); // 出错指令的下一条地址
// printf("LR = 0x%08X\n", stacked_regs.lr);
// printf("PSR= 0x%08X\n", stacked_regs.psr);
// printf("CFSR=0x%08X\n", cfsr_val);
// printf("HFSR=0x%08X\n", hfsr_val);
// printf("MMFAR=0x%08X\n", mmfar_val);
// printf("BFAR=0x%08X\n", bfar_val);
// 设置一个断点在这里,或者进入无限循环等待调试器连接
__asm volatile("BKPT #0\n"); // Software breakpoint
// 或者
// while(1);
}
注意:
2
步骤 2: 复现 HardFault 并使用调试器分析
编译并下载包含上述 HardFault_Handler 的代码到目标板。
连接调试器(如 ST-Link, J-Link)。
运行代码直到 HardFault 发生。如果设置了 BKPT #0,程序会自动停在断点处。如果没有设置断点,并且处理函数最后是 while(1);,则在 HardFault 发生后手动暂停程序,程序计数器应该停在 while(1); 循环内。
检查变量值在调试器的 Watch 窗口或 Memory 窗口中查看 stacked_regs, cfsr_val, hfsr_val, mmfar_val, bfar_val 等变量的值。
3
步骤 3: 解读故障信息
分析 CFSR
分析 HFSR
分析 MMFAR 和 BFAR
如果 MMARVALID 或 BFARVALID 置位,这两个寄存器会告诉你导致内存或总线错误的确切地址。检查这个地址是否在你预期的内存范围内,是否需要特殊访问权限(如 MPU 设置),或者是否指向了一个无效的外设地址。
分析堆栈帧中的 PC 和 LR
4
步骤 4: 定位并修复源代码
根据反汇编窗口中定位到的指令地址,结合 .map 文件或调试器的符号信息,找到对应的 C 源代码行。
分析原因:
根据分析出的原因修改代码,重新编译、下载并运行代码,确保HardFault 不再发生。