首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【ARM Cortex-M 系列 2.1 -- RT-Thread Cortex-M7 异常处理及 hardfault 处理分析】

  • 23-09-19 22:01
  • 2680
  • 13104
blog.csdn.net

文章目录

    • 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注册流程

上篇文章:ARM Cortex-M 系列 2 – CPU 之 Cortex-M7 介绍
下篇文章:ARM Cortex-M 系列 3 番外篇 – ARMv6, ARMv7, ARMv8, ARMv9 架构差异及精简指令集 与 复杂指令集 介绍

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

(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使用满栈递减的入栈方式。
在这里插入图片描述
从图中可以看到当在 HardFault_Handler 完成手动入栈之后,栈顶存放的值是EXC_RETURN。

最先入栈的是xPSR,接着是PC,LR…,
在这里插入图片描述

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

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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

栈帧结构实现:

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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

比较巧妙的是栈帧 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5

在异常处理完成后会通过将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
  • 2
  • 3

1.1.7 cm_backtrace注册流程

INIT_DEVICE_EXPORT(rt_cm_backtrace_init) //进行注册
  • 1

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);
  • 1
  • 2

rt-thread/libcpu/arm/cortex-m7/cpuport.c 中的 rt_hw_exception_install(rt_err_t (*exception_handle)(void *context))注册rt_exception_hook 到rt_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;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

上篇文章:ARM Cortex-M 系列 2 – CPU 之 Cortex-M7 介绍
下篇文章:ARM Cortex-M 系列 3 番外篇 – ARMv6, ARMv7, ARMv8, ARMv9 架构差异及精简指令集 与 复杂指令集 介绍

推荐阅读:
http://iyenn.com/index/link?url=https://wenku.baidu.com/view/01b3e039660e52ea551810a6f524ccbff021ca4b.html

注:本文转载自blog.csdn.net的CodingCos的文章"https://blog.csdn.net/sinat_32960911/article/details/127777649"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top