时间轴

2025-10-16

init

ARM64 的异常处理(Exception Model)

参考文档:

ARM64 的异常等级(Exception Level)

  • EL0 非特权模式,例如应用程序
  • EL1 特权模式,例如 OS 内核
  • EL2 虚拟化监控程序,例如 hypervisor
  • EL3 安全模式,例如 secure monitor

术语(Exception terminology)

  • Taking an exception: 正在处理一个异常
  • Returning from an exception: 从一个异常中返回
  • Exception levels: 异常等级
  • Precise exception: 精准异常
  • Synchronous and asynchronous exception: 同步异常与异步异常
    • 同步异常
      • 系统调用, svc, hvc, SMC 等
      • MMU 引发的异常
      • SP 和 PC 对齐检查
      • 未分配的指令
        • 未分配的指令操作码
        • 需要比当前异常级别更高级别特权的指令。
        • 已禁用的指令。
        • PSTATE.IL 域被设置时的任何指令
      • 调试异常
    • 异步异常
      • IRQ 中断
      • FIQ 中断
      • SError(系统错误)

异常入口(Exception Entry)

Process state or PSTATE is an abstraction of process state information

  • 当异常发生时,CPU 硬件都做了哪些事情
    • PSTATE 保存到 SPSR_ELx(The Saved Program Status Register)
    • 返回地址保存 ELR_ELx
    • PSTATE 寄存器的 DAIF 域都设置为 1,相当于把调试异常,系统错误(SError),IRQ 中断以及 FIQ 中断都关闭了
    • 更新了 ESR_ELx(Exception Syndrome Register)寄存器,里面包含了同步异常或SError发生的原因
    • SP 执行 SP_ELx
    • 切换到对应的 EL,然后跳转到异常向量表里执行
  • 当异常发生后,操作系统需要做哪些事情

操作系统需要设置异常向量表,这样CPU根据异常发生的类型,会跳转到合适的异常向量表的表项

异常向量表的每个表项会保存一个异常处理的跳转函数,然后跳转到恰当的异常处理函数并处理异常

异常状态寄存器(Exception Syndrome Register, ESR_ELn)包含允许异常处理程序确定异常原因的信息。它只
对针对同步异常和 SError 做更新,不为 IRQ 或 FIQ 更新,因为这些中断处理程序通常从通用中断控制
器 (GIC) 的寄存器中获取状态信息。

异常的返回

  • 操作系统执行一条eret语句
    • 从 ELR_ELx(Exception Link Register)寄存器中恢复 PC 指针
    • 从 SPSR_ELx(The Saved Program Status Register)寄存器恢复处理器的状态

异常处理的流程

异常返回地址

  • 返回地址两个寄存器
    • x30:子函数的返回地址。使用 ret 指令来返回
    • ELR_ELx:异常返回地址。使用 eret 指令来返回
  • ELR_ELx寄存器保存了异常返回地址
    • 对于异步异常,它的返回地址是中断发生时的下一条指令,或者没有执行的第一条指令
    • 对于不是 system call 的同步异常,返回的是触发同步异常的那一条指令
    • 对于 system call,它返回的是 svc 指令的下一条指令

异步异常(中断)

硬件已经把 ELR 设置为 下一条要执行的指令eret 就直接回去继续执行,不用额外改

同步异常(非 system call)

硬件把 ELR 设置为 触发异常的那条指令
如果你修复了异常原因(比如缺页异常),直接 eret 就会重新执行这条指令
如果你想跳过它(不重试),就要手动 ELR_ELx += 4

system call(svc)

硬件把 ELR 设置为 svc 的下一条指令,所以 eret 会直接返回到下一条

异常处理的路由

当在一个特定的“异常等级(Exception Level, EL)”发生异常时,CPU 应该跳转到哪个异常等级去处理它

  • 异常发生时,可以在当前 EL 处理也可以在更高 EL 处理
    • EL0 不能用来处理异常
    • 同步异常是可以在当前 EL 里处理的,比如在 EL1 发生了同步异常
    • 对于异步异常,可以路由到 EL1,EL2,EL3 处理,需要配置HCR(Hypervisor Configuration Register)以及SCR(Secure Configuration Register)相关寄存器
    • 如果异常是由于在 EL0 处取指令而产生的,则将其视为 EL1 的异常,除非 HCR_EL2.TGE 位设置为非安全状态,在这种情况下将其视为 EL2。

