Cortex-M3开发经验:确定发生HardFault的地方

Posted 无痕幽雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cortex-M3开发经验:确定发生HardFault的地方相关的知识,希望对你有一定的参考价值。

Cortex-M3开发经验(二):确定发生HardFault的地方
我们在调试时,最害怕就是出现HardFault错误了。因为我们不知道是从哪个地方跳到这里的?单步调试起来太过于麻烦,特别在代码量大的时候,更是费时间。

那么有没有一种方法,可以快速定位到发生HardFault错误的代码位置(函数)呢?Cortex-M3中断/异常的响应过程我们知道,HardFault实质上是一个中断,中断的过程如下图:
 

在这里我就有一个疑问了:既然FUNC1和IRQ_Handler之间不存在调用关系,那IRQ_Handler运行完之后怎么返回到FUNC1中去的呢?

中断/异常响应原理:当Cortex-M3响应一个中断时,会在它体内奔涌起三股暗流:

1.入栈:吧8个寄存器(R0~R3,R12,LR,PC,xPSR)的值压入栈中;

2.取向量:从向量表中找出对应的中断服务程序入口地址(函数指针的原理);

3.更新PC指针,堆栈指针SP,连接寄存器LR的值。

摘自《ARM Cortex M3权威指南》

明白原理后,就好办了。那么我们在HardFault函数中打一個断点,在他刚进入中断时,内核寄存器的值,就是保存发生中断那一刻的值。我们就可以通过这些值,得到发送错误的位置。





通过响应中断保存的寄存器查找问题

下图是出现错误时状态:

 我们关注SP寄存器的值:0x2000FFAC,我们再查看SP地址附近所对应的值。

addressvalue
0x2000FFAC0x2000FFD0
0x2000FFB00x0000000D
0x2000FFB40x00000000
0x2000FFB80x00000000
0x2000FFBC0x0001F204
0x2000FFC00xA5A5A5A5
0x2000FFC40x0001F239
0x2000FFC80x0001F204
0x2000FFCC0x00000000
0x2000FFD00x8000000C

我们需要的是PC和LR的值,因为这两个寄存器代表的是:PC代表当前执行位置,LR代表返回地址。

从栈空间我们可以得知:PC的值为0x0001F204, LR的值为0x1F239。

怎么找的?
很简单,我们知道进入中断处理函数后,内核依次将xPSR,PC,LR,R12,R3,R2,R1,R0的顺序入栈。(至于为什么是这个顺序,请看Cortex-M3权威指南)。然后我们需要知道栈的操作方式(满减,空减,满增,空增),而Cortex-M3属于满减栈,就是堆栈指针指向最后一个被压入堆栈的32位数值;PUSH压栈时,SP先自减4,再存入新值,POP出栈时相反,先从SP指针处读出上次被压入堆栈的值,SP再自增4。

所以我们要从0x2000FFD0开始看起,PC跟LR是第二跟第三入栈的。所以就是0x2000FFC8和0x2000FFC4对应的值。

发生HardFault时,PC正在执行0x1F204地址的指令,LR是准备返回的地址0x1F239。

怎么看这两个地址所在的函数?
有两个方法:

1.看编译器生成的map文件。

2.使用编译工具链的addr2line,如下:

arm-none-eabi-addr2line 1f204 -f -e xxxx.elf	(烧录文件)
>fp_test	(函数名)
>..../main.c:129	(对应文件及行号)

arm-none-eabi-addr2line 1f239 -f -e xxxx.elf
>test
>..../main.c:142

Cortex-M3开发经验(三):在HardFault中打印栈信息
在《Cortex-M3开发经验(二):确认发生HardFault的地方》中,我们提到如何查找出错地方。但是这有一个问题,就是必须链接调试器。那么在某些情况下,我们无法连接调试器,那么就无法读取到栈信息了吗?我们可以在进入HardFault时,获取栈指针,然后通过串口的方式打印出来吗?

说干就干,有好的想法,也必须有实际的行动验证自己的想法。

