概述
在日常的嵌入式的开发过程中,往往因为自己的不小心(比如指针指向未进行赋值便操作该指针)而触发硬件错误,而这类问题的出现又比较隐蔽,所以排查起来比较困难。本文以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