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 位,导致向量表映射错误。
ARM64 异常向量表 2KB 对齐布局 ARM64 异常向量表 2KB 对齐布局(.align 11) 异常向量表基址(VBAR_ELx)— 2KB 对齐 0x000 Sync (EL1t) SP_EL0 0x080 IRQ (EL1t) SP_EL0 0x100 FIQ (EL1t) SP_EL0 0x180 SError (EL1t) SP_EL0 0x200 Sync (EL1h) SP_EL1 0x280 IRQ (EL1h) SP_EL1 0x300 FIQ (EL1h) SP_EL1 0x380 SError (EL1h) SP_EL1 0x400 Sync (EL2t) SP_EL0 0x480 IRQ (EL2t) SP_EL0 0x500 FIQ (EL2t) SP_EL0 0x580 SError (EL2t) SP_EL0 0x600 Sync (EL2h) SP_EL2 0x680 IRQ (EL2h) SP_EL2 0x700 FIQ (EL2h) SP_EL2 0x780 SError (EL2h) SP_EL2 2KB(0x800)— .align 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:
Head.S 异常向量表设置流程 Head.S 异常向量表设置流程 1. 定义 .vectors 段 + .align 11 2. 填充 16 个向量条目(b + .skip) 3. adr x0, vectors 4. msr vbar_el1, x0 + isb

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) 是链接器层面的对齐保证。两者结合可以避免因链接脚本修改或代码移动导致的对齐失效。