STM32进入HardFault的调试方法
我们在设计STM32程序时,经常由于内存越界等,使得程序意外进入了HardFault中断。但我们一时半会又不知道,这个中断,究竟是代码运行到哪里,才触发的。常规的做法,我们只能在线调试,一步步寻找异常点,但由于发生异常的时机是不确定的,有可能在线调试的时候就不出现异常了,所以相当头疼。但本文介绍一种方法,可以直接定位到异常点,节省排查问题的时间。
这个方法的关键是,由于STM32中断前会由硬件保护现场,这个所谓的“现场”正包含了中断前程序运行的位置信息。我们只要在HardFault中断服务程序里面,把这个“现场”读取出来,就能定位到发生异常时,运行程序的位置了。
具体应该怎么做呢?
第一步,了解中断保存现场的机制。
可以阅读下图中的内容,详细介绍了中断现场的保存机制。我们主要是拿到PC值即可,因为PC值是中断前,代码的执行位置。
根据上图内容,我们要得到中断前PC的值,可以这样做:
在中断服务程序里面,
- 取得当前SP值
- 以当前SP值加上6*4,得到中断前PC值的地址
- 从这个地址取得中断前PC值。
第二步,确定SP
图中内容还说了,堆栈寄存器SP实际有两个,一个是MSP,一个是PSP。不同的情况,使用不同的SP。那究竟哪个才是发生中断前,程序正在使用的SP呢?弄清楚这个很重要,因为不同的情况,现场的存储位置是不一样的。
有一种比较简单的方法就是,把MSP和PSP保存的现场都读取出来,由程序员自己去估计哪个现场更有参考价值。
但是还有一种可以判断的方法,那就是LR寄存器。STM32的LR寄存器会在发生中断时被赋值为EXC_RETURN,不同的值代表了保存现场所使用的SP。如下图所示。
那么判断SP是MSP还是PSP,可以这样做:
在中断服务程序中
- 读取当前LR
- 根据LR的第2位判断。如果第二位为0,证明现场是保存在MSP指向的位置;否则是PSP指向的位置。
如何编写代码取得寄存器LR,MSP,PSP的值,可参看MDK帮助文档。
那么可以编写函数如下。这是在MDK 5.14的代码,其中 __return_address()是由MDK提供的,功能是得到当前LR寄存器的值;__get_MSP() ,__get_PSP() 是STM32固件库提供的,获得MSP,PSP寄存器的值。
unsigned int Debug_GetPCPreForISR(int ISR_SoftPushBytes,unsigned int lr)
{
unsigned int pc_pre = 0;
if((lr &(1<<2)) == 0)
{ //6*4是STM32硬件中断保护现场的压栈;
//ISR_SoftPushBytes是中断服务函数进来时,编译器生成的PUSH指令压栈
//4*4是在中断服务函数中,调用该函数自身的压栈;
pc_pre =*(unsigned int*)( __get_MSP() + 6*4 + ISR_SoftPushBytes + 4*4 );
}
else
{
pc_pre =*(unsigned int*)( __get_PSP() + 6*4);
}
return pc_pre;
}
要弄清楚压栈过程:
1,当从普通函数进入到中断服务程序时,压栈过程是
用PSP进行硬件中断压栈(使用RTOS时)->中断服务函数使用MSP进行变量压栈-> Debug_GetPCPreForISR函数调用压栈
2,当从一个中断服务程序进入到另一个中断服务程序时,压栈过程是
用MSP进行硬件中断压栈->中断服务函数使用MSP进行变量压栈-> Debug_GetPCPreForISR函数调用压栈
其中, 中断服务函数使用MSP进行变量压栈,有的中断存在这个过程,有的没有。需要自己使用编译器的反汇编功能,查看中断服务函数处的汇编代码,看进入函数有没有压栈操作(PUSH指令)。
有了PC值之后还不好办吗?打开MDK工程,有一个反汇编调试功能,输入PC值就能定位到C代码所在位置,这就知道发生HardFault的位置了。