异常发生时要路由到哪一级

参考 armv8.6 的 tableD1-10

  • SCR_EL3: Secure Configuration Register
  • HCR_EL2: Hyervisor Cofiguration Register

该图片中的表格(Table D1-10)是ARM 架构下 EL3(安全监控模式)与 EL2(虚拟化监控模式)同时实现时的中断 / 异常路由规则表,核心用于定义不同系统状态(寄存器配置)下,中断(IRQ/FIQ)或异常(如 Abt 中止异常)触发后,处理器最终跳转的目标特权级别(EL0~EL3)。

Routing when both EL3 and EL2 are implemented

  1. 第 1 行:SCR=0,NS EEL2a=0,EA IRQ FIQ=0,RW=0
  • 输入条件:安全配置为默认(SCR=0)、非安全 EL2 异常入口关闭(NS EEL2a=0)、无活跃中断 / 异常(EA IRQ FIQ=0)、读写权限为 0;HCR TGE、E2H 为任意值(X)。
  • 路由结果
    • 从 EL0 触发:跳转到 “FIQ IRQ Abt”(即触发对应中断 / 异常的原生处理入口,未被高 EL 拦截);
    • 从 EL1 触发:同 EL0,跳转到 “FIQ IRQ Abt”;
    • 从 EL2 触发:n/a(不适用,因 NS EEL2a=0 时 EL2 未使能该场景);
    • 从 EL3 触发:跳转到 “C”(通常指 “安全态下的异常处理入口”,ARM 架构中 “C” 常代表安全相关的默认目标)。
  1. 第 2 行:SCR=0,NS EEL2a=0,EA IRQ FIQ=0,RW=1
  • 输入条件:仅 RW(读写权限)从 0 改为 1,其他与第 1 行一致。
  • 路由结果
    • 从 EL0/EL1 触发:跳转到 “EL1”(即异常被 EL1(内核态)拦截处理,而非原生中断入口,因 RW=1 对应更高权限的内核处理逻辑);
    • 从 EL2 触发:n/a(同第 1 行,EL2 未使能);
    • 从 EL3 触发:跳转到 “C”(安全态入口不变)。
  1. 第 3 行:SCR=0,NS EEL2a=0,EA IRQ FIQ=1
  • 输入条件:EA IRQ FIQ=1(有活跃的中断 / 异常触发),其他配置同前两行;HCR TGE、E2H 为任意值。
  • 路由结果
    • 从 EL0/EL1 触发:跳转到 “EL3”(活跃中断 / 异常被最高安全级 EL3 拦截,符合 ARM 安全架构中 “高优先级异常由 EL3 处理” 的逻辑);
    • 从 EL2 触发:n/a(EL2 未使能);
    • 从 EL3 触发:跳转到 “EL3”(EL3 自身触发的异常,在 EL3 内部处理,不跳转其他级别)。
  1. 第 4 行:SCR=0,NS EEL2a=1,EA IRQ FIQ=0,HCR TGE=0,E2H=0,RW=0
  • 输入条件:NS EEL2a=1(非安全 EL2 异常入口开启)、无活跃中断 / 异常(EA IRQ FIQ=0)、虚拟化模式关闭(HCR TGE=0)、EL2 不拦截 EL1 异常(E2H=0)。
  • 路由结果
    • 从 EL0 触发:跳转到 “FIQ IRQ Abt”(原生中断入口);
    • 从 EL1 触发:跳转到 “FIQ IRQ Abt”(EL2 未拦截,因 E2H=0);
    • 从 EL2 触发:跳转到 “C”(非安全 EL2 触发的异常,路由到安全态入口 “C”);
    • 从 EL3 触发:跳转到 “C”(安全态内部处理,目标不变)。

栈的选择

  • 每个异常等级 EL 都有对应栈指针寄存器 SP_ELx
    • SP_EL0, SP_EL1, SP_EL2, SP_EL3
  • 栈必须 16 字节对齐。硬件可以检测栈指针是否对齐
  • 当异常发生时,并跳转到目标异常等级时,硬件会自动选择 SP_ELx
  • 操作系统负责和分配保证每个异常等级 EL 对应的栈,是可用的

执行模式

