HardFault错误排查

概述

在日常的嵌入式的开发过程中,往往因为自己的不小心(比如指针指向未进行赋值便操作该指针)而触发硬件错误,而这类问题的出现又比较隐蔽,所以排查起来比较困难。本文以cortex-M3为例,说一种大家比较通用的排查方法来排查此问题,自己也就此记录一下。

cortex-M3异常处理

在发生HardFault的时候我们往往会触发HardFault异常,所以要找到引发HardFault的地方,还需要知道发生HardFault异常前一些寄存器的值。翻开《Cortex-M3权威指南》,中断/异常的响应序列中讲到,当CM3开始响应一个中断时,会在它看不见的体内奔涌起三股暗流:

  • 入栈: 把8个寄存器的值压入栈
  • 取向量:从向量表中找出对应的服务程序入口地址
  • 选择堆栈指针MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC。

入栈
响应异常的第一个行动,就是自动保存现场的必要部分:依次把xPSR, PC, LR, R12以及R3‐R0由硬件自动压入适当的堆栈中:如果当响应异常时,当前的代码正在使用PSP,则压入PSP,即使用线程堆栈;否则压入MSP,使用主堆栈。一旦进入了服务例程,就将一直使用主堆栈。

假设入栈开始时, SP的值为N,则在入栈后,堆栈内部的变化如下表表示。又因为AHB接口上的流水线操作本质,地址和数据都在经过一个流水线周期之后才进入。另外,这种入栈在机器的内部,并不是严格按堆栈操作的顺序的——但是机器会保证:正确的寄存器将被保存到正确的位置。
在这里插入图片描述
在这里我们需要着重了解入栈后各个寄存器存储的位置,因为一定进入HardFault异常后,我们会根据SP指向的栈顶指针去找到入栈的PC指针和LR寄存器内容。入栈的PC指针指向异常指令的下一条指令,LR寄存器为发生函数调用完成后返回的函数地址,我们需要知道 LR和PC在栈中是倒数第6个和第7个地址(32位地址) 便可找到这两个寄存器的内容。
在找到入栈的PC指针的值,如果用的是keil仿真的情况下,我们可以很容易根据反汇编的指令找到发生错误的地方。当然很多时候我们无法进行仿真,那么肯能需要我们在HardFault中断函数里面将相应的寄存器打印出来,并根据SP的指向将一段RAM打印出来,然后再结合map文件,找到发生错误的函数,再去排查问题所在点。

实例解析

为了演示该错误,我们再代码里面插入一段错误代码,即一个野指针的操作:

void error_function(void)
{
	int * ptr = (int *)0x01;
	for(int i = 0; i < 10; i++)
	{
		ptr[i] = i;
	}
}

然后将这段代码插入到我们的程序中。
在这里插入图片描述

可以仿真的情况

我们仿真这段代码:在这里插入图片描述
根据入栈的PC指针的值和KEIL的汇编代码我们很容易找到问题出现在void error_function(void)函数内部。

不能仿真的情况

在不能仿真的情况下,可以借助UART打印的方式,在HardFault中断函数里面将相应的寄存器打印出来,并根据SP的指向将一段RAM打印出来,然后再结合map文件来排查问题。在该例子中我们打开map文件,找到0x08000C04所在的函数,便可以知道问题出在哪。
在这里插入图片描述
从图片中可以很容易的找到0x08000C04的地址在void error_function(void)函数内部,从而锁定问题的范围。

额外的小问题

在实际的应用过程中我们知道在用C语言直接操作芯片的寄存器不太方便(当然也可以借助asm去调用汇编指令),而在不能仿真的情况下我们又要去打印这些寄存器的值,我看到有芯片是这样做的,感觉很不错,所以分享一下。

在cortex-m3的启动文件中修改HardFault_Handler的内容如下:

CopyPSP			PROC	
				MRS R0, PSP
				ENDP
					
