1. 异常概念总览

1.1 什么是异常?

在 Linux 内核与嵌入式系统中,异常(Exception) 是指处理器在执行指令流时,由于内部或外部事件导致的控制流强制转移。异常是内核响应硬件事件、处理错误、实现虚拟化和任务调度的基础机制。理解异常的分类、触发后的硬件行为与软件行为,是分析寄存器现场恢复与关键值的前提。

核心观点: 异常不是“错误”的同义词——中断、系统调用(陷阱)、缺页(故障)、内核恐慌(终止)都属于异常范畴。寄存器现场恢复的难度与异常类型强相关。

1.2 Linux 内核异常分类(ARM64 / x86_64 视角)

根据处理器架构规范(ARM ARM、Intel SDM)与 Linux 内核实现,异常分为四大类:

异常类别 触发原因 返回行为 典型示例 现场恢复难度
中断 (Interrupt) 外部硬件设备(定时器、网卡、磁盘) 返回到被中断指令的下一条 IRQ、FIQ、NMI 低(保存完整上下文)
陷阱 (Trap) 主动执行特定指令(SVC、syscall) 返回到陷阱指令的下一条 系统调用、断点指令 低(预期内切换)
故障 (Fault) 指令执行过程中可修复的错误 返回到触发故障的指令重新执行 缺页异常、段错误、对齐错误 高(需恢复精确现场)
终止 (Abort) 不可恢复的硬件错误 不返回,系统终止或重启 机器检查、双位错误、看门狗超时 极高(通常无法恢复)
调试要点: 故障类异常(如缺页)是寄存器现场恢复分析的重点——内核必须保存触发指令的 PC 值、页表基址、错误地址(FAR/ESR),才能在修复后精确重试。

1.3 异常触发后的硬件行为(以 ARM64 为例)

当异常发生时,处理器硬件自动执行以下原子操作序列:

  1. 保存 PSTATE 到 SPSR_ELx: 将当前处理器状态(条件标志、中断掩码、执行模式)保存到异常级别的 Saved Program Status Register。
  2. 保存返回地址到 ELR_ELx: 将触发异常的指令地址(或下一条地址,取决于异常类型)保存到 Exception Link Register。
  3. 切换异常级别: 根据异常类型和目标异常向量,硬件自动提升异常级别(EL0→EL1、EL1→EL2 等)。
  4. 禁用中断: 硬件自动设置 PSTATE 中的中断掩码位(DAIF),防止嵌套中断破坏现场。
  5. 跳转到异常向量表: 根据异常类型和来源(同/异步、当前 EL 使用 SP0/SPx),从 VBAR_ELx 指向的向量表中取出对应入口地址并跳转。
关键约束: 硬件只保存 PC 和 PSTATE,不保存通用寄存器(x0-x30)。通用寄存器的保存是软件(异常入口汇编代码)的责任。如果入口代码出错,现场将永久丢失。
异常触发硬件行为流程(ARM64) 异常触发硬件行为流程(ARM64) 异常事件发生 保存 PSTATE → SPSR_ELx 保存返回地址 → ELR_ELx 切换异常级别 + 硬件禁用中断 (DAIF) 跳转 VBAR_ELx + 偏移 → 入口 硬件只保存 PC 和 PSTATE;通用寄存器由软件入口代码保存

1.4 异常触发后的软件行为概述

硬件完成原子保存后,控制权移交给内核异常入口代码(entry.S / vectors.S)。软件行为分为三个阶段:

