【RT-Thread 系统异常入门及渐进 3 - Cortex-m7 异常处理及 hardfault 处理分析】

文章目录

    • 1.1 异常处理模型
      • 1.1.1 异常升级
      • 1.1.2 HardFault Handler
      • 1.1.3 中断入栈
      • 1.1.4 异常栈帧
      • 1.1.5 异常信息描述结构
      • 1.1.6 hard_fault_trace 实现
      • 1.1.7 cm_backtrace注册流程

1.1 异常处理模型

1.1.1 异常升级

当前在Cortex-M7上为系统异常留了16个中断向量的位置,常见的系统异常有MemMange Fault/Usage Fault/Bus Fault, 如果这3个系统异常没有进行enable操作,那么发生这几个系统异常的时候会升级为 HardFault 异常。

rt-thread/rt-thread/bsp/schan/demo/common/startup.s 这里定义了 hardfault 中断处理的若函数。

在进入异常的时候 R0/R1/R2/R3/R12/LR/PC/PSR会自动硬件入栈。

1.1.2 HardFault Handler

rtos/rt-thread/rt-thread/libcpu/arm/cortex-m7/context_gcc.S 中实现HardFault_Handler:

.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
   /* get current context */
    MRS     r0, msp            :(1-1)
    TST     lr, #0x04          :(1-2)
    BEQ     _get_sp_done       :(1-3)
    MRS     r0, psp            :(1-4)
_get_sp_done:                  :(1-5)

    STMFD   r0!, {r4 - r11}                     :(2-1)
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
	/*push exec_return register*/
    STMFD   r0!, {lr}                           :(2-2)  
#endif
    STMFD   r0!, {lr}                           :(2-3)

    TST     lr, #0x04                 :(3-1)
    BEQ     _update_msp               :(3-2)
    MSR     psp, r0                   :(3-3)
    B       _update_done              :(3-4)
    MSR     msp, r0                   :(3-5)   //读取栈顶到r0
_update_done:
    PUSH    {LR}                         :(4-1)
    BL      rt_hw_hard_fault_exception   :(4-2) //已r0的值作为参数
    POP     {LR}                         :(4-3)
    ORR     lr, lr, #0x04                :(5-1)
    BX      lr                           :(5-2)

(1-1) 到 (1-5)概括为:判断EXC_RETURN[2] 是否为0,用来判断进入hardfault之前使用的是MSP还是PSP,因为在进入异常服务程序后,LR的值会被自动更新为特殊的EXC_RETURN,当EXC_RETURN[2]为 0 时异常返回后从主堆栈做出栈操作(因为进入异常之前使用的是MSP),返回后使用MSP,当 EXC_REUTURN[2]为1时,异常返回后从进程栈做出栈操作(因为进入异常之前使用的是PSP),返回后使用PSP

关于STDF Rn{!}, {reglist}{^}
Rn为基础寄存器,装有传递数据的初始地址,Rn不可以为R15;后缀 “!" 表示最后地址写回Rn中;“^” 后缀表示不允许在用户模式下使用,如果Rn寄存器为SP上面过程等价于:push {reglist}

第(1-1) 步操作已经把MSP加载到 R0中了,所以此时R0中存放的就是当前堆栈顶地址。假设此时的SP地址为0x20005818, 即R0 = 0x20005818;
执行(2-1)步之后,R0 = R0 - 4 * 8(R4-R11一共8个寄存器) R0 = 0x200057F8, 整个过程其实就是完成R4-R11的压栈操作。

在(2-3)的时候将LR压栈(将EXC_RETURN入栈),在从异常退出的时候会将该值赋值给PC(见上面BX lr)

(3-1) 到(3-5)可以概括为:判断进入异常之前使用的是MSP还是PSP,然后再将 R0 的值赋值给MSP或者PSP
上面先使用 R0 进行压栈操作,然后再把R0的值传给MSP是因为异常工作在H andler模式,此时SP=MSP,而对R4-R11需要压入进入异常之前的栈中,如果进入异常模式之前使用的是MSP,那么完全可以使用PUSH指令来完成,但是如果进入异常 之前系统使用的是PSP,此时,就需要对PSP进行操作,因此就不能使用PUSH了。

(4-1) 到(4-3) 概括为异常处理中调用其他函数,因为都是在异常栈中,所以只需要入栈LR即可。如果调用的函数是C函数,可以省略掉(4-1)和(4-3)因为在函数进入之前和退出之后编译器会自动完成对LR的入栈和出栈。

(5-1) 到(5-2)概括为:位置EXC_RETURN[2]使异常结束后进入线程模式,且使用PSP

1.1.3 中断入栈

在Cortex-M7上当发生外部中断跳转或者系统异常跳转时,候硬件会自动把r0-r3/r12/lr/pc/xspr进行自动入栈,其他的部分可以通过手动进行入栈,如上面 HardFault_Handler 函数中会手动入栈 r4-r11, 以及再入栈 EXC_RETURN 的值。此外 Cortex-M7使用满栈递减的入栈方式。
【RT-Thread 系统异常入门及渐进 3 - Cortex-m7 异常处理及 hardfault 处理分析】_第1张图片
从图中可以看到当在 HardFault_Handler 完成手动入栈之后,栈顶存放的值是EXC_RETURN