如何获取栈指针?
卡住我们的第一个问题就是如何获取栈指针了。就是如何获取SP,MSP(主堆栈指针),PSP(进程堆栈指针)的值了。
 

 在《Cortex-M3权威指南》中,有这么一段话:CM3微控制器内核中共有两个堆栈指针,于是也就是支持两个堆栈,当引用R13(写作SP)时,引用到的是当前正在使用的那一個(MSP或PSP),另一个必须用特殊的指令来访问(MRS和MSR指令)。

 也就是说,我们需要用汇编的指令来获取栈指针。

uint32_t get_msp_addr()

    __asm("mrs r0, msp");
    __asm("bx lr");


uint32_t get_psp_addr()

    __asm("mrs r0, psp");
    __asm("bx lr");


uint32_t get_sp_addr()

    __asm("mov r0, sp");
    __asm("bx lr");

注:每个编译器所支持C嵌入汇编的方式不同,也可能一些编译器不支持__asm指令。



通过栈指针获取内核寄存器的值

uint32_t reg_buff[10];
uint32_t *sp = NULL;

void HardFault_Handler()

    sp = (uint32_t*)get_msp_addr();
    reg_buff[0] = *(sp++);
    reg_buff[1] = *(sp++);
    reg_buff[2] = *(sp++);
    reg_buff[3] = *(sp++);
    reg_buff[4] = *(sp++);
    reg_buff[5] = *(sp++);
    reg_buff[6] = *(sp++);
    reg_buff[7] = *(sp++);
    reg_buff[8] = *(sp++);
    reg_buff[9] = *(sp++);
    while(1)

编译,运行!

结果有点意料之外!

LR的值和PC的值跟我们之前单步调试的不一样!偏移了12个字节。为什么?后面单步看了一下后发现,我们在HardFault中调用了get_msp_addr这个函数,而调用函数就意味着使用栈空间。如果我把reg_buff放到HardFault中,这样就不止偏移12个字节了!

有没有更好的方法啊!?

我们在进入HardFault_Handler函数之前就获取SP指针的值,并作为参数传入到HardFault_Handler中不就可以了吗?

谁在调用中断处理函数?
要解决上面的问题,我们就需要知道内核在哪里调用中断函数的,这样我们才能修改对应的中断处理函数,使其可以接收参数。

《Cortex-M3开发经验(二):确认发生HardFault的地方》中,我们提到过,在发生中断/异常时,内核会去中断向量表中找到对应的中断,找到中断的入口地址。那么我们就看看中断向量表在哪?

最终,在startup_xxx.S文件中找到了向量表的定义。我们也找到了HardFault_Handler的定义
 

.weak	HardFault_Handler
.type	HardFault_Handler, %function

HardFault_Handler:
	B .

虽然可能不了解汇编,不知道什么意思,但也能猜测出大概的意思,也可能查资料。发现B是跳转指令,这应该就是跳转到同名的C函数中。那么我们可以修改为:

这样就吧LR的值和SP的值传入到hardfault_handler函数中去了。

编译,运行!

这次的结果就是我们想要的了。

1、这是一个启动文件,里面包含了该芯片启动需要的一些过程。 ↩︎

2、R0~R3寄存器保存的是函数调用时所传入的参数,同时也可作为函数返回值。hardfault_handler定义如下:void hardfault_handler(uint32_t lr, uint32_t sp) ↩︎
————————————————
版权声明:本文为CSDN博主「食梦少年」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/small_prince_/article/details/92718388

以上是关于Cortex-M3开发经验:确定发生HardFault的地方的主要内容,如果未能解决你的问题,请参考以下文章

ARM中Cortex-A8,Cortex-M0,Cortex-M3 他们的区别在哪?

Cortex-M3&Cortex-M4-嵌入式软件开发

Cortex-M3 SysTick 双中断

Arm Cortex-M4 LDRD 指令导致硬故障

ARM Cortex-M3/M4 启动分析

Cortex-M3之STM32嵌入式系统设计的内容简介