HardFault

1、引言

Cortex-M3微控制器因其功能强大、性价比高以及易用性好,在嵌入式体系结构中得到了广泛应用。然而,在实际开发过程中,如果程序很大或运行很久后可能会遇到HardFault异常。为了快速有效地解决HardFault异常,本文将讨论定位HardFault问题的方法。

2、常见引发HardFault原因

1)访问非法内存地址:编程中处理指针时可能导致访问未定义的内存地址,特别是在数组越界、非法指针解引用等情况下。

2)叠栈溢出:程序运行过程中如果栈溢出,会导致栈上数据错误,从而引发异常。

3)寄存器未正确初始化:寄存器配置和使用不正确,可能导致硬件异常。

4)除0运算:当程序试图执行除0运算时,可能发生硬件故障异常。

5)总线错误:外设总线通信发生错误,可能引起硬件故障。

3、软件触发HardFault异常

ES32F36xx是Cortex-M3内核,当被除数为0时,硬件将触发一个异常,导致系统进入Hard Fault异常状态。这种情况下,处理器不能正常运行,并且不能恢复,直到硬件或软件采取措施使得系统回到正常状态。在ES32_SDK中,ES32F36xx 的KEY_LED_ADC例程中增加如下程序:

// B函数
void B_Function(void)
{
    printf_e("Enter B function\r\n");
    C_Function(0);
    printf_e("Exit B function\r\n");
}
// A函数
void A_Function(void)
{
    printf_e("Enter A function\r\n");
    B_Function();
    printf_e("Exit A function\r\n");
}
// C函数
int C_Function(int val)
{
    return 100/val;
    }
// D函数
void D_Function(void)
{
    printf_e("Enter D function\r\n");
    C_Function(1);
    printf_e("Exit D function\r\n");
}
// TestDebug函数
void TestDebug(void)
{
    // 使能除0异常
volatile int *CCR = (volatile int *)0xE000ED14;
    *CCR |= (1 << 4);
    
    A_Function();
    D_Function();
}
// 主函数
int main(void)
{   
    uart_stdio_init();
    
    TestDebug();
    while(1);
}

如图 1所示,工程文件中,User选项中增加“fromelf -a -c --output=all.dis  .\obj\out.axf“,生成汇编文件。

1.png

图1 MDK工程生成汇编文件配置

4、HardFault异常函数调用关系及问题定位

上述程序运行过程中会发生hardfault异常,发生hardfault异常瞬间,程序立刻中止运行,硬件自动保存“调用者保存寄存器“的值到栈,同时跳转到异常向量表执行异常处理函数。如图 2所示为发生异常瞬间,硬件自动保存xPSR、ReturnAddress、LR、R12、R3、R2、R1及R0寄存器。

2.png

图2 处理器进入异常时的栈帧

硬件仅保存了部分寄存器,为了保存发生异常瞬间所有寄存器值,程序跳转到中断向量处需要执行如下的汇编代码:

;get current context
    TST          lr, #0x04        ;if (!EXC_RETURN[2])
    ITE          EQ
    ;[2]=0 ==> Z=1, get fault context from handler
MRSEQ        r0, msp          
    ;[2]=1 ==> Z=0, get fault context from thread
MRSNE        r0, psp         
    
    STMFD        r0!, {r4 - r11}  ; push r4 - r11 register
    STMFD        r0!, {lr}         ; 将EXC_RETURN值压栈
    
    TST          lr, #0x04        ;if (!EXC_RETURN[2])
    ITE          EQ                 
; R0为栈值,作为hw_hardfault_exception函数首个参数
MSREQ        msp, r0
;[2]=1 ==> Z=0, get fault context from thread     
    MSRNE        psp, r0          
    
;再次将EXC_RETURN值压栈
    PUSH         {lr}              
    ;跳转至hw_hardfault_exception函数    
BL           hw_hardfault_exception 
    POP          {lr}
    ORR           lr, lr, #0x04
    BX            lr
    ENDP

上述汇编代码的主要功能是获取栈(SP)地址,并将R4 ~ R11压栈,跳转执行hw_hardfault_exception函数。压栈后栈中的数据情况如图 3所示:

3.png

图3 hardfault异常瞬间栈中完整寄存器

Return Address是发生异常指令点,LR是发生异常指令所在函数的下一条指令地址。在hw_hardfault_exception函数中将栈中的寄存器值都通过串口打印出来。

void hw_hardfault_exception(struct exception_info *exception_info)
{
    uint32_t *app_sp = NULL;
int i = 0;
/*sp指向发生hardfault前栈地址*/
    app_sp = (uint32_t *)(exception_info + 1);  /* context + 16*4 */
    
    printf_e("psr: 0x%08x\r\n", exception_info->psr);
    printf_e("r00: 0x%08x\r\n", exception_info->r0);
    printf_e("r01: 0x%08x\r\n", exception_info->r1);
    printf_e("r02: 0x%08x\r\n", exception_info->r2);
    printf_e("r03: 0x%08x\r\n", exception_info->r3);
    printf_e("r04: 0x%08x\r\n", exception_info->r4);
    printf_e("r05: 0x%08x\r\n", exception_info->r5);
    printf_e("r06: 0x%08x\r\n", exception_info->r6);
    printf_e("r07: 0x%08x\r\n", exception_info->r7);
    printf_e("r08: 0x%08x\r\n", exception_info->r8);
    printf_e("r09: 0x%08x\r\n", exception_info->r9);
    printf_e("r10: 0x%08x\r\n", exception_info->r10);
    printf_e("r11: 0x%08x\r\n", exception_info->r11);
    printf_e("r12: 0x%08x\r\n", exception_info->r12);
    printf_e(" lr: 0x%08x\r\n", exception_info->lr);
    printf_e(" pc: 0x%08x\r\n", exception_info->pc);
    
    printf_e("stacks: \r\n");
    
    for (i = 0; i < 1024; ++i)
    {
        printf_e("%08x ", *app_sp);
        app_sp++;
        ++i;
        if (i % 16 == 0)
            printf_e("\r\n");
    }
    printf_e("\r\n");
    while(1);
}

在hw_hardfault_handler函数中打印出了所有相关寄存器,如图 4所示:

4.png

图4 发生异常时栈数据

从打出来的返回地址值(PC)为0x00000644,在生成的all.dis汇编文件搜索该地址,如图 5所示,该地址是C_Function函数中的一个除法指令,R0寄存器值除以R1寄存器值,并将结果存放R0中。R0为0x64,确认R1寄存器值即可。

5.png

图5 发生hardfault异常瞬间执行的指令

图 4中,LR的值为0x0000060f,all.dis无法搜索到该地址。由于Cortex-M3使用的是Thumb指令集,bit0置位指示该地址地址指令是Thumb指令。bit0复位,搜索0x0000060e,如图 6所示,该地址在B_Function函数中。B_Function函数调用了C_Function函数,R0为传递的参数0。由此可知,图 5中,R1的除数值0,故程序会发生hardfault异常。

6.png

图6 B_Function函数的汇编代码

图 6中,调用C_Function函数前,R4和LR寄存器被压入了栈中。即图 7中,LR的值为0x000005D1,R4的值为0xe000ed14。

7.png

图 7 B_Function函数压栈值

如图 8所示,在all.dis文件搜索0x000005D0地址在A_Function函数中,在执行A_Function函数前,对R4和LR进行了压栈。A_Function函数调用了B_Function函数。

8.png

图 8 A_Function函数汇编代码

如图 9所示,A_Function函数压入的R4值为0xe000ed14,LR值为0x000006a1。

9.png

图 9 A_Function函数压栈值

如图 10,在all.dis文件中,搜索0x000006a0,发现该地址在TestDebug函数中,且该函数将R4和LR压入栈中。

10.png

图 10 TestDebug函数汇编代码

如图 11所示,A_Function函数压入的R4值为0xe0001c18,LR值为0x00001ab9。

11.png

图 11 TestDebug函数压栈值

如图 12所示,在all.dis文件中,搜索0x00001ab8,该地址在main函数中。

12.png

图 12 main函数的汇编代码

