手上起小水泡很痒Cortex-M处理器hardfault定位⽅法和步骤(基于Keilmdk)⼀. 问题的产⽣
Hard fault (硬错误,也有译为硬件错误的)是在STM32上编写程序中所产⽣的错误,造成Hard Fault错误的可能原因较多,排除硬件问题,如何在代码量较⼤的情况下,快速定位造成的hardfault的问题代码,就成为⽐较关键的问题。
本⽂将基于STM32处理器(stm32f091),keil-MDK开发环境,总结hardfault的调试定位⽅法。在其他Cortex-M0 (m3,m4)内核处理器,和其他开发环境下,也可作为参考。
⼆. Cortex-M 处理器内核异常中断简介
无何有之乡1)错误种类
对于Cortex-M内核,架构采⽤错误异常的机制来检测问题,当核⼼检测到⼀个错误时,异常中断会被触发,并且核⼼会跳转到相应的异常终端处理函数执⾏,错误异常的终端分为以下四种:
HardFault
MemManage
BusFault
UsageFault
其中hardfault为最常见的错误类型,并且,在没有开启其他异常处理的情况下,默认进⼊hardfault异常中断处理函数:
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
2) 可能的原因
从软件⾓度,产⽣hardfault的可能原因有:
(1) 数组越界
(2)野指针
(3)未初始化硬件却开始操作,或⽆中断服务函数等
(4)任务堆栈溢出
《ARM Cortex-M0权威指南》中提到,关于 Cortex M0+内核主要有以下⼏点引起 HardFault 的原因:
⾮法存储器访问
⾮对齐数据访问
从总线返回错误
异常处理中的栈被破坏
程序在某些 C 函数中崩溃
意外地试图切换⾄ ARM 状态
在错误的优先级上执⾏系统服务调⽤指令(SVC)
下⾯将以⼀个stm32f091上运⾏的数组越界代码为例,具体阐述定位步骤:
产⽣越界的代码段:
void StackFlow(void)
{
int a[3],i;
for(i=0; i<10000; i++)
{
a[i]=1;
}
}
三. ⼈⼯查找hardfault ⽅法和步骤
⽅法1.查看寄存器
1)查看fault种类
通过菜单栏Peripherals >Core Peripherals >Fault Reports打开fault reports
直二面角
2)调试定位步骤
查看fault种类有时可能对解决问题并没有直接帮助,关键是如何定位在进⼊异常中断前执⾏的代码段,步骤如下:a. 确定当前使⽤堆栈是MSP还是PSP
异常发⽣后会把进⼊异常前的 R0-R3,R12, LR, PC,PSR 寄存器值栈⼊ Main Stack 或Process Stack(取决于异常发⽣时使⽤的哪个栈)。 进⼊异常后链接寄存器 LR 中存放异常返回值 EXC_RETURN, 如果其 bit 2=0 那么⽤的就是 Main Stack,如果 bit 2=1,那么⽤的就是 Process Stack。
由下图可以看出,当前使⽤的堆栈为PSP
b.找到异常发⽣代码地址
在memory中,定位到堆栈地址:0x200020E0,依据:R0~R3、R12、LR、PC、XPRS 顺序,找到LR的值,即第6个寄存器值
LR = 0x0800632B
PC = 0x08006300
c.Disasmbly中,查找定位代码
在反汇编窗⼝中点击右键,选中show disasmbly at address 。
输⼊LR地址:为发⽣异常后调⽤的下⼀条指令的地址,可看到发⽣异常的为StackFlow()函数
输⼊PC地址:可以定位到发⽣异常的调⽤语句
⽅法2:调试步骤
在仿真状态下,调出Call Stack Window,可直接跳转到调⽤代码
四. 程序查找hardfault ⽅法和步骤
实际环境中,由于测试时产品常常⽆法连接调试器,故需要代码来定位⽬标语句地址,并通过⼀定⼿段保存:在stm32中,需先修改启动⽂件startup_stm32f091xc.s:
HardFault_Handler\
PROC
MOVS r0, #4
MOV r1, LR
TST r0, r1
BEQ stacking_ud_MSP
MRS R0, PSP
B get_LR_and_branch
民族风歌曲
stacking_ud_MSP
MRS R0, MSP
get_LR_and_branch
MOV R1, LR
IMPORT hard_fault_handler_c
BL hard_fault_handler_c
ENDP
该段代码会判断当前堆栈使⽤的是MSP或PSP,然后将堆栈参数传递给hard_fault_handler_c函数,该函数定义如下:
void hard_fault_handler_c(unsigned int * hardfault_args, unsigned lr_value)
{
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]);
while(1)
{
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 ("Stacked LR = %x\n", stacked_lr);
printf ("Stacked PC = %x\n", stacked_pc);
printf ("Stacked PSR = %x\n", stacked_psr);
printf ("Current LR = %x\n", lr_value);
for(int i = 10000;i>0;i--)钢琴简谱
for(int j = 1000;j>0;j--);
}
/
360推荐/while(1); // endless loop
}
代码跑死后,会将R0~R3、R12、LR、PC信息通过串⼝打印,之后根据寄存器信息排查问题代码即可~
参考资料:
(9)《ARM Cortex-M0权威指南》Joph Yiu 著,宋岩 译