CM3 异常处理

简述 #

异常包括:

异常编号 #

异常编号异常类型CMSIS-Core 枚举CMSIS-Core 枚举值异常处理名优先级
1复位Reset_Handler-3(最高)
2不可屏蔽中断NonMaskableInt_IRQn-14NMI_Handler-2
3硬件故障MemoryManagement_IRQn-13HardFault_Handler-1
4内存管理故障BusFault_IRQn-12MemManage_Handler可编程
5总线故障BusFault_IRQn-11BusFault_Handler可编程
6使用故障UsageFault_IRQn-10UsageFault_Handler可编程
7 ~ 10
11系统服务调用SVCall_IRQn-5SVC_Handler可编程
12调试监控DebugMonitor_IRQn-4DebugMon_Handler可编程
13
14可挂起的服务调用PendSV_IRQn-2PendSV_Handler可编程
15系统节拍定时器SysTick_IRQn-1SysTick_Handler可编程
16 ~ 255外部中断 #0 ~ #239设备定义0 ~ 239设备定义可编程

向量表偏移寄存器 #

位 31:30
位 31:30
位 29
位 29
位 28:7
位 28:7
位 6:0
位 6:0
向量表偏移寄存器 VTOR ,地址 0xE000ED08
向量表偏移寄存器 VTOR ,地址 0xE000ED08
TBLOFF
TBLOFF
--
--
TBLOFF
TBLOFF
--
--
TBLBASE
TBLBASE
--
--
CM3 r2p1
CM3 r2p1
CM3 r0p0 - r2p0
CM3 r0p0 - r2p0
SRAM → 0x20000000
SRAM → 0x20000000
0
0
1
1
00
00

向量表 #

MemManage 向量
MemManage 向量
BusFault 向量
BusFault 向量
UsageFault 向量
UsageFault 向量
--
--
--
--
--
--
--
--
SVC 向量
SVC 向量
调试监控向量
调试监控向量
--
--
PendSV 向量
PendSV 向量
SysTick 向量
SysTick 向量
外部中断 #0 向量
外部中断 #0 向量
外部中断 #1 向量
外部中断 #1 向量
外部中断 #2 向量
外部中断 #2 向量
外部中断 #3 向量
外部中断 #3 向量
0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10
11
11
12
12
13
13
14
14
15
15
16
16
17
17
18
18
19
19
异常编号
异常编号
0x00000000
0x00000000
0x00000004
0x00000004
0x00000008
0x00000008
0x0000000C
0x0000000C
0x00000010
0x00000010
0x00000014
0x00000014
0x00000018
0x00000018
0x0000001C
0x0000001C
0x00000020
0x00000020
0x00000024
0x00000024
0x00000028
0x00000028
0x0000002C
0x0000002C
0x00000030
0x00000030
0x00000034
0x00000034
0x00000038
0x00000038
0x0000003C
0x0000003C
0x00000040
0x00000040
0x00000044
0x00000044
0x00000048
0x00000048
0x0000004C
0x0000004C
存储器地址
存储器地址
MSP 初始值
MSP 初始值
复位向量
复位向量
NMI 向量
NMI 向量
HardFault 向量
HardFault 向量

CONTROL 寄存器 #

--
--
SPSEL
SPSEL
nPRIV
nPRIV
位 31:2
位 31:2
位 1
位 1
位 0
位 0

AAPCS #

ARM 架构过程调用标准(ARM Architecture Procedure Call Standard)。

寄存器别名作用
R0a1参数1 / 返回值
R1a2参数2 / 返回值(64 位结果时与 R0 组合)
R2a3参数3
R3a4参数4
R4 ~ R11v1 ~ v8变量寄存器
R12IP内部调用临时寄存器
R13SP堆栈指针
R14LR链接寄存器
R15PC程序计数器
此表仅截取了部分内容。

通常 C 函数调用由编译器自动生成汇编代码保存 “调用者保存寄存器” 。但异常处理函数不由用户程序调用,所以异常发生时由硬件自动将 xPSR 、返回地址(PC)、LR 、R12 、R3 、R2 、R1 、R0 共 8 个寄存器压栈。

这 8 个寄存器并非依次压栈,处理器首先将 PC 和 xPSR 压栈,这样在取向量时会尽快更新 PC 。压栈操作中用的栈可以是 MSP 或 PSP ,取决于 CONTROL[1] 。

双字栈对齐 #

双字栈对齐特性由 SCB 中的 CCR 寄存器的第 9 位 STKALIGN 控制。

AAPCS 中要求函数栈帧的起始地址应是双字(8 字节)对齐的。若异常发生时栈帧未对齐到双字地址上,CM3/4 处理器会自动插入一个字即 SP = SP - 4,然后将压栈的 xPSR 值的第 9 位设置为 1 (表示 SP 调整过)。异常返回时,若栈帧中的 xPSR(bit9=1), 则出栈后 SP = SP + 4 。

xPSR (Bit9=1)
xPSR (Bit9=1)
插入的空位
插入的空位
已使用的栈空间
已使用的栈空间
0x2000FFF4
0x2000FFF4
0x2000FFF0
0x2000FFF0
高地址
高地址
返回地址
返回地址
LR
LR
R12
R12
R3
R3
R2
R2
R1
R1
R0
R0
低地址
低地址
8 字节对齐,地址末尾为 0 或 8
8 字节对齐,地址末尾为 0 或 8
栈帧
栈帧

