【嵌入式】HardFault原因定位

1.简介

在Linux下出现程序跑飞时,如段错误(segment fault),往往可以借助CoreDump结合gdb快速定位引起段错误的程序。而在单片机调试时,发生类似段错误时会进入硬件错误HardFault,引发HardFault异常中断,以STM32F4系列为例,当发生HardFault异常时会进入如下中断服务函数,在调试阶段失能看门狗的情形下,将会进入死循环。

void HardFault_Handler(void)
{
	while (1)
	{
	}
}

2.HardFault的主要诱因

HardFault的主要诱因与段错误非常相似,总结大致如以下几点:
1.数组越界,内存溢出;
2.指针使用错误,如使用、释放未申请的空间;
3.堆栈空间不足,在单片机里也就是可用ram空间不足;

明白了以上几个诱因后,一方面在写程序时要尽量避免以上错误。但常在河边走哪有不湿鞋,一旦发生了HardFault,如何快速定位就显得极为重要了。

3.一个经历的HardFault实例

3.1 描述

某项目验证阶段,驱动SPI时使用的官方HAL库函数,发现当程序运行1到2个小时后,会莫名进入HardFault中断服务函数。

3.2 原因定位

可以在其中断服务函数中的while(1)上打断点,当发生HardFault后,观察call stack(函数调用栈)来观察程序是如何一步步跑到HardFault里的。一般情况下,通过观察call stack就能够定位到引发HardFault的程序语句了。以我的程序为例,发现进入HardFault前,上一条语句如下:

/* Check if the SPI is already enabled */
  if((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  }

是HAL_SPI_TransmitReceive函数里的一句,看到这里,基本就可以猜出是指针使用错误导致的了,问题一定在hspi->Instance->CR1这句上,那么hspi、Instance、CR1,一定有一个出了问题,此时需要慢慢排查。

3.3 真凶露面

通过观察这几个指针后,发现Instance存在问题,Instance为SPI初始化时的SPI设备,如下所示:

void MX_SPI1_Init(void)
{
	  hspi1.Instance = SPI1;
	  hspi1.Init.Mode = SPI_MODE_MASTER;
	  ...
	  ...
	  ...
}

在初始化SPI1时, hspi1.Instance应为0x40013000,而发生HardFault时,其值变成了一个乱七八糟的值。作为在ram中存放的hspi1结构体全局变量,他里边的值Instance发生了莫名其妙的改变,大概率发生了ram冲内存的现象。
一般情况下,在eeprom、flash中发生冲内存时,有可能是写的时候长度越界,也有可能是写地址错误。而在全局ram中发生冲内存,十有八九是操作存在hspi1前边的全局变量时发生了越界,导致把hspi1的内存给冲了。那么如何知道ram中存在hspi1前边的变量是什么呢?这时就要map文件出场了。
打开编译生成的map文件,观察各全局变量在ram中的存放,以发现hspi1变量前存的是什么,定位如下:

clrIRQ                  0x800'195f    0x12  Code  Gb  comm_EXIT_IRQ.o [1]
errCheck             	0x2000'623c    0xcc  Data  Gb  ad7606_app.o [1]
hspi1                   0x2000'62fc    0x58  Data  Gb  spi.o [1]
hspi2                   0x2000'6354    0x58  Data  Gb  spi.o [1]
hspi3                   0x2000'63ac    0x58  Data  Gb  spi.o [1]

发现hspi1的前边是名为errCheck的变量,存放地址为0x2000623c,占用长度为0xcc。那就去程序中检查errCheck的相关操作,这次问题一眼看出来了——数组越界。导致操作errCheck时一不留神操作到hspi1所在的内存区域了,导致Instance指针存了个乱值,指向一片未申请的内存,那么使用Instance时必然会导致HardFault。

4.HardFault调试总结

可以看到,调试HardFault时主要步骤如下:
1.在HardFault中断函数中打断点;
2.查看call stack定位跑飞前的语句;
3.分析有无数组越界、内存溢出、指针误操作的情况,并借助map文件对内存进行分析,揪出真凶。

4.1 利用堆栈找出跑飞语句

然而,存在一种情况,那就是跑到HardFault中断函数里的断点时,查看call stack时一片空白,这时如何定位跑飞前的语句呢?
这时就要用到CPU的SP寄存器,根据异常压栈流程反推出LR寄存器,那么LR对应的汇编指令就是跑飞前的程序。
以STM32F4为例,其基于cortex-M4内核。进入到HardFault中断时,查看堆栈指针SP,然后根据《cortex-M3/M4权威指南》,得出异常压栈顺序为R0、R1、R2、R3、R12、LR。此时SP寄存器中存的地址指向栈顶R0,那么将SP寄存器中的地址加5*4=20,即为LR寄存器的地址,在Memory窗口中查看此地址存的值,即为HardFault前的语句地址0x8000282,到汇编窗口中即可查看相应的汇编指令,以及对应的C语言语句。
在这里插入图片描述

  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜老越

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值