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 为例)
当异常发生时,处理器硬件自动执行以下原子操作序列:
- 保存 PSTATE 到 SPSR_ELx: 将当前处理器状态(条件标志、中断掩码、执行模式)保存到异常级别的 Saved Program Status Register。
- 保存返回地址到 ELR_ELx: 将触发异常的指令地址(或下一条地址,取决于异常类型)保存到 Exception Link Register。
- 切换异常级别: 根据异常类型和目标异常向量,硬件自动提升异常级别(EL0→EL1、EL1→EL2 等)。
- 禁用中断: 硬件自动设置 PSTATE 中的中断掩码位(DAIF),防止嵌套中断破坏现场。
- 跳转到异常向量表: 根据异常类型和来源(同/异步、当前 EL 使用 SP0/SPx),从 VBAR_ELx 指向的向量表中取出对应入口地址并跳转。
关键约束: 硬件只保存 PC 和 PSTATE,不保存通用寄存器(x0-x30)。通用寄存器的保存是软件(异常入口汇编代码)的责任。如果入口代码出错,现场将永久丢失。
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 损坏或恢复顺序错误都会导致内核崩溃或安全漏洞。
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 解析错误,进而恢复错误的寄存器值。