2. ARM64 异常向量表:基址、布局与入口区分
ARM64 架构的异常处理机制是内核异常现场恢复的基石。当 CPU 检测到异常(如中断、缺页、系统调用)时,硬件会自动跳转到预定义的异常向量入口执行。理解异常向量表的基址寄存器 VBAR_EL1、向量表的物理布局以及同步/异步异常的入口区分,是分析异常现场的第一步。
2.1 异常向量表基址:VBAR_EL1
VBAR_EL1(Vector Base Address Register, EL1)是 ARM64 架构中用于存放异常向量表基地址的系统寄存器。该寄存器保存了异常向量表在物理内存或虚拟内存中的起始地址,且要求地址必须 2KB 对齐(即低 11 位为 0)。
- 作用:CPU 在发生异常时,根据异常类型和来源,从
VBAR_EL1指向的基址加上固定偏移量,计算出异常处理入口地址。 - 初始化时机:Linux 内核在启动早期(head.S)中设置
VBAR_EL1,指向内核预定义的异常向量表__entry_tramp_text_start或vectors符号。 - 多级异常级别:EL2(虚拟化)、EL3(安全监控)也有各自的
VBAR_EL2、VBAR_EL3,但内核最常用的是VBAR_EL1。
rdmsr VBAR_EL1 或读取 sys_regs 结构体,可以确认异常向量表是否被篡改或损坏,这是排查异常入口异常的关键步骤。
2.2 异常向量表布局:2KB 对齐的 16 个入口
ARM64 异常向量表固定大小为 2KB,共包含 16 个异常向量入口,每个入口占用 128 字节(32 条指令)。这 16 个入口按照异常类型(同步/异步)和异常来源(当前 EL 使用 SP0/SPx、低异常级别 AArch64/AArch32)进行排列。
| 偏移量(十六进制) | 异常类型 | 异常来源描述 | 典型触发场景 |
|---|---|---|---|
| 0x000 | 同步异常 | 当前 EL,使用 SP_EL0 | EL0 下的系统调用(SVC) |
| 0x080 | IRQ(异步) | 当前 EL,使用 SP_EL0 | EL0 下的普通中断 |
| 0x100 | FIQ(异步) | 当前 EL,使用 SP_EL0 | 快速中断(通常未使用) |
| 0x180 | SError(异步) | 当前 EL,使用 SP_EL0 | 内存访问错误、总线错误 |
| 0x200 | 同步异常 | 当前 EL,使用 SP_ELx | EL1 内核态下的缺页、数据中止 |
| 0x280 | IRQ(异步) | 当前 EL,使用 SP_ELx | EL1 内核态下的中断 |
| 0x300 | FIQ(异步) | 当前 EL,使用 SP_ELx | EL1 快速中断 |
| 0x380 | SError(异步) | 当前 EL,使用 SP_ELx | EL1 系统错误 |
| 0x400 | 同步异常 | 低异常级别 AArch64 | EL0 64 位应用触发同步异常 |
| 0x480 | IRQ(异步) | 低异常级别 AArch64 | EL0 64 位应用被中断 |
| 0x500 | FIQ(异步) | 低异常级别 AArch64 | EL0 64 位快速中断 |
| 0x580 | SError(异步) | 低异常级别 AArch64 | EL0 64 位系统错误 |
| 0x600 | 同步异常 | 低异常级别 AArch32 | EL0 32 位应用触发同步异常 |
| 0x680 | IRQ(异步) | 低异常级别 AArch32 | EL0 32 位应用被中断 |
| 0x700 | FIQ(异步) | 低异常级别 AArch32 | EL0 32 位快速中断 |
| 0x780 | SError(异步) | 低异常级别 AArch32 | EL0 32 位系统错误 |
2.3 同步异常与异步异常的入口区分
在 ARM64 异常向量表中,同步异常(Synchronous)和异步异常(Asynchronous)的入口通过 偏移量最低 7 位 进行区分:
- 同步异常:偏移量低 7 位为
0x00(即偏移量 0x000、0x200、0x400、0x600)。 - IRQ(异步):偏移量低 7 位为
0x80(即偏移量 0x080、0x280、0x480、0x680)。 - FIQ(异步):偏移量低 7 位为
0x100(即偏移量 0x100、0x300、0x500、0x700)。 - SError(异步):偏移量低 7 位为
0x180(即偏移量 0x180、0x380、0x580、0x780)。
这种设计使得 CPU 硬件可以通过异常类型和当前异常级别快速计算入口地址,无需复杂的查表操作。内核在 entry.S 中为每个入口编写了对应的处理存根(stub),这些存根负责保存现场、识别具体异常原因并分发到高层处理函数。
2.4 异常向量表结构 SVG 图
下图展示了 ARM64 异常向量表的完整布局,包括 16 个入口的偏移量、异常类型以及来源分组关系。
2.5 内核代码中的向量表定义
在 Linux 内核源码 arch/arm64/kernel/entry.S 中,异常向量表通过 .align 11(2KB 对齐)和 __entry_vectors 标签定义。以下是简化后的向量表结构:
.align 11
ENTRY(__entry_vectors)
// 当前 EL, SP_EL0
ventry el1_sync_invalid // 0x000 同步异常
ventry el1_irq_invalid // 0x080 IRQ
ventry el1_fiq_invalid // 0x100 FIQ
ventry el1_error_invalid // 0x180 SError
// 当前 EL, SP_ELx
ventry el1_sync // 0x200 同步异常
ventry el1_irq // 0x280 IRQ
ventry el1_fiq_invalid // 0x300 FIQ
ventry el1_error_invalid // 0x380 SError
// 低异常级别 AArch64
ventry el0_sync // 0x400 同步异常
ventry el0_irq // 0x480 IRQ
ventry el0_fiq_invalid // 0x500 FIQ
ventry el0_error_invalid // 0x580 SError
// 低异常级别 AArch32
ventry el0_sync_compat // 0x600 同步异常
ventry el0_irq_compat // 0x680 IRQ
ventry el0_fiq_invalid // 0x700 FIQ
ventry el0_error_invalid // 0x780 SError
END(__entry_vectors)
其中 ventry 是一个宏,用于生成跳转指令(b 或 br),跳转到对应的异常处理存根。每个存根会执行以下操作:
- 分配栈帧(
sub sp, sp, #S_FRAME_SIZE)。 - 保存通用寄存器(
stp x0, x1, [sp, #S_X0]等)。 - 保存异常原因寄存器
ESR_EL1和返回地址ELR_EL1。 - 调用 C 语言异常处理函数(如
do_mem_abort、el0_svc_handler)。
ELR_EL1(返回地址)和 SPSR_EL1(保存的 PSTATE)。如果这些值被破坏,将导致无法正确返回到异常发生前的执行流,从而引发内核崩溃。
2.6 同步与异步异常的处理差异
| 特性 | 同步异常 | 异步异常 |
|---|---|---|
| 触发源 | 当前指令执行直接导致(如 SVC、数据中止、未定义指令) | 外部事件或硬件错误(如外设中断、总线错误) |
| 返回地址 (ELR_EL1) | 指向触发异常的指令(需重试或修正) | 指向下一条未执行的指令(可继续执行) |
| 可重入性 | 通常不可重入(同一异常级别下) | 可嵌套(通过中断优先级和掩码控制) |
| 现场保存要求 | 必须保存完整现场,包括 ESR_EL1 异常原因 | 需保存现场,但异常原因寄存器可能不关键 |
| 典型处理流程 | 识别异常原因 → 修复或终止 → 返回到异常指令 | 保存现场 → 处理中断 → 返回到下一条指令 |
在寄存器现场恢复时,同步异常需要特别关注 ELR_EL1 是否指向正确的指令地址,以及是否需要修改 ESR_EL1 中的异常原因。异步异常则更关注中断控制器状态和嵌套中断的栈管理。
ESR_EL1 值为 0x96000021(数据中止,异常级别 EL1,写操作),且 ELR_EL1 指向内核代码段,则说明是内核态访问非法地址导致的同步异常。此时需要检查 FAR_EL1(错误地址寄存器)来定位具体访问了哪个地址。