阶段 操作 关键寄存器/数据结构 风险点
入口 (Entry) 保存所有通用寄存器到栈帧(pt_regs);读取 ESR、FAR 等异常综合征寄存器;切换内核栈 SP_EL0/SP_ELx、pt_regs 结构体、ESR_EL1、FAR_EL1 栈溢出、未保存完整寄存器集
处理 (Handler) 根据异常类型分发到具体处理函数(handle_IRQ、do_page_fault、do_mem_abort) current、thread_info、异常向量表 嵌套异常导致现场覆盖
返回 (Return) 从 pt_regs 恢复所有寄存器;执行 eret(ARM64)或 iret(x86)返回 ELR_ELx、SPSR_ELx、pt_regs 恢复顺序错误、特权级不一致
寄存器现场恢复的核心: pt_regs 结构体是异常入口代码在栈上构建的“寄存器快照”。返回时,内核从 pt_regs 中按序恢复 x0-x30、SP、PC、PSTATE,然后执行 eret。任何 pt_regs 损坏或恢复顺序错误都会导致内核崩溃或安全漏洞。
异常软件处理三阶段 异常软件处理三阶段 入口 (Entry) 保存通用寄存器 读取 ESR / FAR 切换内核栈 处理 (Handler) 异常类型分发 do_page_fault / handle_IRQ 可能触发调度 返回 (Return) 从 pt_regs 恢复 eret / iret 恢复 PSTATE pt_regs 是寄存器现场恢复的核心数据结构,损坏将导致不可预测行为 t0: 异常触发 t1: 处理完成 t2: 现场恢复

1.5 异常分类与现场恢复的关联

不同异常类型对寄存器现场恢复的要求差异显著:

  • 中断: 现场恢复最“宽松”——返回地址是下一条指令,不要求精确的寄存器状态,只需保证内核栈不损坏。
  • 陷阱: 类似中断,但需要正确传递系统调用参数(x0-x5 在 ARM64 中),恢复时需确保参数寄存器不被污染。
  • 故障: 现场恢复最“苛刻”——必须恢复触发指令时的精确寄存器状态,包括 PC、SP、页表基址、错误地址。任何偏差都会导致重试失败或死循环。
  • 终止: 通常不尝试恢复现场,而是触发 panic 或 reboot。但调试时仍需捕获现场用于 post-mortem 分析。
实战建议: 在调试缺页异常或段错误时,优先检查 pt_regs->pc 是否指向触发指令,pt_regs->sp 是否有效,以及 FAR_EL1 是否包含正确的错误虚拟地址。这三个值决定了故障恢复的成败。

1.6 异常向量表结构(ARM64 示例)

ARM64 异常向量表固定 2KB 对齐,包含 16 个入口(4 类 × 4 种来源):

// Linux arch/arm64/kernel/entry.S 中的向量表布局
.align 11          // 2KB 对齐
ENTRY(vectors)
    // 当前 EL 使用 SP_EL0(同步/IRQ/FIQ/SError)
    ventry  el1_sync_invalid       // 同步异常
    ventry  el1_irq_invalid        // IRQ
    ventry  el1_fiq_invalid        // FIQ
    ventry  el1_error_invalid      // SError

    // 当前 EL 使用 SP_ELx(同步/IRQ/FIQ/SError)
    ventry  el1_sync               // 同步异常(缺页、系统调用等)
    ventry  el1_irq                // IRQ 中断
    ventry  el1_fiq_invalid        // FIQ(通常未使用)
    ventry  el1_error_invalid      // SError

    // 从 EL0 来的异常(同步/IRQ/FIQ/SError)
    ventry  el0_sync               // 用户态系统调用、缺页
    ventry  el0_irq                // 用户态中断
    ventry  el0_fiq_invalid        // FIQ
    ventry  el0_error_invalid      // SError

    // 从 EL0 来的 32 位兼容模式异常
    ventry  el0_sync_compat        // 32 位系统调用
    ventry  el0_irq_compat         // 32 位中断
    ventry  el0_fiq_invalid
    ventry  el0_error_invalid
END(vectors)
向量表与现场恢复: 每个 ventry 入口的第一条指令通常是 stp x29, x30, [sp, #-16]! 或 sub sp, sp, #S_FRAME_SIZE,开始构建 pt_regs。向量表选择错误(如同步异常误入 IRQ 入口)会导致 ESR 解析错误,进而恢复错误的寄存器值。

1.7 本章小结