找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 69526|回复: 14
打印 上一主题 下一主题
收起左侧

stm32 HardFault_Handler调试及问题查找方法

  [复制链接]
跳转到指定楼层
楼主
本帖最后由 piaolin 于 2015-10-29 17:20 编辑

  STM32出现HardFault_Handler故障的原因主要有两个方面:

1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。

2、堆栈溢出。增加堆栈的大小。

出现问题时排查的方法:

发生异常之后可首先查看LR寄存器中的值,确定当前使用堆栈为MSP或PSP,然后找到相应堆栈的指针,并在内存中查看相应堆栈里的内容。由于异常发生时,内核将R0~R3、R12、Returnaddress、PSR、LR寄存器依次入栈,其中Return address即为发生异常前PC将要执行的下一条指令地址。

注意:寄存器均是32位,且STM32是小端模式。(参考Cortex-M3权威)

编写问题代码如下:

  1. void StackFlow(void)

  2. {

  3. int a[3],i;

  4. for(i=0; i<10000; i++)

  5. {

  6. a[i]=1;

  7. }

  8. }

  9. void SystemInit(void)

  10. {





  11. RCC->CR |= (uint32_t)0x00000001;



  12. RCC->CFGR = 0x00000000;



  13. RCC->CR &= (uint32_t)0xFEF6FFFF;



  14. RCC->PLLCFGR = 0x24003010;

  15. StackFlow();



  16. RCC->CR &= (uint32_t)0xFFFBFFFF;

  17. 。。。。。。。。。。。。。。

  18. }
复制代码


DEBUG如下图

SP值为0x20008560,查看堆栈里面的值依次为R0~R3、R12、Return address、PSR、LR, 例如R0(1027 00 00), 显然堆栈后第21个字节到24字节即为Returnaddress,该地址0x08001FFD即为异常前PC将要执行的下一条指令地址(即StackFlow()后面的语句处RCC->CR &= (uint32_t)0xFFFBFFFF)

另一种方法:

默认的HardFault_Handler处理方法不是B .这样的死循环么?楼主将它改成BXLR直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿


      Cortex-M3/4的Fault异常是由于非法的存储器访问(比如访问0地址、写只读存储位置等)和非法的程序行为(比如除以0等)等造成的。常见的4种异常及产生异常的情况如下:


BusFault:在fetch指令、数据读写、fetch中断向量或中断时存储恢复寄存器栈情况下,检测到内存访问错误则产生BusFault。
Memory ManagementFault:访问了内存管理单元(MPU)定义的不合法的内存区域,比如向只读区域写入数据。
UsageFault:检测到未定义指令或在存取内存时有未对齐。还可以通过软件配置是否检测到除0和其它未对齐内存访问也产生该异常,默认关闭,需要在工程初始化时配置:

[cpp] viewplaincopyprint?


  • SCB->CCR |= 0x18; // enable div-by-0 and unaligned fault  


HardFault:在调试程序过程中,这种异常最常见。上面三种异常发生任何一种异常都会引起HardFault,在上面的三种异常未使能的情况下,默认发生异常时进入HardFault中断服务程序。使能前三种异常也要在初始化时配置:

[cpp] viewplaincopyprint?


  • SCB->SHCSR |= 0x00007000;   // enable Usage Fault, Bus Fault, and MMU Fault  



在默认复位初始化时,HardFault使能,其它三者不使能,因此当程序中出现不合法内存访问(一般是指针错误引起)或非法的程序行为(一般就是数学里面常见的除0)时都将产生HardFault中断

[url=]2 HardFault调试方法[/url]假设IDE环境为Keil,芯片为STM32F103。
在stm32f10x_it.c中,添加软件断点,一旦调试时出现Hard Fault则会在停在__breakpoint(0)处。




  •   
  • void HardFault_Handler(void)  
  • {  
  •    
  •   if (CoreDebug->DHCSR & 1) {  //check C_DEBUGEN == 1 -> Debugger Connected  
  •       __breakpoint(0);  // halt program execution here         
  •   }  
  •   while (1)  
  •   {  
  •   }  
  • }  



当进入HardFault断点后,菜单栏Peripherals >Core Peripherals >FaultReports打开异常发生的报告,查看发生异常的原因。
  

上面的报告发生了BUS FAULT,并将Fault的中断服务转向Hard Fault。


相对于检测发生了什么异常,定位异常发生位置显得更重要。
(1)打开Call Stack窗口(如下图左侧,断点停在Hard Fault服务程序中)


(2)在Call Stack的HardFault_Handler上右键Show CallerCode(有的Keil版本也可以直接双击)


这时将跳转到发生异常的源代码位置(如上图),异常发生在p->hour=0这一行。这里错误很明显:指针p尚未为成员变量分配内存空间,直接访问未分配的内粗空间肯定出错。