至此,如图 13所示为发生hardfault异常时函数的调用关系,在C_Function函数中,被除数为0是导致进入hardfault异常的原因。

13.png

图 13 发生hardfault时的函数调用关系

来源:东软载波微电子

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 283

1.概述

客户在使用 STM32G070 的时候,KEIL MDK 为编译工具,当编译优化选项设置为Level0 的时候,程序会出现 Hard Fault 异常,而当编译优化选项设置为 Level1 的时候,则程序运行正常。

表面上看,这似乎是 KEIL MDK 的问题,通过分析,导致这个问题的本质原因是内存地址没有对齐引起的,下面章节将详细分析该问题的来龙去脉以及解决方法。

2.问题描述与分析

根据客户的反馈,引起问题的代码很简单,客户定义了几个全局数组,在主程序中访问这几个数组就会出现 Hard Fault 异常,参考代码如下。

1.png

把客户提供的代码片段移植到 NUCLEO-G070RB 开发板上,问题很容易就复现了,代码本身功能简单,写法上也没有错误,所以从代码片段本身上看,无法确定问题出在哪里,通过 KEIL 调试器,在汇编窗口单步调试下,最终发现导致 HardFault 异常的语句为下图所示语句。

2.png

根据单步调试得知出现问题的语句为 LDR 指令,参考 Cortex M0 编程手册 PM0223 得知 LDR 指令的作用是从内存地址中加载一个 WORD 数据到目的寄存器 Rt 中,其中内存地址根据 Rn 或者 SP 寄存器的值以及立即数 imm 得到。

3.png

根据指令的描述,使用 LDR 指令的时候,通过 Rn 和 imm 计算得到的内存地址必须是读取字节数的倍数,LDR 每次读取一个 WORD,所以使用 LDR 指令时,内存地址必须 4字节对齐。如果地址没有对齐,则会导致 HardFault 异常。

结合 LDR 指令的描述,在调试状态下,通过查看寄存器值,图 2 出错语句中根据 Rn和 imm 计算得到的内存地址为 R0=0x2000000B,imm=4 所以内存地址为 0x2000000F,很显然这个地址不是 4 字节对齐的。

4.png

而当我们改变编译优化选项为 Level1 时,得到的内存地址为R0=0x20000000,imm=0x04 显然这个地址是按照 4 字节对齐的,所以这种情况下是不会出现 HardFault 异常的,印证了客户的问题现象。

5.png

3.问题解决

通过上一节的分析,明确了导致该问题的本质原因是内存地址没有对齐,这个内存地址实际上是代码中定义的全局变量 g_curPlaySound_app 指向的地址,也就是全局数组变量 SoundFile 的地址,在编译器不同的优化选项下,分配给 SoundFile 变量的地址是不一样的,在本案例中,编译优化选项 Level0 条件下,SoundFile 分配的地址没有按照WORD 对齐,而在优化选项 Level1 条件下,SoundFile 分配的地址是 WORD 对齐,所以在两种优化选项下,出现了不一样的运行结果。

所以要保证程序不出错,当通过指针访问变量的时候,要确保指针指向的地址是 4 字节对齐的,在 Keil 环境下,可以通过__attribute__((aligned (4))) 关键字实现,如下图所示,通过该关键字,对齐了地址,也就不会出现 HardFault 异常了。

图6 确保地址对齐

6.png

4.总结

地址未对齐是嵌入式系统中容易忽视的一个细节,忽视这点往往会导致一些奇怪的问题,所以在开发过程中,注意这些细节还是很有必要的。

参考文献:PM0223 Programming maual

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 279

TrustZone

恩智浦基于Cortex M33内核的MCU,LPC55S6x/LPC55S1x, RT600/RT500等产品提供了对TrustZone的支持,并在SOC上提供了安全AHB控制器等功能,旨在帮助客户完成良好的安全隔离,并建立可信执行环境。

在产品设计之初,我们就要有一个基本的“隔离”的概念。