异常处理的执行模式

  • 当异常发生时,切换到高级别的 EL,这个 EL 运行在哪个模式?AArch64 or AArch32
    • HCR_EL2.RW(Hypervisor Configuration Register)记录了 EL1 要运行在哪个模式
      • 1 表示 aarch64
      • 0 表示 aarch32

Execution state control for lower Exception levels

  • 当异常发生后,执行模式可以发生改变
    • 一个 aarch32 的应用程序正在运行,这时候来了一个中断,它可能会跑到 aarch64 执行状态下的 EL1 里处理这个中断

异常返回的执行模式

  • 从一个异常返回,SPSR 寄存器(Saved Program Status Register)记录了:
    • 返回到哪个 EL? SPSR.M[3:0]
    • 返回目标 EL 的执行模式?SPSR.M[4]
      • 0 表示 aarch64
      • 1 表示 aarch32

Exceptions taken from AArch64 state

实验 1:切换到 EL1 中运行

实验一

提示

从 EL2 切换到 EL1,需要做如下几件事情

  1. 设置 HCR_EL2(Hypervisor Configuration Register)寄存器,最重要的是 Bit 31 的 RW 域,表示 EL1 要运行在哪个执行环境里,aarch32 或 aarch64(相似地对于EL2的执行状态由SCR_EL3决定)

    • HCR_EL2 属于 General System Control Register

    HCR_EL2

    HCR_EL2.RW

  2. 设置 SCTLR_EL1(System Control Register),要设置大小端和关闭 MMU

    SCTLR_EL1.EE

    SCTLR_EL1.E0E

    SCTELR_EL1.M

  3. 设置 SPSR_EL2(Saved Program Status Register)寄存器,设置模式 M 域为 EL1h,另外需要关闭所有 PSTATE 的 DAIF

    • SPSR_ELx属于Special-purpose Register

    SPSR_EL2

    SPSR_EL2.M

    PSTATE.DAIF at EL2


    SPSR_EL1

    SPSR_EL.M

M[3:0] 目标模式 说明
0000 EL0t EL0,使用 SP_EL0
0100 EL1t EL1,使用 SP_EL0
0101 EL1h EL1,使用 SP_EL1
1000 EL2t EL2,使用 SP_EL0
1001 EL2h EL2,使用 SP_EL2
1100 EL3t EL3,使用 SP_EL0
1101 EL3h EL3,使用 SP_EL3

PSTATE.DAIF at EL1

  1. 设置异常返回寄存器 elr_el2,让其返回到 el1_entry 汇编函数里

  2. 执行eret

(3,4 其实是一个异常返回的经典步骤,从 EL2 返回到 EL1)

首先根据当前异常等级判断要跳到哪个异常等级

判断当前异常等级

注意执行 eret 前要设置 elr_el2 寄存器

跳到EL1

异常向量表(Exception vectors)

  • 每个异常等级 EL 都有自己的异常向量表,EL0 除外

  • 异常向量表的基地址需要设置到VBAR_ELx(Vector Base Address Register)寄存器中

  • VBAR_EL1 寄存器:

    VBAR_ELx

    0~10 是 reserve,因此地址是 2k 对齐

异常向量表结构

  1. 行(Exception taken from)

    定义异常触发时的 “当前执行状态”,分 4 类场景:

    • Current Exception level with SP_EL0:当前异常级别使用 SP_EL0 栈指针(如在 EL1 下用用户级栈指针时触发异常 )。
    • Current Exception level with SP_ELx, x>0:当前异常级别使用 SP_ELx(x≥1,如 EL1 用 SP_EL1、EL2 用 SP_EL2 等内核级栈指针)时触发异常。
    • Lower Exception level(AArch64):从更低异常级别触发,且 “更低级别” 处于 AArch64 执行状态(如从 EL0/EL1 切换到更高 EL 时,且原级别是 64 位模式 )。
    • Lower Exception level(AArch32):从更低异常级别触发,且 “更低级别” 处于 AArch32 执行状态(如从 32 位模式的 EL0/EL1 切换到更高 EL )。
  2. 列(Offset for exception type)

    按异常类型分类,对应不同偏移:

    • Synchronous:同步异常(如指令执行错误、软件触发异常等,与指令流同步发生 )。
    • IRQ or vIRQ:普通中断(IRQ)或虚拟中断(vIRQ,虚拟化场景下)。
    • FIQ or vFIQ:快速中断(FIQ,优先级通常更高)或虚拟快速中断(vFIQ,虚拟化场景 )。
    • SError or vSError:系统错误(SError,如总线错误等严重错误)或虚拟系统错误(vSError,虚拟化场景 )。
  3. 偏移值(如 0x000、0x080 等):基于行(触发状态)和列(异常类型)的组合,给出相对于向量表基地址的偏移,处理器通过基地址 + 偏移,就能找到异常处理程序的入口地址 。

