当前在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
会自动硬件入栈。
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
。
在Cortex-M7上当发生外部中断跳转或者系统异常跳转时,候硬件会自动把r0-r3/r12/lr/pc/xspr
进行自动入栈,其他的部分可以通过手动进行入栈,如上面 HardFault_Handler
函数中会手动入栈 r4-r11
, 以及再入栈 EXC_RETURN
的值。此外 Cortex-M7使用满栈递减的入栈方式。
从图中可以看到当在 HardFault_Handler
完成手动入栈之后,栈顶存放的值是EXC_RETURN
。
到此介绍的都是硬件处理流程,我们知道硬件信息通常都是通过软件抽象的方式进行描述。接下来是介绍如果通过软件来对栈进行抽象的。
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
等的值。
软件上使用 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
。
从上一节的 (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), 原因是三种:
bus_fault_track();
mem_manage_fault_track();
usage_fault_track();
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_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;
}
在这里插入代码片
函数 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;
}
当前传给 void cm_backtrace_fault的参数不对,需要修改,参数1需要是exc_return 而不是lr, 参数二是msp
推荐阅读:
https://wenku.baidu.com/view/01b3e039660e52ea551810a6f524ccbff021ca4b.html