再说明2点:
[1] 在复杂的情况下,即使定位了异常发生位置也很难容易的改正错误,要学会使用Watch窗口对发生错误的指针变量进行跟踪;
[2]在问题不明晰的情况下,尝试分析反汇编代码,就自己遇到的,部分情况下的异常发生在BL等跳转指令处,BL跳转到了不合法的内存地址产生异常

Refrences:
[1] Application Note209. Using Cortex-M3 and Cortex-M4 FaultExceptions.


[2] Cortex-M3权威指南





分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏3 分享淘帖 顶1 踩
回复

使用道具 举报

沙发
ID:91350 发表于 2015-10-29 17:21 | 只看该作者
看到有朋友遇到Hard Fault 异常错误,特地找到一篇飞思卡尔工程师写的一片经验帖,定位Hard Fault 异常。

Kinetis MCU 采用 Cortex-M4 的内核,该内核的 Fault 异常可以捕获非法的内存访问和非法的编程行为。Fault异常能够检测到以下几类非法行为:
·        总线 Fault:  在取址、数据读/写、取中断变量、进入/退出中断时寄存器堆栈操作(入栈/出栈)时检测到内存访问错误。
·        存储器管理 Fault: 检测到内存访问违反了内存保护单元(MPU, MemoryProtection Unit)定义的区域。
·        用法 Fault:  检测到未定义的指令异常,未对其的多重加载/存储内存访问。如果使能相应控制位,还可以检测出除数为零以及其他未对齐的内存访问。

·        硬 Fault:  如果上述的总线 Fault、存储器管理 Fault、用法 Fault 的处理程序不能被执行(例如禁能了总线 Fault、存储器管理Fault、用法Fault 的异常或者在这些异常处理程序中又出现了新的Fault)则触发硬Fault。
       MQX 操作系统启动的时候会安装上默认的异常中断处理函数,当系统异常时会产生一个“unexpected”中断,内核就会自动调用异常处理函数,同时也将运行用户自定义的处理函数,来实现特殊故障的定位方法。
       默认情况下,MQX把出现异常的任务挂起,避免故障进一步扩大。通过TAD 任务感知调试插件的Task summary 功能,我们可以观察到出现异常的任务情况。
开发人员在调试期间,需要弄清楚系统异常触发了哪类Fault,由什么原因触发了Fault 以及定位触发Fault 的代码。在这种情况下,可以利用自定义的Fault 中断处理程序来分析
Fault 出错原因。
     为了解释所述的 Fault 中断处理程序的原理,这里重述一下当系统产生异常时 MCU 的处理过程:
·        有一个压栈的过程,若产生异常时使用 PSP(进程栈指针),就压入到 PSP 中,若产生异常时使用MSP(主栈指针),就压入MSP 中。
·        会根据处理器的模式和使用的堆栈,设置 LR 的值(当然设置完的LR 的值再压栈)。
·        异常保存,硬件自动把 8 个寄存器的值压入堆栈(8 个寄存器依次为 xPSR、PC、LR、R12以及 R3~R0)。如果异常发生时,当前的代码正在使用PSP,则上面8 个寄存器压入PSP; 否则就压入MSP。
       当系统产生异常时,我们需要两个关键寄存器值,一个是 PC ,一个是 LR (链接寄存器),通过 LR找到相应的堆栈,再通过堆栈找到触发异常的PC 值。将产生异常时压入栈的 PC 值取出,并与反汇编的代码对比就能得到哪条指令产生了异常。
        这里解释一下关于 LR 寄存器的工作原理。如上所述,当 Cortex-M4 处理器接受了一个异常后,寄存器组中的一些寄存器值会被自动压入当前栈空间里,这其中就包括链接寄存器(LR )。这时的 LR 会被更新为异常返回时需要使用的特殊值(EXC_RETURN)。关于
EXC_RETURN 的定义如下,其为 32 位数值,高 28 位置 1,第 0 位到第三位则提供了异常返回机制所需的信息,如下表所示。可见其中第 2 位标示着进入异常前使用的栈是 MSP还是PSP。在异常处理过程结束时,MCU 需要根据该值来分配 SP 的值。这也是本方法中用来判断所使用堆栈的原理,其实现方法可以从后面_init_hardfault_isr 中看到。
   另外,我们可以利用 MQX 的控制台串口输出Fault 异常信息来帮助调试。编写Fault 处理程序时,将启动代码中默认的Fault 处理程序跟换成自己需要的Fault 处理程序。需要注意的是,由于是在中断中进行打印输出,MQX的控制台串口只能使用POLL 轮询模式的驱动,不能使用中断模式的驱动。
     用户可以编写自定义的硬 Fault 处理程序_int_hardfault_isr,修改 MQX 的中断向量定义vector.c,把里面的DEFAULT_VECTOR 代码段换成下面的代码。当系统出现硬Fault 异常时,将会调用自定义的Fault 处理_int_hardfault_isr函数。在这个函数,我们可以通过StackTrace-back 回溯出现问题的代码。