异常向量表结构

  • 异常向量表的整体结构

AArch64 EL1 及以上的执行级别中,异常向量表(Vector Table)存放在 VBAR_ELx(Vector Base Address Register)寄存器指向的地址。

这个表里一共包含 4 个“区块” (blocks),分别对应不同的异常来源:

  1. 从当前的 SP (SP0) 触发的异常
  2. 从当前的 SP (SPx) 触发的异常(x≠0,比如 SP_EL1)
  3. 来自下一个更低特权级(比如 EL0) 的异常
  4. 来自当前特权级(比如 EL1 内部) 的异常

每个区块又包含 4 种异常类型

  • Synchronous exception (同步异常,比如 SVC 指令、数据访问异常)
  • IRQ (普通中断)
  • FIQ (快速中断)
  • SError (系统错误,通常是硬件异常)

所以总共:
4 个区块 × 4 种异常 = 16 个表项


每个表项的大小

ARMv8-A 规定:

  • 每个表项的大小固定为 128 字节(0x80)(32条指令大小)。
  • 16 个表项 × 128 字节 = 2048 字节 = 2KB
  • 所以整个向量表的大小是 2KB

也就是说,异常向量表必须是 2KB 对齐的区域。

异常向量表表实际上由 4 组 4 项组成

Linux5.0 内核的异常处理

Linux5.0的异常向量表

  • .align 11 就是让异常向量表按2k 大小对齐

  • kernel_ventry 是一个宏,简化代码如下

kernel_ventry

align 7 表示按 2 的 7 次方大小来对齐,2 的 7 次方是 128 个字节

sub 指令是让栈指针 sp 减去一个 S_FRAME_SIZE,其中 S_FRAME_SIZE 称为寄存器框架大小,也就是 struct pt_regs 数据结构的大小

“\()”表示连接

举例:在发生 EL1 的 IRQ 中断时,这条语句变成了”b el1_irq”

kernel_entry 是异常向量表跳转到的第一级入口,它的职责是:

  1. 保存上下文:把 CPU 现场(通用寄存器)保存到栈上。
  2. 建立栈帧:为异常处理函数(C 代码)准备好栈空间。
  3. 切换栈(如果需要):例如从用户态进入内核态时,栈要换成内核栈。
  4. 调用 C 层的异常分发函数

以发生在 EL1 的 FIQ 为例

发生在EL1的FIQ

bad_mode

保存异常上下文

  • 栈框:Linux 内核中定义了一个struct pt_regs的数据结构来描述内核栈上保存寄存器的排列信息,通常用于保存中断上下文等信息

栈框

orig_x0, syscallno, orig_addr_limit, unused, stackfram[0], stackframe[1]都是软件定义保存的东西

保存异常上下文的处理流程

实验 2:建立一个异常向量表并创造一个同步异常

实验2

FAR Fault Address Register (at EL1)

它的作用是:

  • 发生同步异常(Synchronous Exception) 时,处理器会把导致异常的 虚拟地址写入到 FAR_ELx 对应寄存器中。
  • EL1 就对应内核态(操作系统层),所以 FAR_EL1 保存的是 EL1 下异常发生时的虚拟地址

常见场景

  1. Page Fault(缺页异常)
    如果 CPU 在访问一个不存在或非法的虚拟地址时触发异常,FAR_EL1 会保存这个地址,内核就可以根据它决定是否分配新页、还是杀掉进程。
  2. Alignment Fault(未对齐访问异常)
    如果访问地址不符合对齐规则,FAR_EL1 也会保存那个地址。
  3. Watchpoint/Breakpoint 异常
    FAR_EL1 也可能被用来存储触发异常的地址。

