打印
[技术问答]

定位ARM Hard Fault 的方法

[复制链接]
1330|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
maqianqu|  楼主 | 2024-7-18 02:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1,  用Keil的话,可以做如下操作:

先将fault中断函数的内容改为:

HardFault_Handler\

                PROC

                ;EXPORT  HardFault_Handler         [WEAK]

                ;B                  .

                                IMPORT hard_fault_handler_c  

                                TST LR, #4  

                                ITE EQ  

                                MRSEQ R0, MSP  

                                MRSNE R0, PSP  

                                B hard_fault_handler_c  

                ENDP

************************************************insert start*************************************************

摘抄自Triton.zhang——eeworld

1. MSP和PSP 的含义是Main_Stack_Pointer 和Process_Stack_Pointer,在逻辑地址上他们都是R13

2. 权威手册上说的很清楚PSP主要是在Handler的模式下使用,MSP主要在线程模式下使用(当然你在线程模式下也可以调用PSP,需要你做特殊的处理).
3. 这意味着同一个逻辑地址,实际上有两个物理寄存器,一个为MSP,一个为PSP,在不同的工作模式调用不同的物理寄存器。举一个简单的例子,很多MCU的的UART只有一个BUFF,TXBUFF和RXBUFF都是一个地址,当你写BUFF时写入的是TXBUFF, 读操作时调用的是RXBUFF。基本原理就是这样。
4. 至于为什么这么设计,我想是为了在进行模式转换的时候,减少堆栈的保存工作。同时也可以为不同权限的工作模式设置不同的堆栈。

************************************************insert end***************************************************

然后在源程序里添加下面的函数代码:

// hard fault handler in C,  

// with stack frame location as input parameter  

void hard_fault_handler_c(unsigned int * hardfault_args)  

{  

unsigned int stacked_r0;  

unsigned int stacked_r1;  

unsigned int stacked_r2;  

unsigned int stacked_r3;  

unsigned int stacked_r12;  

unsigned int stacked_lr;  

unsigned int stacked_pc;  

unsigned int stacked_psr;  

stacked_r0 = ((unsigned long) hardfault_args[0]);  

stacked_r1 = ((unsigned long) hardfault_args[1]);  

stacked_r2 = ((unsigned long) hardfault_args[2]);  

stacked_r3 = ((unsigned long) hardfault_args[3]);  

stacked_r12 = ((unsigned long) hardfault_args[4]);  

stacked_lr = ((unsigned long) hardfault_args[5]);  

stacked_pc = ((unsigned long) hardfault_args[6]);  

stacked_psr = ((unsigned long) hardfault_args[7]);  

printf ("[Hard fault handler]\n");  

printf ("R0 = %x\n", stacked_r0);  

printf ("R1 = %x\n", stacked_r1);  

printf ("R2 = %x\n", stacked_r2);  

printf ("R3 = %x\n", stacked_r3);  

printf ("R12 = %x\n", stacked_r12);  

printf ("LR = %x\n", stacked_lr);  

printf ("PC = %x\n", stacked_pc);  

printf ("PSR = %x\n", stacked_psr);  

printf ("BFAR = %x\n", (*((volatile unsigned long *)(0xE000ED38))));  

printf ("CFSR = %x\n", (*((volatile unsigned long *)(0xE000ED28))));  

printf ("HFSR = %x\n", (*((volatile unsigned long *)(0xE000ED2C))));  

printf ("DFSR = %x\n", (*((volatile unsigned long *)(0xE000ED30))));  

printf ("AFSR = %x\n", (*((volatile unsigned long *)(0xE000ED3C))));  



while(1)

{

        ;;

}

}  

如果使用调试器,则可以在第一个printf处设置断点.没有的话看串口打印结果

通过查看stacked_lr的内容可以知道程序运行到哪个位置出现fault

然后查看编译后汇编代码,可以知道源程序是哪个函数哪一步出现问题,

配合其它寄存器的内容来分析找出原因

2.用IAR的话,把startup_ewarm.c文件中的FaultISR()函数的内容改为:

volatile unsigned int stacked_r0;  

volatile unsigned int stacked_r1;  

volatile unsigned int stacked_r2;  

volatile unsigned int stacked_r3;  

volatile unsigned int stacked_r12;  

volatile unsigned int stacked_lr;  

volatile unsigned int stacked_pc;  

volatile unsigned int stacked_psr;  

//unsigned long cc;

stacked_r0 = ((unsigned long) hardfault_args[0]);  

stacked_r1 = ((unsigned long) hardfault_args[1]);  

stacked_r2 = ((unsigned long) hardfault_args[2]);  

stacked_r3 = ((unsigned long) hardfault_args[3]);  

stacked_r12 = ((unsigned long) hardfault_args[4]);  

stacked_lr = ((unsigned long) hardfault_args[5]);  

stacked_pc = ((unsigned long) hardfault_args[6]);  

stacked_psr = ((unsigned long) hardfault_args[7]);  

printf ("[Hard fault handler]\n");  

printf ("R0 = %x\n", stacked_r0);  

printf ("R1 = %x\n", stacked_r1);  

printf ("R2 = %x\n", stacked_r2);  

printf ("R3 = %x\n", stacked_r3);  

printf ("R12 = %x\n", stacked_r12);  

printf ("LR = %x\n", stacked_lr);  

printf ("PC = %x\n", stacked_pc);  

printf ("PSR = %x\n", stacked_psr);  

printf ("BFAR = %x\n", (*((volatile unsigned long *)(0xE000ED38))));  

printf ("CFSR = %x\n", (*((volatile unsigned long *)(0xE000ED28))));  

