HardFault异常调试总结

在使用Keil进行STM32开发过程中,经常遇到HardFault硬件异常,导致程序卡死。HardFault异常比较隐匿,如何快速定位产生HardFault异常前的问题代码段是解决异常的关键。该文总结了发生HardFault异常时,定位问题代码段的三种方法。前两种是在反汇编文件中,根据LR寄存器的值来确定问题代码段的位置。最后一种方法是通过KEIL仿真调试环境中函数调用栈直接定位问题代码段的具体位置。

 1、产生HardFault硬件异常的几个操作

  • 数组越界操作:进行数组操作时,发生了越界访问。

  • 堆栈溢出,程序跑飞:当程序函数调用较深,且每个函数中有较多的局部变量时,如果栈空间设置的比较小,很容易产生栈溢出,致使程序跑飞。

  • 非法地址访问:在使用指针时,引用NULL指针或其他寄存器非法地址时有可能会导致错误,产生HardFault硬件异常。

  • 中断处理错误。

因此,当产生HardFault硬件异常时,定位到发生HardFault前的问题程序片段后,可以查看程序片段中是否有发生数组越界,指针操作是否合法,是否定义比较大的局部变量数组(栈溢出),一般就是这几种错误。

 2、快速定位发生HardFault硬件异常三种方法

                2.1、Coretex-M3架构芯片异常处理

以下内容参考书籍The definitive guide to ARM Cortex-M3 and cortex-M4 Processors(Third Edition)中第8章---->深入了解异常处理。

使用STM32CubeMX创建工程时,默认使用的是HAL库,HAL库中使用C函数处理异常。

在使用C函数处理处理异常时,异常机制需要在异常入口处自动保存R0~R3、R12、LR、PSR,并在异常退出时将它们恢复,这些都要由处理器硬件控制。这样,当返回到被中断的程序时,所有寄存器的数值都会和进入中断时相同。另外,与普通的C函数调用不同,返回地址(PC)的数值并没有存储在LR中。异常机制在进入异常时将EXC_RETURN代码放入了LR中,当利用BX、POP或存储器加载指令(LDR或LDM)被加载到程序寄存器中时,该数值用于触发异常返回机制。因此,异常流程也需要将返回地址保存。

从EXC_RETURN位域表可以知道,EXC_RETURN值的不同,代表的含义不同:

  • EXC_RETURN = 0XFFFFFFF9,使用MSP,中断返回用户程序。

  • EXC_RETURN = 0XFFFFFFFD,使用PSP,中断返回用户程序。

  • EXC_RETURN = 0XFFFFFFF1,使用MSP,从中断返回另一个中断。

因此,进行KEIL仿真时,通过查看EXC_RETURN(也即LR寄存器)的值,我们就可以判断出,错误是发生在中断中,还是发生在其他函数中,同时判断使用的是MSP(主栈)还是PSP(进程栈)。MSP、PSP中保存的是栈帧的起始地址,根据栈帧结构和起始地址就可以确定发生异常前LR的值。

栈帧:在异常入口处被压入栈空间的数据块为栈帧。发生异常时,R0~R3、R12、LR、PSR会被压入栈。下图是栈帧结构:

图1 在不需要或禁止双字栈对齐时,Cortex-M3或Cortex-M4(无浮点单元)处理器的异常栈帧

                2.2、如何确定LR链接寄存器的值

为什么要确定LR寄存器的值?

LR(Link Register)链接寄存器,在Coretex-M3架构中有两种用途:一是用来保存子程序的返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2)。因此在异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行,可以确定发生异常前,处理器正在运行的程序片段。因此,要想确定发生Hardfault异常前,处理器正在运行的程序片段,就必须确定LR寄存器的值。

确定LR寄存器值的步骤:

  1. 调试开始前先在HardFault_Handler函数while(1)处打断点。

  2. 运行到断点处时,查看右侧LR寄存器的值(特别注意,此时LR是保存的EXC_RETURN的值),根据寄存器值确定使用的是主栈指针MSP还是进程栈指针PSP(图中EXC_RETURN = 0xFFFFFFF9,表明使用的是主栈)。

  3. 查看Banked中对应栈指针的值,这里MSP = 0x20000678。

  4. 调出Memory1窗口,查看以0x20000678为首地址的栈帧内容。

  5. 从首地址开始,第六个08开头的数据就是实际LR寄存器的值。

 图2 如何确定LR的值

                2.3、通过反汇编文件根据LR的值确定发生异常前运行的程序片段

2.3.1 配置KEIL生成反汇编文件

根据图3,在Run#1一栏中填写如下指令,编译工程生成扩展名为dis的汇编文件。