相关寄存器

  • ESR_EL1 (Exception Syndrome Register)
    保存异常的 原因码(比如是缺页、权限错误、未对齐等)。
  • FAR_EL1
    保存导致异常的 具体虚拟地址

entry.S

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#define BAD_SYNC  0
#define BAD_IRQ 1
#define BAD_FIQ 2
#define BAD_ERROR 3

.macro inv_entry el, reason
// 保存异常现场,暂未实现
// kernel_entry el
mov x0, sp
mov x1, #\reason
mrs x2, esr_el1
b bad_mode
.endm


// 异常向量表
.macro vtentry label
.align 7
b \label
.endm

/*
* ARM64的异常向量表一共占用2048个字节,分成四组,每个表项占用128字节
* .align 11表示按2048对齐
*/


.align 11
.global vectors

vectors:
/* Current EL with SP0
* 当前系统运行在EL1时使用EL0的栈指针SP
* 这是一种异常的错误类型
*/
vtentry el1_sync_invalid
vtentry el1_irq_invalid
vtentry el1_fiq_invalid
vtentry el1_error_invalid

/* Current EL with SPx
* 当前系统运行在EL1时使用EL1的栈指针SP
* 说明系统内核态发生了异常
* 暂时只实现IRQ中断
*/

vtentry el1_sync_invalid
vtentry el1_irq_invalid
vtentry el1_fiq_invalid
vtentry el1_error_invalid

/* Lower EL using AArch64
* 用户态的aarch64程序发生异常
*/

vtentry el0_sync_invalid
vtentry el0_irq_invalid
vtentry el0_fiq_invalid
vtentry el0_error_invalid


/* Lower EL using AArch32
* 在用户态的aarch32程序发生异常
*/

vtentry el0_sync_invalid
vtentry el0_irq_invalid
vtentry el0_fiq_invalid
vtentry el0_error_invalid



el1_sync_invalid:
inv_entry 1, BAD_SYNC
el1_irq_invalid:
inv_entry 1, BAD_IRQ
el1_fiq_invalid:
inv_entry 1, BAD_FIQ
el1_error_invalid:
inv_entry 1, BAD_ERROR


el0_sync_invalid:
inv_entry 0, BAD_SYNC
el0_irq_invalid:
inv_entry 0, BAD_IRQ
el0_fiq_invalid:
inv_entry 0, BAD_FIQ
el0_error_invalid:
inv_entry 0, BAD_ERROR

// 占用两个字节
string_test:
.string "t"
// 因为string_test导致不是4个字节对齐了
.global trigger_alignment

trigger_alignment:
ldr x0, =0x80002
ldr x1, [x0]
ret

kernel.c中定义

bad_mode

kernel_main中触发trigger_alignment

trigger_alignment

boot.S中设置异常向量表

设置异常向量表

实验 3:解 bug:寻找树莓派 4 上触发异常的指令

实验3

ldr一个hong

这条指令会加载当前 PC+MY_LABEL 为地址取值到 x6

相当于:

1
x6 = *(PC-relative 地址 + MY_LABEL)

对于 LDR Xt 来说目标地址必须是 8 字节对齐的(因为 Xt 是 64 位寄存器)

可以改成 w6,只需要四个字节对齐,因为 MY_LABEL 定义为 0x20

异常的解析

ESR_ELx(异常综合信息寄存器 Excepion Syndrome Register)

ESR_ELx它只对针对同步异常和 SError 做更新,不为 IRQ 或 FIQ 更新,因为这些中断处理程序通常从通用中断控制器 (GIC) 的寄存器中获取状态信息。

ESR_ELx

  • ESR 寄存器一共包含四个字段(域)
    • Bit 32~63 保留比特位
    • Bit 26~31,是异常类型(Exception Class,简称 EC),这个字段指示发生异常的类型,同时用来索引 ISS 域
    • Bit 25,IL,表示同步异常指令的指令长度,Instruction Length
    • Bit 0~24,ISS(Instruction Specific Syndrome)具体的异常指令编码。这个异常指令编码表依赖不同的异常类型,不同的异常类型有不同的编码格式

ESR寄存器中的异常类型

常见的异常类型对应编码

Instruction Abort

第一个表示从低级异常等级的指令异常,第二个表示当前异常等级的指令异常

Data Abort

ISS 字段的编码方式