需要考虑的问题大致有以下两个方面:

  • 产品中的哪些功能和模块应该放在安全区?(这部分代码往往是核心且精简,且经过安全审查的,安全区的内容不允许非安全区的代码触碰);
  • 哪些功能应该放在非安全区?(这样放在非安全区中的代码,即使出现安全漏洞(例如栈溢出漏洞等等)后被攻击,MCU的安全区中的资源和外设也无法被攻击者利用)。

令人头疼的HardFault

对于MCU工程师,TrustZone是一个比较新的技术,在开发调试过程中,由于TrustZone配置问题,时常遇到各种意料之外的HardFault,我们也经常调侃,这个TrustZone也太安全了,连我们自己都进不去。

其实这种问题往往是由于在开发阶段,我们实际的行为和我们对TrustZone的划分不一致。

平常我们认为正常的行为与操作,对于TrustZone来说,已经越界违规了,这就会使TruztZone触发HardFault,并阻止违规行为。

在这种情况下,我们就需要找到导致HardFault的原因,并调整对于TrustZone的配置。

注:TrustZone相关的故障会触发SecureFault异常。在芯片上电后的默认情况下,SecureFault异常并没有被使能,因此,此异常会被“升级”为HardFault。在通常情况下,建议使用默认的设置,因为HardFault拥有更高的中断优先级,可以使故障在第一时间被响应。

获取违规操作的蛛丝马迹

Cortex M33内核本身在SAU中提供了两个寄存器:SFSR(Secure FaultStatus Register,安全故障状态寄存器)和SFAR(Secure Fault Address Register,安全故障地址寄存器)。

“调试TrustZone时,如何处理HardFault?"

SFSR寄存器用于指示出现错误的类型,例如非安全区试图访问安全区,从安全区到非安全区的非法跳转等。

SFAR寄存器用于指示出现错误的内存地址。

看起来这两个寄存器就足以帮助我们查到问题的根源了。但是,有时候,我们从这两个寄存器拿到了错误的原因和地址,仔细检查后发现无论是安全区还是非安全区的程序,都没有显式的访问这个地址。

这是由于MCU系统愈发复杂,总线上除了M33内核之外,还有许许多多的其他的外设,例如DMA,USB等等。肇事者不一定是M33内核,还有可能是其他的外设,例如DMA。

下面是一种常见的事故:被划分为非安全的DMA在工作中访问了安全区的地址或外设,违反了TrustZone的配置,造成了HardFault。

恩智浦的MCU在SOC层面提供了一个安全AHB控制器,能够帮我们侦查肇事现场,找出肇事者的蛛丝马迹。

其实原理很简单,安全AHB控制器提供了三个寄存器,SEC_VIO_INFO_VALID,SEC_VIO_MISC_INFO和SEC_VIO_ADDR。

SEC_VIO_INFO_VALID用来指示肇事现场,这个寄存器中存储了肇事现场的AHB 的端口号(port number),端口号与外设的对应关系参见用户手册的”Memory map overview”章节。

每一个AHB端口都相应有一个SEC_VIO_MISC_INFO寄存器,用来指示肇事的信息,例如违规操作是读操作引起的还是写操作引起的等等,最重要的是会指出肇事者的身份:

“调试TrustZone时,如何处理HardFault?"

每个AHB端口还有一个SEC_VIO_ADDR,用来指示肇事现场的地址。

有了上述信息,我们就捉到了真正的肇事者。在开发阶段,我们就可以利用这些信息去调整我们的TrustZone的配置。

更进一步

当然,上述功能不只能在开发阶段帮我们排查HardFault。我们也可以利用这个机制在产品出厂之后为我们提供防御措施。

产品出厂后,当我们检测到由TrustZone产生的HardFault的原因之后,可以将其记录,以便于后续分析。如果设备有联网能力,可以将其传输至服务器。利用这些信息我们可以发现产品的哪些模块受到了攻击,方便我们后续针对性地进行OTA升级。云端和设备本地也可在此时检测镜像以及存储介质的完整性,以检查程序和存储介质是否被恶意篡改。

小结

综上所述,利用SAU和安全AHB控制器调查HardFault的方法并不复杂,NXP的MCUXpresso SDK也提供了一个完整的demo。

以LPC55S69为例,demo的路径如下:SDK\boards\lpcxpresso55s69\trustzone_examples\secure_faults。