图3 配置KEIL自动生成反汇编文件

使用Notepad++打开生成的反汇编文件,查找LR寄存器的值0x08000DA1,「有可能查不到」。因为ARM-UAL( Unified Assembly Language)中规定,操作数的bit0决定该条指令使用Thumb指令集还是ARM指令集。

  • bit0 = 0时,使用ARM指令集

  • bit0 = 1时,使用Thumb指令集

因此,可以忽略bit0,也即查找0x08000DA0,发现代码片段在main函数中,然后去main函数中去查看代码有没有用发生数组越界,非法地址访问等错误。

 

 图4 在反汇编文件中查找LR,定位问题代码段

main函数异常代码如下,导致错误的原因是数组访问越界。主函数中定义了testbuff数组,长度为10。但是在主循环中,累加赋值时,并没有限制变量i的范围,导致数组访问越界,产生了HardFault异常。

2.3.2 直接在调试时调出反汇编代码窗口进行定位

在调试界面工具栏View-->Disassembly Window,调出反汇编窗口,然后在该窗口中右击选择下图中的④Show Disassembly  at Address...,输入要查找的LR地址0x08000DA0,点击Go To,就会跳转到对应的代码区域(反汇编代码和代码区同步联动)

图5 利用反汇编调试窗口定位问题代码段

                2.4、根据程序调用栈直接定位问题代码段

  1. 在调试界面右下方选择Call Stack+Locals。

  2. 选择HardFault_Handler,鼠标右击。

  3. 在右键菜单选择Show Caller Code,程序会直接跳转到进入HardFault_Handler函数之前的代码片段。

 图6 使用程序调用栈,直接定位问题代码段

3、HardFault调试实例

本例是在实际开发中遇到的一个问题,场景是在开发GPS数据解析程序时,串口3中断中利用函数指针调用GPS数据解析处理回调函数,但是由于GPS芯片一上电就会不间断的向串口发送数据。此时如果已经打开串口中断,但是没有设置回调函数,使用函数指针调用回调函数时就会发生HardFault异常,因为此时函数指针的值是0。

                3.1、制造HardFault异常

1、在函数MX_USART3_UART_Init中,添加25~27行代码,让串口3初始化时就打开接收中断。

 

2、主函数中故意在函数SetGPSInputDataProcessCallBack之前延时1000ms,制造异常发生的条件。

3、因为在串口初始化时,串口中断已经打开,在延时1000ms过程中,GPS已经向串口发送数据,产生了串口中断,但此时还没有设置回调函数,函数指针__GPSInputProcessCallback的值是0,当程序进入串口3中断执行到17行时,就会发生HardFault异常。

4、调试之前,先在main.h中,将宏TEST_INVALID_ADDRESS_ERR设置为1,宏TEST_STACK_ERR设置为0。 

                3.2、异常调试

 1、根据2.4中的方法,直接利用函数调用栈定位问题代码段,可以发现,系统自动定位在第55行函数指针__GPSInputProcessCallback使用的地方,然后在定位处打断点。

PS:此时可以看到LR寄存器的值,也就是EXC_RETURN的值为0xFFFFFFF1,说明使用的是MSP,从中断返回另一个中断,也就是说,异常之前代码是在中断中产生的。

 

图7 根据程序调用栈直接确定问题代码段

 2、重新调试,程序停止在上一步骤设置的断点处,此时光标移动到函数指针__GPSInputProcessCallback处,会发现,系统提示函数指针__GPSInputProcessCallback的值是0x00000000。

图8 程序代码段错误原因分析

3、分析为什么函数指针的值为0x00000000,由于是示例程序,前面已经给出了答案。但是在实际的调试过程中,是需要根据自己的程序进行分析的。本例程序的具体解决方案可以分为两种:

  • 第一种解决方法:不在串口初始化时打开中断,在函数MX_USART3_UART_Init中去掉25~27行代码。在设置回调函数指针后再打开串口中断。

第二种解决办法:在调用函数指针时进行判断,添加第17行代码即可。

 4、项目使用说明及资料获取地址

                4.1、项目使用说明

项目工程中配置了预编译代码,可以单独进行数组越界和非法地址引用异常测试。测试时只需要在main.h中打开对应的宏开关即可。

                 4.2、项目获取地址

链接:https://pan.baidu.com/s/1lXdKwnyVNfbs6X7ZZl3v0g?pwd=u098 
提取码:u098 
--来自百度网盘超级会员V6的分享

其他博文:

【STM32】HardFault问题详细分析及调试笔记_stm32hardfault定位_一起玩MCU的博客-CSDN博客

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值