最先入栈的是xPSR,接着是PC,LR…,
【RT-Thread 系统异常入门及渐进 3 - Cortex-m7 异常处理及 hardfault 处理分析】_第2张图片

到此介绍的都是硬件处理流程,我们知道硬件信息通常都是通过软件抽象的方式进行描述。接下来是介绍如果通过软件来对栈进行抽象的。

1.1.4 异常栈帧

Cortex-m7上当发生异常时硬件会自动入栈r0-r3/r12/lr/pc/psr, 软件上使用结构体 struct exception_stack_frame 来描述异常栈帧,我们知道进程上下文或者中断上下文不止包含 8个 register,还有其他 register,软件上使用 struct stack_frame 来描述一个栈帧(注意和异常栈帧的区别),可以看出 struct stack_frame中包含了异常栈帧。
异常栈帧结构实现:

struct exception_stack_frame
{
    rt_uint32_t r0;
    rt_uint32_t r1;
    rt_uint32_t r2;
    rt_uint32_t r3;
    rt_uint32_t r12;
    rt_uint32_t lr;
    rt_uint32_t pc;
    rt_uint32_t psr;
};

栈帧结构实现:

struct stack_frame
{
#if USE_FPU
    rt_uint32_t flag;
#endif /* USE_FPU */
    /* r4 ~ r11 register */
    rt_uint32_t r4;
    rt_uint32_t r5;
    rt_uint32_t r6;
    rt_uint32_t r7;
    rt_uint32_t r8;
    rt_uint32_t r9;
    rt_uint32_t r10;
    rt_uint32_t r11;
    struct exception_stack_frame exception_stack_frame;
};

比较巧妙的是栈帧 struct stack_frame 中寄存器的排布正好和 HardFault_Handler中对寄存器的入栈方式一致,所以只需要在 HardFault_Handler 中获取 msp 的值即可得到从异常转跳转到 HardFault_Handler 之前的上下文,也就可以拿到发生异常之前的 PC/LR等的值。

1.1.5 异常信息描述结构

软件上使用 struct exception_info 描述异常信息,通过下面内容可以看到最上面放的 exce_return 的值,这里正好对应上 HardFault_Handler 中最后入栈的 lr 的值。

struct exception_info
{
    rt_uint32_t exc_return;
    struct stack_frame stack_frame;
}

在异常处理完成后会通过将lr(exc_return)的值赋值给pc,然后硬件判断出退出中断之后使用psp还是msp

1.1.6 hard_fault_trace 实现

从上一节的 (4-1) 到 (4-3) 可以看到 HardFault_Handler 会调用 rt_hw_hard_fault_exception(rt-thread/libcpu/arm/cortex-m7/cpuport.c)
rt_hw_hard_fault_exception 再会中调用 hard_fault_track(void)
rt-thread/libcpu/arm/cortex-m7/cpuport.c 中的 hard_fault_track(void) 先判断发生HardFault异常的原因(通过读取HFSR), 原因是三种:

  • Debug event has occurred. The Debug Fault Status Register has been
  • updated. Processor has escalated a configurable-priority exception to
  • HardFault. Vector table read fault has occurred.
bus_fault_track();
mem_manage_fault_track();
usage_fault_track();

1.1.7 cm_backtrace注册流程

INIT_DEVICE_EXPORT(rt_cm_backtrace_init) //进行注册

rt-thread/packages/CmBacktrace/cmb_port.c 中的 rt_cm_backtrace_init(void) 调用rt_hw_exception_install(exception_hook);

 rt_cm_backtrace_init(void)
     rt_hw_exception_install(exception_hook);

rt-thread/libcpu/arm/cortex-m7/cpuport.c 中的 rt_hw_exception_install(rt_err_t (*exception_handle)(void *context))注册rt_exception_hookrt_hw_hard_fault_exception中,

/* exception hook */
static rt_err_t (*rt_exception_hook)(void *context) = RT_NULL;
void rt_hw_exception_install(rt_err_t (*exception_handle)
                                        (void *context))
{
   //这里将cm_backtrace函数赋值给全局变量
    rt_exception_hook = exception_handle;  
}

在这里插入代码片函数 rt_hw_hard_fault_exception 通过 HardFault Handler 中赋给r0MSP,当做参数来回去异常栈的地址。

void rt_hw_hard_fault_exception(struct exception_info 
                                        *exception_info)
{
    extern long list_thread(void);
    struct exception_stack_frame *exception_stack = 
         &exception_info->stack_frame.exception_stack_frame;
    struct stack_frame *context = &exception_info->stack_frame;

   if (rt_exception_hook != RT_NULL) //判断是否有注册,
    {
        rt_err_t result;
        //传入异常栈帧的指针
		result = rt_exception_hook(exception_stack);
		if (result == RT_EOK) return;
    }

当前传给 void cm_backtrace_fault的参数不对,需要修改,参数1需要是exc_return 而不是lr, 参数二是msp

推荐阅读:
https://wenku.baidu.com/view/01b3e039660e52ea551810a6f524ccbff021ca4b.html

你可能感兴趣的:(#,ARM,System,Exception,arm开发,嵌入式硬件,arm)