程序死机:停在HardFault_Handler的while(1)死循环中
问题描述
- 程序死机,停在停在
HardFault_Handler的while(1)死循环中 - 调试日志疯狂报错:
Cannot access Memory (@ 0x0000457e, Read...)。 - 芯片内核寄存器
R14(LR)显示为0xFFFFFFFD,说明异常发生在任何(线程)模式下,使用的是PSP。
原因分析:
走过的弯路
- 手动查堆栈失败:根据
PSP去Memory窗口看压栈的PC和LR,结果找出来的数据全是乱码(0x20001A6A...)。程序发生异常时,栈已经被踩坏,也可能是底层汇编的压栈动作被中断打断。 BFAR误导:故障报告里的BFAR显示0xE000EDF8,这是内核调试寄存器地址。
问题定位
- Call Stack(调用栈):通过Keil自带的Call Stack窗口,直接定位到了出事前的函数调用链:
network_entry->air_work_rst_dev。 - 地址关联:发生崩溃时报错的
0x457e和air_work_rst_dev函数的地址0x4543极其接近。 - Watch(观察)窗口:在嫌疑函数
air_work_rst_dev内部打断点,展开相关的全局结构体指针netdev_opt。 - 一眼定乾坤:在Watch窗口里看到
clear_rxbuf的值是0x00000000
根本原因
- 在初始化
do_netdev_opt结构体时,漏掉了对clear_rxbuf函数指针的赋值。 - 当
air_work_rst_dev执行到netdev_opt->clear_rxbuf( netdev );时,试图调用一个空指针(跳转到0x00000000),瞬间触发硬件异常(HardFault)
该具体问题解决方案:【只针对我做的项目遇到的问题】

总结
以后遇到问题怎么Debug?
以后如果再遇到死机、跑飞、HardFault,按以下步骤:
- 第一步:看现场
- 打开Call Stack窗口:看看死机前最后调用的两三个函数是谁,嫌疑犯基本就在这几个函数里。
- 打开Fault Reports:看看是 CFSR 里的哪位(如 PRECISERR 精确总线错误、UNDEFINSTR 未定义指令)在作祟,并记录下 BFAR(如果有效的话)。
- 第二步:锁变量(Watch窗口+局部变量)
- 在Call Stack锁定的嫌疑函数开头打断点
- 把该函数用到的结构体、指针、关键变量全部拖进Watch窗口。
- 重点关注指针类型:看看哪个指针变成了0x00000000、0x00000004、0x2000...(非正常数组区)、或者0x000045xx(代码区地址)
- 第三步:看汇编(Disassembly 窗口)
- 如果C代码里看不出来问题,去反汇编窗口
- 跳到报错的PC地址(或者使用Show Disassembly at Address),看看崩在哪条指令。通常是LDR(加载)或STR(存储)指令。
- 第四步:验堆栈(Memory 窗口 - 进阶)
- 如果上述方法都找不到,再去用
MSP或PSP算地址查堆栈。注意,一定要确保看到了正确的内存块,如果数据全乱,说明发生了栈溢出(Stack Overflow)。
- 如果上述方法都找不到,再去用
避坑(如何不写Bug)
- 函数指针结构体格式必须初始化 :如果用了函数指针结构体,初始化最好用
.成员名 = 函数名的格式,并且赋值NULL或者写个默认函数,调用前判断一下if(ptr->func != NULL)。 - 防御性编程 :在解引用指针(
ptr->xxx)之前,一定要判断if(ptr != NULL)。 - 警惕数组越界:这次的乱码0x457e也可能是其他数组越界把指针踩坏了。使用数组时检查下标边界。
- 善用调试工具:日常开发时,编译选项坚决关闭高等级优化(用 -O0),开启Debug信息,这样单步调式才准。
- 多看编译警告:很多时候,把函数当成变量穿,未初始化指针,编译器会给Warning的,不要忽略它们。