嵌入式HardFault原因定位
Posted 菜老越
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式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语言语句。
以上是关于嵌入式HardFault原因定位的主要内容,如果未能解决你的问题,请参考以下文章
Keil环境下STM32定位hardfault位置方法和遇到的情况
Keil环境下STM32定位hardfault位置方法和遇到的情况
Keil环境下STM32定位hardfault位置方法和遇到的情况
嵌入式J-Link Commander + map文件 + asm文件 离线定位crash崩溃