ISS encoding for Instruction Abort

ISS encoding for Data Abort

ISS编码格式 Data Abort

Data Abort DFSC

FAR(失效地址寄存器)

FAR

FAR 寄存器保存了发生异常时刻的虚拟地址

实验四:解析数据异常的信息

实验四

实验四结果

esr.h

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#ifndef __ESR_H
#define __ESR_H

#define UL(x) x

#define ESR_ELx_EC_UNKNOWN (0x00)
#define ESR_ELx_EC_WFx (0x01)
/* Unallocated EC: 0x02 */
#define ESR_ELx_EC_CP15_32 (0x03)
#define ESR_ELx_EC_CP15_64 (0x04)
#define ESR_ELx_EC_CP14_MR (0x05)
#define ESR_ELx_EC_CP14_LS (0x06)
#define ESR_ELx_EC_FP_ASIMD (0x07)
#define ESR_ELx_EC_CP10_ID (0x08)
/* Unallocated EC: 0x09 - 0x0B */
#define ESR_ELx_EC_CP14_64 (0x0C)
/* Unallocated EC: 0x0d */
#define ESR_ELx_EC_ILL (0x0E)
/* Unallocated EC: 0x0F - 0x10 */
#define ESR_ELx_EC_SVC32 (0x11)
#define ESR_ELx_EC_HVC32 (0x12)
#define ESR_ELx_EC_SMC32 (0x13)
/* Unallocated EC: 0x14 */
#define ESR_ELx_EC_SVC64 (0x15)
#define ESR_ELx_EC_HVC64 (0x16)
#define ESR_ELx_EC_SMC64 (0x17)
#define ESR_ELx_EC_SYS64 (0x18)
/* Unallocated EC: 0x19 - 0x1E */
#define ESR_ELx_EC_IMP_DEF (0x1f)
#define ESR_ELx_EC_IABT_LOW (0x20)
#define ESR_ELx_EC_IABT_CUR (0x21)
#define ESR_ELx_EC_PC_ALIGN (0x22)
/* Unallocated EC: 0x23 */
#define ESR_ELx_EC_DABT_LOW (0x24)
#define ESR_ELx_EC_DABT_CUR (0x25)
#define ESR_ELx_EC_SP_ALIGN (0x26)
/* Unallocated EC: 0x27 */
#define ESR_ELx_EC_FP_EXC32 (0x28)
/* Unallocated EC: 0x29 - 0x2B */
#define ESR_ELx_EC_FP_EXC64 (0x2C)
/* Unallocated EC: 0x2D - 0x2E */
#define ESR_ELx_EC_SERROR (0x2F)
#define ESR_ELx_EC_BREAKPT_LOW (0x30)
#define ESR_ELx_EC_BREAKPT_CUR (0x31)
#define ESR_ELx_EC_SOFTSTP_LOW (0x32)
#define ESR_ELx_EC_SOFTSTP_CUR (0x33)
#define ESR_ELx_EC_WATCHPT_LOW (0x34)
#define ESR_ELx_EC_WATCHPT_CUR (0x35)
/* Unallocated EC: 0x36 - 0x37 */
#define ESR_ELx_EC_BKPT32 (0x38)
/* Unallocated EC: 0x39 */
#define ESR_ELx_EC_VECTOR32 (0x3A)
/* Unallocted EC: 0x3B */
#define ESR_ELx_EC_BRK64 (0x3C)
/* Unallocated EC: 0x3D - 0x3F */
#define ESR_ELx_EC_MAX (0x3F)

#define ESR_ELx_SET_SHIFT (11)
#define ESR_ELx_FnV_SHIFT (10)
#define ESR_ELx_EA_SHIFT (9)
#define ESR_ELx_CM_SHIFT (8)
#define ESR_ELx_S1PTW_SHIFT (7)
#define ESR_ELx_WNR_SHIFT (6)

#define ESR_ELx_EC_SHIFT (26)
#define ESR_ELx_EC_MASK (UL(0x3F) << ESR_ELx_EC_SHIFT)
#define ESR_ELx_EC(esr) (((esr) & ESR_ELx_EC_MASK) >> ESR_ELx_EC_SHIFT)