这个demo中,人为制造了几种TrustZone触发HardFault的案例,在产生HardFault后,使用上文描述的方法,处理相关寄存器并打印事故信息。

通常来说,在开发阶段,我们可以参考demo中HardFault的处理代码,按需移植到自己的工程中。

来源:恩智浦MCU加油站
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 92

Cortex-M0 处理器内核异常中断简介

在Cortex‐M0内核上搭载了一个异常响应系统,支持众多的系统异常和外部中断。其中,编号为1-15的对应系统异常,大于等于16的则全是外部中断,优先级的数值越小,则优先级越高。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。

因为芯片设计可以修改内核的硬件描述源代码,所以做成芯片后,支持的中断源数目常常不到240 个,并且优先级的位数也由芯片厂商最终决定。

“HardFault定位方法和步骤"

类型编号为 1-15 对应系统异常,在《ARM Cortex-M0权威指南》一书中的第12章节<错误处理>章节中有描述:对于ARM处理器,架构采用错误异常的机制来检测问题,当一个程序产生了错误并且被处理器检测到时,异常中断会被触发,并且核心会跳转到相应的异常终端处理函数执行,错误异常的中断有如下:

Reset

在上下电、NRST拉低、看门狗复位或软复位时启动复位。当复位产生时,处理器停止一切操作,并将复位当做一种特殊形式的异常来执行,进入到对应的中断函数。当复位撤销时,从向量表中复位项提供的地址处重新启动执行,芯片重新开始执行。

NMI

不可屏蔽中断(NMI),可以由外设产生,也可以由软件来触发。这是除复位之外优先级最高的异常中断,NMI永远使能,优先级固定为-2,CSS的时钟安全机制使能判定时钟失效后就会进入到该中断。NMI 不能:

1、被屏蔽,它的执行也不能被其他任何异常中止;

2、被除复位之外的任何异常抢占。

HardFault

HardFault 是由于在正常操作过程中或在异常处理过程中出现错误而出现的一个异常。HardFault的优先级固定为-1,表明它的优先级要高于任何优先级可配置的异常。

SVCall

管理程序调用(SVC)异常是一个由SVC指令触发的异常。在OS环境下,应用程序可以使用 SVC指令来访问OS内核函数和器件驱动。

PendSV

PendSV是一个中断驱动的系统级服务请求。在OS环境下,当没有其它异常有效时,使用 PendSV 来进行任务切换。

SysTick

SysTick是一个系统定时器到达零时产生的异常,软件也可以产生一个SysTick异常。在OS环境下,处理器可以将这个异常用作系统节拍。

中断(IRQ)

中断(或 IRQ)是外设发出的一个异常,或者由软件请求产生的一个异常。在系统中,外设使用中断来与处理器通信,在中断函数中可以查询和清除标志操作。

HardFault异常

HardFault (硬件错误,也有译为硬错误)是在MCU上编写程序中所产生的错误,硬件错误处理几乎是最高优先级,它的优先级为-1,只有复位和不可屏蔽中断(NMI)可以对其进行抢占。当它发生时,表示处理器出现了问题,需要采取紧急修复措施。

造成HardFault错误的可能原因较多,如何在代码量较大的情况下,快速定位造成的HardFault的问题代码,就成为比较关键的问题。

本文将以MM32F0130系列MCU为例,Keil-MDK开发环境,总结HardFault的调试、定位方法。在其它Cortex-M0 (M3,M4)内核处理器,和其它开发环境下,也可作为参考。

2.1、可能的原因

《ARM Cortex-M0权威指南》中提到,关于 Cortex M0内核主要有以下几点引起HardFault的原因:

  • 非法存储器访问

  • 非对齐数据访问

  • 从总线返回错误

  • 异常处理中的栈被破坏

  • 程序在某些 C 函数中崩溃

  • 意外地试图切换至 ARM 状态

  • 在错误的优先级上执行系统服务调用指令(SVC)

从软件角度,产生HardFault的可能原因有:

  • 数组越界

  • 野指针

  • 未初始化硬件却开始操作,或无中断服务函数等

  • 任务堆栈溢出

  • 中断服务函数设置错误

  • 时钟异常