printf ("HFSR = %x\n", (*((volatile unsigned long *)(0xE000ED2C))));  

printf ("DFSR = %x\n", (*((volatile unsigned long *)(0xE000ED30))));  

printf ("AFSR = %x\n", (*((volatile unsigned long *)(0xE000ED3C))));  



  while(1)

  {

          ;;

  }

如果使用调试器,则可以在第一个printf处设置断点.没有的话看串口打印结果

通过查看stacked_lr的内容可以知道程序运行到哪个位置出现fault

然后查看编译后汇编代码,可以知道源程序是哪个函数哪一步出现问题,

配合其它寄存器的内容来分析找出原因

另一种方法:
    默认的HardFaudler 处理方法不是死循环么?将它改成BX LR直接返回的形式。然后再这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句哪里。
_asm void wait()
{
    BX lr  //BX无条件转移指令
}


void HardFault_Handler(void)
{
    wait();

}

【ARM 汇编基础速成6】ARM汇编之条件执行与分支 - 简书

ARM 指令举例

SWI_Exception:
STMFD SP!, {R2-R3,LR} //把R2,R3,LR值入栈
#0号软中断的处理程序
CMP R0, #0 //将R0和0比较
//以下4行带EQ条件的代码均为当R0为0时应该执行的语句
MRSEQ R2, SPSR //把SPSR读入到R2中
STREQ R2, [R1] /把R2的值存入到[R1]中
ORREQ R2, R2, #0x80 //把R2的Bit7位置1
MSREQ SPSR_c, R2 //把R2的值写入到SPSR_c中,即禁止IRQ
#1号软中断的处理程序
CMP R0, #1 //比较R0值和1
LDREQ R2, [R1] //相等则把[R1]中的数据存入R2中
MSREQ SPSR_c, R2 //相等把R2的值写入到SPSR_c中,恢复IRQ
#11号软中断的处理程序
CMP R0, #11 //比较R0的值和11
MRSEQ R2, SPSR //相等则把SPSR的值转存入到R2中
BICEQ R2, R2, #0x1F //相等则把R2的Bit0~Bit4全部清零
ORREQ R2, R2, #Mode_SYS //相等则把R2与#Mode_SYS相与再存入R2
MSREQ SPSR_c, R2 //相等则把R2的值存入SPSR_c中,即进入系统模式
#12号软中断的处理程序
CMP R0, #12 //比较R0的值和12
MRSEQ R2, SPSR //相等则把SPSR的值存入R2
BICEQ R2, R2, #0x1F //相等则把R2的Bit0~Bit4清零
ORREQ R2, R2, #Mode_USR //相等则把R2与#Mode_USR相与再存入R2中
MSREQ SPSR_c, R2 //相等则把R2存入SPSR_c,即进入用户模式
LDMFD SP!, {R2-R3,PC} //恢复R2、R3、PC值,返回
.END //汇编代码段结束

使用特权

评论回复
沙发
alxd| | 2024-8-21 14:59 | 只看该作者
在ARM架构的微控制器中,Hard Fault 是一种严重的错误状态,通常发生在系统无法处理某些异常或错误时。定位和调试 Hard Fault 是嵌入式开发中的一个重要任务

使用特权

评论回复
板凳
Betty1299| | 2024-8-21 16:02 | 只看该作者
当发生 Hard Fault 时,可以查看 Hard Fault 状态寄存器(HardFault Status Register)来获取错误的原因。这些寄存器包括 HFSR(HardFault Status Register)、CFSR(Configurable Fault Status Register)等

使用特权

评论回复
地板
Charlene沙| | 2024-8-21 17:11 | 只看该作者
使用 JTAG 或 SWD 调试器连接到目标设备,在 Hard Fault 发生时,调试器可以捕获程序的当前状态,包括寄存器值、堆栈内容和程序计数器(PC)

使用特权

评论回复
5
Allison8859| | 2024-8-21 18:16 | 只看该作者
通过调试器的中断功能,可以在 Hard Fault 处理函数中设置断点,以便在发生 Hard Fault 时立即捕获

使用特权

评论回复
6
Emily999| | 2024-8-21 19:23 | 只看该作者
在代码中实现一个 Hard Fault 处理函数,当 Hard Fault 发生时,这个函数会被调用。在这个函数中,可以打印或保存关键寄存器的值,如 PC、LR、SP 等,以便后续分析

使用特权

评论回复
7
Charlotte夏| | 2024-8-21 20:30 | 只看该作者
通过分析堆栈跟踪,可以确定 Hard Fault 发生时的函数调用链。这有助于定位导致 Hard Fault 的具体代码位置

使用特权

评论回复
8
B1lanche| | 2024-8-22 09:07 | 只看该作者
Hard Fault 通常与非法内存访问有关,如访问未映射的内存区域、写入只读内存等。检查代码中的指针操作和数组访问,确保它们都在有效的内存范围内。

使用特权

评论回复
9
Carina卡| | 2024-8-22 10:13 | 只看该作者
调试器通常支持硬件断点,可以在怀疑导致 Hard Fault 的代码位置设置硬件断点,当程序执行到这些位置时,调试器会自动中断,从而帮助定位问题

使用特权

评论回复
10
Candic12e| | 2024-8-22 11:21 | 只看该作者
确保中断优先级配置正确,避免由于中断优先级配置不当导致的 Hard Fault

使用特权

评论回复
11
Annie556| | 2024-8-22 13:00 | 只看该作者
使用硬件仿真器(如 ARM MPS2+ 或类似的开发板)可以在更接近实际硬件的环境中进行调试,有助于更准确地复现和定位 Hard Fault。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

14

主题

2458

帖子

2

粉丝