#define ESR_ELx_IL (UL(1) << 25)
#define ESR_ELx_ISS_MASK (ESR_ELx_IL - 1)
#define ESR_ELx_ISV (UL(1) << 24)
#define ESR_ELx_SAS_SHIFT (22)
#define ESR_ELx_SAS (UL(3) << ESR_ELx_SAS_SHIFT)
#define ESR_ELx_SSE_SHIFT (21)
#define ESR_ELx_SSE (UL(1) << 21)
#define ESR_ELx_SRT_SHIFT (16)
#define ESR_ELx_SRT_MASK (UL(0x1F) << ESR_ELx_SRT_SHIFT)
#define ESR_ELx_SF_SHIFT (15)
#define ESR_ELx_SF (UL(1) << 15)
#define ESR_ELx_AR_SHIFT (14)
#define ESR_ELx_AR (UL(1) << 14)
#define ESR_ELx_EA (UL(1) << 9)
#define ESR_ELx_CM (UL(1) << 8)
#define ESR_ELx_S1PTW (UL(1) << 7)
#define ESR_ELx_WNR (UL(1) << 6)
#define ESR_ELx_FSC (0x3F)
#define ESR_ELx_FSC_TYPE (0x3C)
#define ESR_ELx_FSC_EXTABT (0x10)
#define ESR_ELx_FSC_FAULT (0x04)
#define ESR_ELx_FSC_PERM (0x0C)
#define ESR_ELx_CV (UL(1) << 24)
#define ESR_ELx_COND_SHIFT (20)
#define ESR_ELx_COND_MASK (UL(0xF) << ESR_ELx_COND_SHIFT)
#define ESR_ELx_WFx_ISS_WFE (UL(1) << 0)
#define ESR_ELx_xVC_IMM_MASK ((1UL << 16) - 1)

#endif

作用:解析 ESR_ELx 寄存器的值,方便异常处理。

结构

  • 高 6 位 → EC(Exception Class)
  • IL、ISV → 指令长度/有效性
  • ISS → Instruction Specific Syndrome
  • FSC → 数据访问异常代码
  • WNR、EA、CM → 访问属性

kernel.c

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
static const char *const bad_mode_handler[] = {"Sync Abort", "IRQ", "FIQ",
"SError"};
static const char *data_fault_code[] = {
[0] = "Address size fault, level0",
[1] = "Address size fault, level1",
[2] = "Address size fault, level2",
[3] = "Address size fault, level3",
[4] = "Translation fault, level0",
[5] = "Translation fault, level1",
[6] = "Translation fault, level2",
[7] = "Translation fault, level3",
[9] = "Access flag fault, level1",
[10] = "Access flag fault, level2",
[11] = "Access flag fault, level3",
[13] = "Permission fault, level1",
[14] = "Permission fault, level2",
[15] = "Permission fault, level3",
[0x21] = "Alignment fault",
[0x35] = "Unsupported Exclusive or Atomic access",
};

static const char *esr_get_dfsc_string(unsigned int esr) {
return data_fault_code[esr & 0x3f];
}

static const char *esr_class_str[] = {
// GCC 扩展,表示把数组下标从 0 到 ESR_ELx_EC_MAX 全部初始化为 "UNRECOGNIZED
// EC"。
[0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC",
[ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized",
[ESR_ELx_EC_WFx] = "WFI/WFE",
[ESR_ELx_EC_CP15_32] = "CP15 MCR/MRC",
[ESR_ELx_EC_CP15_64] = "CP15 MCRR/MRRC",
[ESR_ELx_EC_CP14_MR] = "CP14 MCR/MRC",
[ESR_ELx_EC_CP14_LS] = "CP14 LDC/STC",
[ESR_ELx_EC_FP_ASIMD] = "ASIMD",
[ESR_ELx_EC_CP10_ID] = "CP10 MRC/VMRS",
[ESR_ELx_EC_CP14_64] = "CP14 MCRR/MRRC",
[ESR_ELx_EC_ILL] = "PSTATE.IL",
[ESR_ELx_EC_SVC32] = "SVC (AArch32)",
[ESR_ELx_EC_HVC32] = "HVC (AArch32)",
[ESR_ELx_EC_SMC32] = "SMC (AArch32)",
[ESR_ELx_EC_SVC64] = "SVC (AArch64)",
[ESR_ELx_EC_HVC64] = "HVC (AArch64)",
[ESR_ELx_EC_SMC64] = "SMC (AArch64)",
[ESR_ELx_EC_SYS64] = "MSR/MRS (AArch64)",
[ESR_ELx_EC_IMP_DEF] = "EL3 IMP DEF",
[ESR_ELx_EC_IABT_LOW] = "IABT (lower EL)",
[ESR_ELx_EC_IABT_CUR] = "IABT (current EL)",
[ESR_ELx_EC_PC_ALIGN] = "PC Alignment",
[ESR_ELx_EC_DABT_LOW] = "DABT (lower EL)",
[ESR_ELx_EC_DABT_CUR] = "DABT (current EL)",
[ESR_ELx_EC_SP_ALIGN] = "SP Alignment",
[ESR_ELx_EC_FP_EXC32] = "FP (AArch32)",
[ESR_ELx_EC_FP_EXC64] = "FP (AArch64)",
[ESR_ELx_EC_SERROR] = "SError",
[ESR_ELx_EC_BREAKPT_LOW] = "Breakpoint (lower EL)",
[ESR_ELx_EC_BREAKPT_CUR] = "Breakpoint (current EL)",
[ESR_ELx_EC_SOFTSTP_LOW] = "Software Step (lower EL)",
[ESR_ELx_EC_SOFTSTP_CUR] = "Software Step (current EL)",
[ESR_ELx_EC_WATCHPT_LOW] = "Watchpoint (lower EL)",
[ESR_ELx_EC_WATCHPT_CUR] = "Watchpoint (current EL)",
[ESR_ELx_EC_BKPT32] = "BKPT (AArch32)",
[ESR_ELx_EC_VECTOR32] = "Vector catch (AArch32)",
[ESR_ELx_EC_BRK64] = "BRK (AArch64)",
};

static const char *esr_get_class_string(unsigned int esr) {
return esr_class_str[esr >> ESR_ELx_EC_SHIFT];
}

void parse_esr(unsigned int esr) {
unsigned int ec = ESR_ELx_EC(esr);

printk("ESR info:\n");
printk(" ESR = 0x%08x\n", esr);
printk(" Exception class = %s, IL = %u bits\n", esr_get_class_string(esr),
(esr & ESR_ELx_IL) ? 32 : 16);

if (ec == ESR_ELx_EC_DABT_LOW || ec == ESR_ELx_EC_DABT_CUR) {
printk(" Data abort:\n");
if ((esr & ESR_ELx_ISV)) {
printk(" Access size = %u byte(s)\n",
1U << ((esr & ESR_ELx_SAS) >> ESR_ELx_SAS_SHIFT));
printk(" SSE = %lu, SRT = %lu\n",
(esr & ESR_ELx_SSE) >> ESR_ELx_SSE_SHIFT,
(esr & ESR_ELx_SRT_MASK) >> ESR_ELx_SRT_SHIFT);
printk(" SF = %lu, AR = %lu\n", (esr & ESR_ELx_SF) >> ESR_ELx_SF_SHIFT,
(esr & ESR_ELx_AR) >> ESR_ELx_AR_SHIFT);
}

printk(" SET = %lu, FnV = %lu\n", (esr >> ESR_ELx_SET_SHIFT) & 3,
(esr >> ESR_ELx_FnV_SHIFT) & 1);
printk(" EA = %lu, S1PTW = %lu\n", (esr >> ESR_ELx_EA_SHIFT) & 1,
(esr >> ESR_ELx_S1PTW_SHIFT) & 1);
printk(" CM = %lu, WnR = %lu\n", (esr & ESR_ELx_CM) >> ESR_ELx_CM_SHIFT,
(esr & ESR_ELx_WNR) >> ESR_ELx_WNR_SHIFT);
printk(" DFSC = %s\n", esr_get_dfsc_string(esr));
} else {
printk("Not supported yet\n");
}
}

void bad_mode(struct pt_regs *regs, int reason, unsigned int esr) {
printk("Bad mode for %s handler detected, far:0x%x esr:0x%x - %s\n",
bad_mode_handler[reason], read_sysreg(far_el1), esr,
esr_get_class_string(esr));

parse_esr(esr);
}

extern void trigger_sync_data_abort(void);
extern void trigger_sync_instruction_alignment(void);

触发数据异常的代码:

触发同步数据异常