注意:只有复位和NMI可以抢占优先级固定的 HardFault 处理程序。HardFault可以抢占除复位、NMI 或其它硬故障之外的任何异常。

2.2、可能出现的异常

如果在执行NMI或HardFault处理程序时,或者在一个使用MSP的异常返回时出栈的却是PSR的时候系统产生一个总线错误,处理器进入一个锁定状态。当处理器处于锁定状态时,它不执行任何指令。处理器保持处于锁定状态,直到下面任何一种情况出现:

  • 出现复位

  • 调试器将锁定状态终止,出现中止仿真的现象

  • 出现一个NMI,以及当前的锁定处于HardFault处理程序中

注意:如果锁定状态出现在NMI处理程序中,后面的NMI就无法使处理器离开锁定状态。

在应用程序中,处理器处于锁定状态,会一直在void HardFault_Handler(void)函数中执行。

void HardFault_Handler(void)
{
  /* Go to infinite loop when Hard Fault exception occurs */
  while (1)
  {
  }
}

下面将在MM32F0130上运行的数组越界代码为例,具体阐述定位步骤:

void StackTest(void)
{
    int data[3],i;
    for(i=0; i<10000; i++)
    {
    data[i]=1;
    }
}

查找HardFault方法和步骤

实际环境中,由于测试高压等产品常常无法连接调试器,故需要代码来定位目标语句地址,并通过一定手段保存:

在MM32F0130中,需先修改启动文件startup_mm32f013x.s:

HardFault_Handler\
            PROC                
            IMPORT  hard_fault_handler_c;函数声明
            MOVS r0, #4                   ;判断主栈指针还是进程栈指针
            MOV r1, LR
            TST r0, r1
            BEQ stacking_used_MSP        ;如果是主栈指针
            MRS R0, PSP              ;否则是进程栈指针,把进程栈指针地址付给 R0
            B get_LR_and_branch          ;跳转到 HardFault 中断程序
            stacking_used_MSP
            MRS R0, MSP                   ;把主栈指针地址赋给 R0
            get_LR_and_branch
            MOV R1, LR

            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; //压栈的 r0
    unsigned int stacked_r1; //压栈的 r1
    unsigned int stacked_r2; //压栈的 r2
    unsigned int stacked_r3; //压栈的 r3
    unsigned int stacked_r12; //压栈的 r12
    unsigned int stacked_lr; //压栈的 lr
    unsigned int stacked_pc; //压栈的 pc
    unsigned int stacked_psr; //压栈的 psr

    stacked_r0 = ((unsigned int) hardfault_args[0]);
    stacked_r1 = ((unsigned int) hardfault_args[1]);
    stacked_r2 = ((unsigned int) hardfault_args[2]);
    stacked_r3 = ((unsigned int) hardfault_args[3]);
    stacked_r12 = ((unsigned int)hardfault_args[4]);
    stacked_lr = ((unsigned int) hardfault_args[5]);
    stacked_pc = ((unsigned int) hardfault_args[6]);
    stacked_psr = ((unsigned int) hardfault_args[7]);

    while(1)
    {
        printf("[Hard fault handler]\r\n");
        printf("R0 = %x\r\n", stacked_r0);
        printf("R1 = %x\r\n", stacked_r1);
        printf("R2 = %x\r\n", stacked_r2);
        printf("R3 = %x\r\n", stacked_r3);
        printf("R12 = %x\r\n", stacked_r12);
        printf("Stacked LR = %x\r\n", stacked_lr);
        printf("Stacked PC = %x\r\n", stacked_pc);
        printf("Stacked PSR = %x\r\n", stacked_psr);
        printf("SCB_SHCSR=%x\r\n",SCB->SHCSR);
        printf("Current LR = %x\r\n", lr_value);
    }
}

处理器进入到HardFault,将R0~R3、R12、LR、PC信息通过串口打印,根据寄存器信息排查问题代码。

