ARM64异常处理技术
ARM64异常处理技术
一、异常级别与分类
1.异常级别
ARM64处理器异常定义:4个异常级别(0~3)。异常级别越高,权限越高。
1. 异常级别0 (EL0) - 正常世界
- 描述 :EL0是用户模式,运行用户应用程序。在这个级别上,应用程序没有直接访问硬件资源的权限,只能通过系统调用请求操作系统服务。
- 例子 :
- 应用1、应用2、应用3、应用4 :这些是运行在EL0级别的普通用户应用程序,如浏览器、文本编辑器、游戏等。
- 客户操作系统 :在某些情况下,客户操作系统(如虚拟机中的操作系统)也可能运行在EL0级别,但这通常取决于具体的虚拟化配置。
2. 异常级别1 (EL1) - 正常世界
- 描述 :EL1是操作系统内核模式,运行操作系统内核和特权级软件。在这个级别上,操作系统可以管理硬件资源、调度进程、处理中断等。
- 例子 :
- 客户操作系统 :在没有虚拟化的情况下,操作系统内核运行在EL1级别。例如,Linux内核、Windows内核等。
- 可信操作系统 :在安全世界中,运行在EL1级别的操作系统通常具有额外的安全特性,如TEE(可信执行环境)操作系统。
3. 异常级别2 (EL2) - 正常世界
- 描述 :EL2是虚拟机监控器(Hypervisor)模式,运行虚拟机监控器(如KVM、Xen)。在这个级别上,虚拟机监控器可以管理多个虚拟机,每个虚拟机运行自己的操作系统。
- 例子 :
- 虚拟机监控器 :如KVM、Xen等虚拟化软件运行在EL2级别,管理多个虚拟机。每个虚拟机中的操作系统(如客户操作系统)运行在EL1级别。
4. 异常级别3 (EL3) - 安全世界
- 描述 :EL3是安全监控器模式,运行安全监控器(如ARM TrustZone的Secure Monitor)。在这个级别上,安全监控器可以管理安全世界和正常世界之间的切换,提供安全服务。
- 例子 :
- 安全监控器 :如ARM TrustZone的Secure Monitor运行在EL3级别,管理安全世界和正常世界之间的切换。安全世界中的软件(如安全固件、可信操作系统)运行在EL1级别。
通常ARM64处理器在异常级别0执行进程,在异常级别1执行内核。ARM64处理器的异常级别0就是我们所讲的用户模式,异常级别1就是我们所说的内核模式。
虚拟机是目前流行的虚拟化技术,在计算机上创建一个虚拟机,在虚拟机里面运行一个操作系统,运行虚拟机的操作系统称为宿主操作系统(host OS),虚拟机里面的操作系统称为客户操作系统(guest OS)。常用的开源虚拟机管理软件是 QEMU,QEMU 支持基于内核的虚拟机(KVM,Kernel-based Virtual Machine)。KVM 的主要特点是直接在处理器上执行客户操作系统,所以虚拟机的执行速度很快。KVM 是内核的一个模块,把内核变成虚拟监控程序。
宿主操作系统中的进程在异常级别 0 运行,内核在异常级别 1 运行,KVM 模块可以穿越异常级别 1 和 2;客户操作系统中的进程在异常级别 0 运行,内核在异常级别 1 运行。在宿主操作系统的异常级别 1 和客户操作系统异常级别 1 之间切换时,需要 KVM 陷入异常级别 2。
为了提高切换速度,ARM64 架构引入虚拟化宿主扩展,在异常级别 2 执行宿主操作系统的内核,从 QEMU 切换到客户操作系统的时候,KVM 不再需要从异常级别 1 切换到异常级别 2 。
ARM64 架构安全扩展定义两种安全状态:正常世界和安全世界。这两个世界只能通过异常级别 3 的安全监控器切换。
1. ARM64 异常级别与系统组件运行逻辑
异常级别(EL)的分层 :
ARM64 通过异常级别(EL0-EL3)划分权限,EL0 权限最低(用户态),EL3 最高。高权限级别可管理低级别操作,低级别无法越界。
宿主与客户系统的运行层级 :
- 宿主操作系统 :进程在 EL0(用户态)运行,内核在 EL1(内核态)。KVM 作为内核模块,具备在 EL1 和 EL2 间切换的能力。
- 客户操作系统 :虚拟机内的进程同样在 EL0,其内核在 EL1。当宿主内核(EL1)与客户内核(EL1)切换时,需借助 KVM 在 EL2 完成过渡,因为不同系统的 EL1 无法直接交互。
2. ARM64 虚拟化宿主扩展的优化
传统切换的开销 :
未优化时,从 QEMU(宿主侧管理工具)切换到客户操作系统,KVM 需在 EL1 和 EL2 间反复跳转,影响效率。
虚拟化宿主扩展的作用 :
让宿主操作系统内核直接在 EL2 执行。切换时,KVM 无需再在 EL1 和 EL2 间切换,减少层级跳转,提升虚拟机切换速度。
3. ARM64 安全扩展:正常世界与安全世界
两种安全状态 :
- 正常世界 :运行普通业务,如日常操作系统、应用程序。
- 安全世界 :处理安全敏感任务,如安全启动、密钥存储、可信执行环境(TEE)。
切换机制 :
两者通过 EL3 的安全监控器切换。EL3 是最高权限级别,安全监控器作为 “守门人”,确保只有授权操作才能在两个世界间流转,保障系统安全,防止正常世界的非安全操作侵入安全世界。
2.异常分类
1.同步异常包括:
a. 系统调用:异常级别 0 使用 svc 指令陷入异常级别 1,异常级别 1 使用 hvc 指令陷入异常级别 2,异常级别 2 使用 smc 指令陷入异常级别 3。
b. 数据中止:访问数据时的页错误异常,虚拟地址没有映射到物理地址,或者没有写权限。
c. 指令中止:取指令时的页错误异常,虚拟地址没有映射到物理地址,或者没有执行权限。
d. 栈指针或指令地址没有对齐、没有定义的指令、调度异常。
2.异步异常包括:
a. 中断(IRQ, normal priority interrupt),即普通优先级的中断;
b. 快速中断(FIQ, fast interrupt),即高优先级的中断;
c. 系统错误(SE, System error),是由硬件错误触发的异常,比如最常见把脏数据从缓存行写回内存时触发异步的数据中止异常。
二、异常向量表与异常处理
1.异常向量表
当异常发生的时候,处理器需要执行异常的处理程序。存储异常处理程序的内存位置成为异常向量,通常把所有异常存放在一张表中,称为异常向量表。
ARM64 处理器的异常级别 1 2 3,每个异常级别都有自己的异常向量表,异常向量表的起始虚拟地址存放在寄存器 VBAR_ELn(向量基准地址寄存器,Vector Based Address Register)中。每个异常向量表有 16 项,分为 4 组,每组 4 项,每项的长度是 128 字节(可以存放 32 条指令)。也就是说,每个异常级别最多16个异常处理函数,而每个函数最多包含32条指令。
1. 第一组(0x000 - 0x180)
- 处理对象 :当前异常级别(EL)生成的异常。
- 栈指针 :使用异常级别 0 的栈指针(
SP_EL0
)。- 典型场景 :当异常在当前 EL 触发时,优先使用该组向量处理,且依赖 EL0 的栈环境。
2. 第二组(0x200 - 0x380)
- 处理对象 :当前异常级别(EL)生成的异常。
- 栈指针 :使用当前异常级别的栈指针(
SP_ELx
,x 为当前 EL 编号)。- 典型场景 :异常在当前 EL 触发,但需使用当前 EL 专属栈(而非 EL0 栈),适用于对栈隔离要求更高的场景。
3. 第三组(0x400 - 0x580)
- 处理对象 :64 位应用程序在更低异常级别(Lower EL,如 EL0)生成的异常。
- 上下文 :专门针对 64 位应用(AArch64 架构)的异常处理,明确区分应用位数。
- 典型场景 :64 位程序在低级别 EL 触发异常时,通过该组向量处理,确保 64 位环境适配。
4. 第四组(0x600 - 0x780)
- 处理对象 :32 位应用程序在更低异常级别(Lower EL)生成的异常。
- 上下文 :针对 32 位应用(AArch32 架构)的异常处理,适配 32 位指令集环境。
- 典型场景 :32 位程序在低级别 EL 触发异常时,通过该组向量处理,满足 32 位应用的兼容性需求。
ARM64架构内存定义的异常级别1的异常向量表如下:
从异常级别1的异常向量表中可以看出:有些异常向量的跳转标号带有"invalid",说明内核不支持这些异常。
2.异常处理
当处理器取出异常处理的时候,自动执行如下操作:
把当前处理器的状态(Processor State,PSTATE)保存在寄存器SPSR_EL1(保存程序状态寄存器)中。
对于64位应用程序在用户模式(异常级别0)下生成的同步异常,入口是el0_sync:
对于内核模式(异常级别1)生成的同步异常,入口是el1_sync: