4. Head.S 中异常向量表的定义:.align 11 对齐、.section .vectors 声明与标签定义
在 ARM64 启动的 Head.S 阶段,异常向量表(Exception Vector Table, EVT)是 CPU 响应异步异常(中断、FIQ、SError、同步异常)的入口映射。向量表必须放置在物理地址 0x0 或由 VBAR_ELx 寄存器指定的基址上,且严格遵循 2KB(0x800) 对齐要求。本章详细解析向量表的定义方式:.align 11 对齐、.section .vectors 段声明以及标签命名规范。
核心要点: ARM64 异常向量表共 16 个条目,每个条目占用 128 字节(0x80),总大小 2KB。对齐要求为 2^11 = 2048 字节,即
.align 11。
4.1 异常向量表的结构与对齐规则
ARM64 架构定义了四种异常类型,每种类型又分为四个偏移等级,形成 4×4=16 个向量条目。每个条目固定 128 字节,用于存放跳转指令或异常处理入口。向量表基址必须 2KB 对齐,这是由硬件取指逻辑决定的——VBAR_ELx 寄存器的低 11 位被硬件忽略。
| 偏移量(Offset) | 异常类型 | 异常等级 | 描述 |
|---|---|---|---|
| 0x000 | Synchronous | EL1t(SP0) | 同步异常,使用 SP_EL0 |
| 0x080 | IRQ | EL1t(SP0) | 普通中断,使用 SP_EL0 |
| 0x100 | FIQ | EL1t(SP0) | 快速中断,使用 SP_EL0 |
| 0x180 | SError | EL1t(SP0) | 系统错误,使用 SP_EL0 |
| 0x200 | Synchronous | EL1h(SPx) | 同步异常,使用 SP_EL1 |
| 0x280 | IRQ | EL1h(SPx) | 普通中断,使用 SP_EL1 |
| 0x300 | FIQ | EL1h(SPx) | 快速中断,使用 SP_EL1 |
| 0x380 | SError | EL1h(SPx) | 系统错误,使用 SP_EL1 |
| 0x400 | Synchronous | EL2t(SP0) | 同步异常,EL2 使用 SP_EL0 |
| 0x480 | IRQ | EL2t(SP0) | 普通中断,EL2 使用 SP_EL0 |
| 0x500 | FIQ | EL2t(SP0) | 快速中断,EL2 使用 SP_EL0 |
| 0x580 | SError | EL2t(SP0) | 系统错误,EL2 使用 SP_EL0 |
| 0x600 | Synchronous | EL2h(SPx) | 同步异常,EL2 使用 SP_EL2 |
| 0x680 | IRQ | EL2h(SPx) | 普通中断,EL2 使用 SP_EL2 |
| 0x700 | FIQ | EL2h(SPx) | 快速中断,EL2 使用 SP_EL2 |
| 0x780 | SError | EL2h(SPx) | 系统错误,EL2 使用 SP_EL2 |
对齐检查: 在 Head.S 中,使用
.align 11 确保向量表起始地址的低 11 位为 0。如果链接脚本将向量表放置在非对齐地址,CPU 在设置 VBAR_ELx 时会忽略低 11 位,导致向量表映射错误。
4.2 使用 .section .vectors 声明向量段
在 Head.S 中,异常向量表通常被放置在一个独立的段(section)中,名为 .vectors。这样做的好处是:链接脚本可以精确控制向量表的加载地址,确保其满足 2KB 对齐要求,同时避免与普通代码段混合导致对齐失效。
.section .vectors, "ax" @ 定义 .vectors 段,a=可分配,x=可执行
.align 11 @ 2KB 对齐(2^11 = 2048)
.globl vectors @ 导出全局符号,供 VBAR_ELx 设置使用
vectors:
/* EL1t 向量(使用 SP_EL0) */
.align 7 @ 每个条目 128 字节对齐(2^7 = 128)
b el1_sync_sp0 @ 0x000: 同步异常
.align 7
b el1_irq_sp0 @ 0x080: IRQ
.align 7
b el1_fiq_sp0 @ 0x100: FIQ
.align 7
b el1_serror_sp0 @ 0x180: SError
/* EL1h 向量(使用 SP_EL1) */
.align 7
b el1_sync_sp1 @ 0x200: 同步异常
.align 7
b el1_irq_sp1 @ 0x280: IRQ
.align 7
b el1_fiq_sp1 @ 0x300: FIQ
.align 7
b el1_serror_sp1 @ 0x380: SError
/* EL2t 向量(使用 SP_EL0) */
.align 7
b el2_sync_sp0 @ 0x400: 同步异常
.align 7
b el2_irq_sp0 @ 0x480: IRQ
.align 7
b el2_fiq_sp0 @ 0x500: FIQ
.align 7
b el2_serror_sp0 @ 0x580: SError
/* EL2h 向量(使用 SP_EL2) */
.align 7
b el2_sync_sp1 @ 0x600: 同步异常
.align 7
b el2_irq_sp1 @ 0x680: IRQ
.align 7
b el2_fiq_sp1 @ 0x700: FIQ
.align 7
b el2_serror_sp1 @ 0x780: SError
对齐陷阱: 每个向量条目内部的
.align 7 是冗余但安全的做法。如果前一个 b 指令占用了 4 字节,.align 7 会填充到下一个 128 字节边界。但注意:b 指令是 4 字节,而条目大小是 128 字节,因此每个条目末尾有 124 字节的填充空间,通常用 .fill 或 .skip 显式填充。
4.3 标签定义与命名规范
每个异常向量入口对应一个全局或局部标签,标签命名应清晰反映异常类型、异常等级和使用的栈指针。推荐使用以下命名模式:
- 格式:
[异常等级]_[异常类型]_[栈指针] - 示例:
el1_sync_sp0、el2_irq_sp1、el1_serror_sp1 - 全局导出: 使用
.globl导出向量表基址标签vectors,以便在 C 代码或链接脚本中引用。
.globl vectors
.type vectors, %object
.section .vectors, "ax"
.align 11
vectors:
/* EL1t - 同步异常 */
.align 7
el1_sync_sp0:
b el1_sync_handler @ 跳转到实际处理函数
.skip 124, 0 @ 填充剩余 124 字节,保持 128 字节条目
/* EL1t - IRQ */
.align 7
el1_irq_sp0:
b el1_irq_handler
.skip 124, 0
/* ... 其余条目类似 ... */
标签作用域: 如果向量处理函数只在 Head.S 内部使用,可以使用局部标签(如
1:、2:)以减少符号表污染。但为了调试和可读性,建议使用有意义的全局标签。
4.4 向量表初始化与 VBAR_ELx 设置
在 Head.S 启动流程中,完成异常向量表定义后,必须将向量表基址写入当前异常等级的 VBAR_ELx 寄存器。典型代码如下:
/* 设置 VBAR_EL1 指向 vectors */
adr x0, vectors
msr vbar_el1, x0
isb @ 指令同步屏障,确保设置生效
/* 如果运行在 EL2,还需要设置 VBAR_EL2 */
mrs x1, currentel
lsr x1, x1, #2
and x1, x1, #0x3
cmp x1, #2 @ 检查是否在 EL2
b.ne 1f
adr x0, vectors
msr vbar_el2, x0
isb
1:
4.5 常见错误与调试建议
| 错误场景 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 向量表未 2KB 对齐 | 异常触发后 PC 跳转到错误地址 | .align 11 缺失或链接脚本放置地址未对齐 |
检查 .align 11 和链接脚本中 .vectors 段的 ALIGN(0x800) |
| 向量条目大小不是 128 字节 | 异常向量覆盖或跳转到错误处理函数 | 缺少 .align 7 或 .skip 填充不足 |
每个条目后使用 .skip 124, 0 确保固定大小 |
VBAR_ELx 设置后未执行 ISB |
异常向量表更新未生效,仍使用旧表 | 缺少指令同步屏障 | 在 msr vbar_elx, x0 后立即添加 isb |
| 标签命名冲突 | 链接时报多重定义错误 | 多个源文件定义了相同标签 | 使用 .globl 控制导出范围,或使用局部标签 |
调试技巧: 在 QEMU 或实际硬件上启动时,可以通过检查
VBAR_EL1 寄存器的值(使用 mrs x0, vbar_el1)来验证向量表基址是否正确。如果基址低 11 位不为 0,说明对齐失败。
4.6 向量表与链接脚本的协同
为了确保 .vectors 段被放置在正确的地址,链接脚本(如 link.lds)需要显式定义该段的起始地址和对齐方式:
SECTIONS
{
. = 0x80000; /* 典型内核起始地址 */
.vectors : ALIGN(0x800) /* 强制 2KB 对齐 */
{
*(.vectors) /* 包含所有 .vectors 段 */
}
.text : {
*(.text*)
}
/* ... 其他段 ... */
}
通过这种方式,链接器在布局时会自动将 .vectors 段对齐到 2KB 边界,与 Head.S 中的 .align 11 形成双重保障。
最佳实践: 在 Head.S 中使用
.align 11 是软件层面的对齐保证,在链接脚本中使用 ALIGN(0x800) 是链接器层面的对齐保证。两者结合可以避免因链接脚本修改或代码移动导致的对齐失效。