当处理器处理异常时,除非异常是一个末尾连锁异常或迟来的异常,否则,处理器把信息都压入到当前堆栈中入栈(stacking),8个数据字的结构被称为栈帧(stack frame),栈按照双字地址对齐方式。

“HardFault定位方法和步骤"

入栈后,堆栈指针立刻指向栈帧的最低地址单元。栈包含返回地址,这是被中止的程序中下条指令的地址。这个值在异常返回时返还给 PC,使被中止的程序恢复执行。

如下图连接仿真器查看汇编的地址可以找到是程序问题,根据PC指针地址,在程序生成的.map中查找出问题函数。

“HardFault定位方法和步骤"

处理建议

根据上述的定位手段可以查找是哪一种情况造成的异常,在编程过程中需要避免出现上述异常情况,但是在恶劣复杂的环境下,可能会小概率触发HardFault中断,可以在函数中添加复位或者跳转指令,具体的实现方式需根据应用和使用环境来评估。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 1949

作者:armink
编辑整理:strongerHuang

该库采用采用 MIT 开源协议,开源地址:
https://github.com/armink/CmBacktrace

一、CmBacktrace 是什么

CmBacktrace (Cortex Microcontroller Backtrace)是一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库。主要特性如下:

1. 支持的错误包括

  • 断言(assert)
  • 故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault)

2. 故障原因自动诊断

可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器;

3. 输出错误现场的函数调用栈(需配合 addr2line 工具进行精确定位),还原发生错误时的现场信息,定位问题代码位置、逻辑更加快捷、精准。也可以在正常状态下使用该库,获取当前的函数调用栈;

4.支持 裸机 及以下操作系统平台:

  • RT-Thread
  • UCOS
  • FreeRTOS(需修改源码)

5. 根据错误现场状态,输出对应的 线程栈 或 C 主栈;

6. 故障诊断信息支持多国语言(目前:简体中文、英文);

7. 适配 Cortex-M0/M3/M4/M7 MCU;

8. 支持 IAR、KEIL、GCC 编译器;

二、为什么选择 CmBacktrace

入门新人:对于从 C51 、MSP430 等简单单片机转而使用更加复杂的 ARM 新人来说,时不时出现的 "hard falut" 死机会让新人瞬间懵掉。定位错误的方法也往往是连接上仿真器,一步步 F10/F11 单步,定位到具体的错误代码,再去猜测、排除、推敲错误原因,这种过程十分痛苦。

熟练老手:慢慢的大家知道可以通过故障寄存器信息来定位故障原因及故障代码地址,虽然这样能解决一小部分问题,但是重复的、繁琐的分析过程也会耽误很多时间。而且对于一些复杂问题,只依靠代码地址是无法解决的,必须得还原错误现场的函数调用逻辑关系。虽然连接仿真器可以查看到的函数调用栈,但故障状态下是无法显示的,所以还是得一步步 F10/F11 单步去定位错误代码的位置。

另外,还有两种场景,
① 很多产品真机调试时必须断开仿真器
② 问题确实存在,但是极难被重现

所以定位这类问题就显得难上加难。

使用本库:上述所有问题都迎刃而解,可以将错误信息输出到控制台上,还可以将错误信息使用 EasyFlash 的 Log 功能保存至 Flash 中,设备死机后重启依然能够读取上次的错误信息。CmBacktrace 输出的信息包括函数调用栈、故障诊断结果、堆栈、故障寄存器及产品固件信息,极大的提升了错误定位的效率及准确性。

俗话说,工欲善其事,必先利其器。所以有时候做事效率低的原因也许是,你会用的工具种类太少。

三、CmBacktrace 如何使用

本文就简单演示一下,演示分如下几个步骤:

1、制造除零异常(IAR 工程,点击查看源码)

2、查看错误诊断信息

3、查看函数调用栈基本信息

4、通过命令行工具进入项目工程存放可执行文件的路径

5、使用 addr2line 命令,查看函数调用栈详细信息,并定位错误代码






具体的使用方法,请见原始说明文档,里面描述了很多细节内容。

开源地址:
https://github.com/armink/CmBacktrace

免责声明:本文素材来源网络,版权归原作者所有。

围观 83
订阅 RSS - HardFault