_GetFault		PROC
				LDR R1,=0x20000000
				
				MRS	R2,APSR
				STR	R2,[R1,#0x0]
				
				MRS	R2,IAPSR
				STR	R2,[R1,#0x4]
				
				MRS	R2,EAPSR
				STR	R2,[R1,#0x8]
				
				MRS	R2,IPSR
				STR	R2,[R1,#0xC]
				
				MRS	R2,EPSR
				STR	R2,[R1,#0x10]
				
				MRS	R2,IEPSR
				STR	R2,[R1,#0x14]
				
				MRS	R2,PRIMASK
				STR	R2,[R1,#0x18]
				
				MRS	R2,CONTROL
				STR	R2,[R1,#0x1C]
				
				STR	R4,[R1,#0x20]
				STR	R5,[R1,#0x24]
				STR	R6,[R1,#0x28]
				STR	R7,[R1,#0x2C]
				
				BX	LR

				ENDP
					
RunHardFaultForC\
				PROC
                IMPORT  IsrHardFault    
				BL	_GetFault
				MOV R1,R3
				BL	IsrHardFault
				B	.
				ENDP
										
HardFault_Handler\
                PROC
;                EXPORT  HardFault_Handler          [WEAK]
				MOV R3, LR     	; R3 = LR
				MOVS R0, #4 	; R0 = 4
				MOV R1, LR		; R1 = LR
				ANDS R1, R0		; R1 += R0 --> R1 += 4
				TST R1, R0		; R1 &= 0x04
				BNE CopyPSP		; if(R1 & 0x04 != 0) goto CopyPSP;
				MRS R0, MSP		; else R0 = MSP
				BL	RunHardFaultForC
;                B       .
                ENDP

在中断文件中(比如stm32f1xx_it.c)添加void IsrHardFault(unsigned long rn[], unsigned long lr)函数,将各寄存器的值打印。

#define printmsg   	printf
#define HFSR				(*((volatile unsigned long *)(0xE000ED2C)))
#define MFSR				(*((volatile unsigned char *)(0xE000ED28)))			
#define BFSR				(*((volatile unsigned char *)(0xE000ED29)))			
#define UFSR				(*((volatile unsigned short *)(0xE000ED2A)))

void FaultInf(unsigned long rn[], unsigned long lr)
{
    unsigned int *pthis = (unsigned int *)0x20000000;
    printmsg("\r\n");
    if (lr == 0xFFFFFFF1)
    {
        printmsg("\r\nEXC(LR)   = 0x%08x (Handler Mode,Main stack)\r\n", (int)lr);
    }
    else if (lr == 0xFFFFFFF9)
    {
        printmsg("\r\nEXC(LR)   = 0x%08x (Thread Mode,Main stack)\r\n", (int)lr);
    }
    else if (lr == 0xFFFFFFFD)
    {
        printmsg("\r\nEXC(LR)   = 0x%08x (Thread Mode,Process stack)\r\n", (int)lr);
    }
    else
    {
        printmsg("\r\nEXC(LR)   = 0x%08x (other)\r\n", (int)lr);
    }
    printmsg("\r\nLR        = 0x%08x\r\n", (int)rn[5]);
    printmsg("\r\nPC        = 0x%08x\r\n", (int)rn[6]);
    printmsg("\r\nR0        = 0x%08x\r\n", (int)rn[0]);
    printmsg("\r\nR1        = 0x%08x\r\n", (int)rn[1]);
    printmsg("\r\nR2        = 0x%08x\r\n", (int)rn[2]);
    printmsg("\r\nR3        = 0x%08x\r\n", (int)rn[3]);
    printmsg("\r\nR4        = 0x%08x\r\n", pthis[8]);
    printmsg("\r\nR5        = 0x%08x\r\n", pthis[9]);
    printmsg("\r\nR6        = 0x%08x\r\n", pthis[10]);
    printmsg("\r\nR7        = 0x%08x\r\n", pthis[11]);
    printmsg("\r\nR12       = 0x%08x\r\n", (int)rn[4]);
    printmsg("\r\nXPSR      = 0x%08x\r\n", (int)rn[7]);
    printmsg("\r\nAPSR      = 0x%08x\r\n", pthis[0]);
    printmsg("\r\nIAPSR     = 0x%08x\r\n", pthis[1]);
    printmsg("\r\nEAPSR     = 0x%08x\r\n", pthis[2]);
    printmsg("\r\nIPSR      = 0x%08x\r\n", pthis[3]);
    printmsg("\r\nEPSR      = 0x%08x\r\n", pthis[4]);
    printmsg("\r\nIEPSR     = 0x%08x\r\n", pthis[5]);
    printmsg("\r\nPRIMASK   = 0x%08x\r\n", pthis[6]);
    printmsg("\r\nCONTROL   = 0x%08x\r\n", pthis[7]);

    printmsg("\r\nHFSR   = 0x%08x\r\n", (int)HFSR);
    printmsg("\r\nMFSR   = 0x%02x\r\n", MFSR);
    printmsg("\r\nBFSR   = 0x%02x\r\n", BFSR);
    printmsg("\r\nUFSR   = 0x%04x\r\n", UFSR);
}
void IsrHardFault(unsigned long rn[], unsigned long lr)
{
#ifdef _DEBUG_OUT_
    FaultInf(rn, lr);
#endif 

	while (1);
}

写在后面的话

翻开《cortex-M3权威指南》可以找到触发hardFault除了硬Fault外,也可以是总线 fault、存储器管理 fault 以及用法 fault 上访的结果,所以如果要进一步排查发生的错误时,可以进一步排查这几个寄存器地址上的数据:
硬 fault 状态寄存器(地址: 0xE000_ED2C)
在这里插入图片描述

用法 fault 状态寄存器(UFSR),地址:0xE000_ED2A
在这里插入图片描述

存储器管理 fault 状态寄存器(MFSR),地址:0xE000_ED28
在这里插入图片描述

总线 fault 状态寄存器(BFSR),地址:0xE000_ED29
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值