xPSR 寄存器的中的第 9 位不会被修改,修改的是栈帧中的 xPSR 值的第 9 位。

普通函数调用不会触发硬件自动的双字栈对齐,应该是编译器通过汇编指令调整的 SP 对齐(存疑)。

异常是突然发生的,CPU 不知道中断何时来,也不知道来的时候 SP 是几字节对齐的,所以需要硬件自动的双字栈对齐。

EXC_RETURN #

EXC_RETURN 是一个特殊值,处理器在进入异常处理函数后,会设置 LR = EXC_RETURN 。当异常函数返回时会将 LR 加载到 PC ,硬件检测到 PC = EXC_RETURN 后会触发异常返回机制。

全为 1
全为 1
返回模式
返回模式
返回栈
返回栈
位 31:5
位 31:5
位 3
位 3
位 2
位 2
栈帧类型
栈帧类型
位 4
位 4
0
0
1
1
位 1
位 1
位 0
位 0
栈帧类型返回模式返回栈
026 字处理模式MSP
18 字线程模式PSP

C 函数调用流程 #

进入函数
进入函数
函数处理
函数处理
恢复寄存器
恢复寄存器
保存寄存器
保存寄存器
加载 LR 到 PC
加载 LR 到 PC
函数返回
函数返回
BL 指令:设置 LR = 返回地址,PC = 函数地址
BL 指令:设置 LR = 返回地址,PC = 函数地址

其汇编的流程大致如下:

.global _start

_start:
    MOV R0, #3        /* 参数 1                               */
    MOV R1, #5        /* 参数 2                               */
    LDR R4, =0x1234   /* 测试 R4-R6 是否会被破坏              */
    LDR R5, =0x5678   /* 测试 R4-R6 是否会被破坏              */
    LDR R6, =0x9ABC   /* 测试 R4-R6 是否会被破坏              */
    BL add            /* 设置 LR = 返回地址并跳转到 add       */
    LDR R3, =0x8888   /* LR = 此处地址:返回后从这里继续执行  */

add: 
    PUSH {R4-R6,LR}   /* 保存 R4-R6 和返回地址 LR             */
    MOV R4, R0
    MOV R5, R1
    ADD R6, R4, R5
    MOV R0, R6        /* R0 保存返回值                        */
    POP {R4-R6,PC}    /* 恢复 R4-R6,将 LR 的值弹入 PC 寄存器 */

对于 PUSH 指令,编号高的寄存器总是先存储;对于 POP 指令,编号低的寄存器总是先加载。

异常处理函数调用流程 #

进入函数
进入函数
函数处理
函数处理
恢复寄存器
恢复寄存器
保存寄存器
保存寄存器
加载 LR 到 PC
加载 LR 到 PC
函数返回
函数返回
将 xPSR 、返回地址(PC)、LR 、R12 、R3 、R2 、R1 、R0 共八个寄存器压栈
将 xPSR 、返回地址(PC)、LR 、R12 、R3 、R2 、R1 、R0 共八个寄存器压栈
设置 LR = EXC_RETURN
设置 LR = EXC_RETURN
设置 PC = 异常向量(向量地址 = 向量表偏移地址 + 异常编号 × 4)
设置 PC = 异常向量(向量地址 = 向量表偏移地址 + 异常编号 × 4)
硬件检测到 PC = EXC_RETURN 触发异常返回机制
硬件检测到 PC = EXC_RETURN 触发异常返回机制
异常产生
异常产生
内核接收并开始处理
内核接收并开始处理
异常处理函数
异常处理函数
NVIC
NVIC
处理模式,强制使用主栈 MSP
处理模式,强制使用主栈 MSP
若 SP 未调整,则压栈的 xPSR[9]=0
若 SP 未调整,则压栈的 xPSR[9]=0
栈已双字对齐,则 SP 不变
栈已双字对齐,则 SP 不变
若 CONTROL[1]=0,使用 MSP
若 CONTROL[1]=0,使用 MSP
若 EXC_RETURN[2]=0 则使用 MSP,CONTROL[1]=0
若 EXC_RETURN[2]=0 则使用 MSP,CONTROL[1]=0
若出栈的 xPSR[9]=0 则 SP 不变
若出栈的 xPSR[9]=0 则 SP 不变
将 xPSR 、返回地址(PC)、LR 、R12 、R3 、R2 、R1 、R0 出栈
将 xPSR 、返回地址(PC)、LR 、R12 、R3 、R2 、R1 、R0 出栈
PC = 出栈的返回地址
PC = 出栈的返回地址
若 CONTROL[1]=1,使用 PSP
若 CONTROL[1]=1,使用 PSP
栈未双字对齐,则 SP = SP - 4
栈未双字对齐,则 SP = SP - 4
若 SP 已调整,则压栈的 xPSR[9]=1
若 SP 已调整,则压栈的 xPSR[9]=1
若 EXC_RETURN[2]=1 则使用 PSP,CONTROL[1]=1
若 EXC_RETURN[2]=1 则使用 PSP,CONTROL[1]=1
若出栈的 xPSR[9]=1 则 SP = SP + 4
若出栈的 xPSR[9]=1 则 SP = SP + 4

总结 #

异常处理相当于在标准 C 函数套了一层 “硬件封装” ,异常处理函数的进入与返回都由硬件自动完成。 标准 C 函数的进入与返回则是由编译器按照 AAPCS 约定生成的汇编指令完成。