我们可以在_int_hardfault_isr 函数里将出现异常时的寄存器、堆栈、状态寄存器等信息打印出来。如果系统出现异常时,一般情况都会通过串口控制台打印出LR,PC的值。然后根据编译器生成的map 文件,找到出现问题的具体函数。
     从上图的串口输出我们可以看到 PC 和 LR 寄存器值,PC 的值为 0x56c6,我们根据汇编代码可以找到出现问题的指令。从而大大缩小了查找出现问题的范围,可以帮助开发人员快速定位问题的根本原因。

附录Fault异常中断处理代码:
  1. // hard fault handler in C,
  2. // with stack frame location as input parameter
  3. void hard_fault_handler_c (unsigned int * hardfault_args)
  4. {
  5.   unsigned int stacked_r0;
  6.   unsigned int stacked_r1;
  7.   unsigned int stacked_r2;
  8.   unsigned int stacked_r3;
  9.   unsigned int stacked_r12;
  10.   unsigned int stacked_lr;
  11.   unsigned int stacked_pc;
  12.   unsigned int stacked_psr;

  13.   stacked_r0 = ((unsigned long)hardfault_args[0]);
  14.   stacked_r1 = ((unsigned long)hardfault_args[1]);
  15.   stacked_r2 = ((unsigned long)hardfault_args[2]);
  16.   stacked_r3 = ((unsigned long)hardfault_args[3]);

  17.   stacked_r12 = ((unsigned long)hardfault_args[4]);
  18.   stacked_lr = ((unsigned long)hardfault_args[5]);
  19.   stacked_pc = ((unsigned long)hardfault_args[6]);
  20.   stacked_psr = ((unsigned long) hardfault_args[7]);

  21.   printf ("\n\n[Hard faulthandler - all numbers in hex]\n");
  22.   printf ("R0 = %x\n",stacked_r0);
  23.   printf ("R1 = %x\n",stacked_r1);
  24.   printf ("R2 = %x\n",stacked_r2);
  25.   printf ("R3 = %x\n",stacked_r3);
  26.   printf ("R12 = %x\n",stacked_r12);
  27.   printf ("LR [R14] = %x  subroutine call return address\n",stacked_lr);
  28.   printf ("PC [R15] = %x  program counter\n", stacked_pc);
  29.   printf ("PSR = %x\n",stacked_psr);

  30.   /******************* Add yourdebug trace here ***********************/
  31.   _int_kernel_isr();
  32. }

  33. /* hard fault interrupt handler */
  34. void _int_hardfault_isr( )
  35. {
  36.   __asm("TST LR, #4");
  37.   __asm("ITE EQ");
  38.   __asm("MRSEQ R0,MSP");
  39.   __asm("MRSNE R0,PSP");
  40.   __asm("Bhard_fault_handler_c");
  41. }
复制代码

fault_isr.c.zip

1.42 KB, 下载次数: 49, 下载积分: 黑币 -5

vectors.c.zip

3.43 KB, 下载次数: 39, 下载积分: 黑币 -5

如何定位Kinetis MCU Hard Fault异常.pdf

362.58 KB, 下载次数: 97, 下载积分: 黑币 -5

回复

使用道具 举报

板凳
ID:130367 发表于 2016-7-13 21:51 | 只看该作者
正在调试STM32F205,OS操作系统调度USB功能,楼主的方法正中,非常感谢!  
回复

使用道具 举报

地板
ID:60851 发表于 2016-8-3 16:57 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

5#
ID:163338 发表于 2017-2-3 12:18 | 只看该作者
我刚好需要,谢谢!
回复

使用道具 举报

6#
ID:166396 发表于 2017-2-24 14:15 | 只看该作者
谢谢楼主,正需要
回复

使用道具 举报

7#
ID:19303 发表于 2017-4-20 09:19 | 只看该作者
非常好,正好遇到同样的问题
回复

使用道具 举报

8#
ID:221889 发表于 2017-7-24 00:12 | 只看该作者
需要,感谢 !!!!!!!!!
回复

使用道具 举报

9#
ID:221889 发表于 2017-7-24 10:39 | 只看该作者
测试 ,需要下载
回复

使用道具 举报

10#
ID:250419 发表于 2017-11-17 15:02 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

11#
ID:321859 发表于 2018-12-25 10:31 | 只看该作者

谢谢楼主分享
回复

使用道具 举报

12#
ID:551850 发表于 2019-5-31 14:37 | 只看该作者
感谢分享 很有用
回复

使用道具 举报

13#
ID:245771 发表于 2019-8-24 10:16 | 只看该作者
如果运行几个小时后才出现硬件错误,而且查看代码定位到HAL_SPI_TransmitReceive应该查什么
回复

使用道具 举报

14#
ID:276761 发表于 2020-12-20 17:29 | 只看该作者
感谢分享 很有用
回复

使用道具 举报

15#
ID:345850 发表于 2023-7-4 17:15 | 只看该作者
谢谢楼主,正需要
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表