ES32F36xx芯片发生HardFault异常时的函数调用关系及问题定位

cathy的头像
cathy 发布于:周二, 06/20/2023 - 09:15 ,关键词:

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)。

围观 119