最后更新于
最后更新于
在ARMv8架构中,执行级别称为异常级别(ELs),定义了执行环境的特权级别和功能。有四个异常级别,从EL0到EL3,每个都有不同的目的:
EL0 - 用户模式:
这是最低特权级别,用于执行常规应用程序代码。
在EL0上运行的应用程序彼此之间以及与系统软件隔离,增强安全性和稳定性。
EL1 - 操作系统内核模式:
大多数操作系统内核在此级别运行。
EL1比EL0具有更多特权,并且可以访问系统资源,但受一些限制以确保系统完整性。
EL2 - 虚拟化模式:
此级别用于虚拟化。在EL2上运行的虚拟机监视程序可以管理在同一物理硬件上运行的多个操作系统(每个操作系统在其自己的EL1中)。
EL2提供了用于隔离和控制虚拟化环境的功能。
EL3 - 安全监视器模式:
这是最高特权级别,通常用于安全引导和可信执行环境。
EL3可以管理和控制安全和非安全状态之间的访问(例如安全引导、可信操作系统等)。
使用这些级别可以以结构化和安全的方式管理系统的不同方面,从用户应用程序到最高特权系统软件。ARMv8对特权级别的处理有助于有效隔离不同系统组件,从而增强系统的安全性和稳健性。
ARM64有31个通用寄存器,标记为x0
到x30
。每个寄存器可以存储64位(8字节)值。对于需要仅使用32位值的操作,可以使用相同的寄存器以32位模式访问,名称为w0到w30。
x0
到 x7
- 这些通常用作临时寄存器和用于向子例程传递参数。
x0
还携带函数的返回数据
x8
- 在Linux内核中,x8
用作svc
指令的系统调用号。在macOS中使用x16!
x9
到 x15
- 更多临时寄存器,通常用于局部变量。
x16
和 x17
- 过程内调用寄存器。用于立即值的临时寄存器。它们还用于间接函数调用和PLT(过程链接表)存根。
x16
在macOS中用作**svc
指令的系统调用号**。
x18
- 平台寄存器。它可以用作通用寄存器,但在某些平台上,此寄存器保留用于特定于平台的用途:在Windows中用作指向当前线程环境块的指针,在Linux内核中用于指向当前执行任务结构。
x19
到 x28
- 这些是被调用者保存的寄存器。函数必须保留这些寄存器的值供调用者使用,因此它们存储在堆栈中,并在返回给调用者之前恢复。
x29
- 帧指针 用于跟踪堆栈帧。当由于调用函数而创建新的堆栈帧时,x29
寄存器被存储在堆栈中,并且新的帧指针地址(sp
地址)被存储在此寄存器中。
尽管通常用作局部变量的引用,但此寄存器也可以用作通用寄存器。
x30
或 lr
- 链接寄存器。在执行BL
(带链接的分支)或BLR
(带链接到寄存器的分支)指令时,通过将**pc
值存储在此寄存器中来保存返回地址**。
它也可以像其他寄存器一样使用。
如果当前函数将调用新函数,因此覆盖lr
,它将在堆栈顶部存储它,这是尾声(stp x29, x30 , [sp, #-48]; mov x29, sp
-> 存储fp
和lr
,生成空间并获取新的fp
),并在最后恢复它,这是序言(ldp x29, x30, [sp], #48; ret
-> 恢复fp
和lr
并返回)。
sp
- 堆栈指针,用于跟踪堆栈顶部。
sp
值应始终保持至少四字对齐,否则可能会发生对齐异常。
pc
- 程序计数器,指向下一条指令。此寄存器只能通过异常生成、异常返回和分支更新。唯一可以读取此寄存器的普通指令是带链接的分支指令(BL、BLR),以将**pc
地址存储在lr
**(链接寄存器)中。
xzr
- 零寄存器。在其32位寄存器形式中也称为**wzr
。可用于轻松获取零值(常见操作)或使用subs
执行比较,例如subs XZR, Xn, #10
将结果数据存储在任何地方(在xzr
**中)。
Wn
寄存器是Xn
寄存器的32位版本。
此外,还有另外32个长度为128位的寄存器,可用于优化的单指令多数据(SIMD)操作和执行浮点运算。这些称为Vn寄存器,尽管它们也可以在64位、32位、16位和8位上操作,然后称为**Qn
、Dn
、Sn
、Hn
和Bn
**。
有数百个系统寄存器,也称为特殊目的寄存器(SPRs),用于监视和控制 处理器的行为。
它们只能使用专用的特殊指令**mrs
和msr
**来读取或设置。
特殊寄存器**TPIDR_EL0
和TPIDDR_EL0
在逆向工程中经常被发现。EL0
后缀表示可以访问寄存器的最小异常**(在这种情况下,EL0是常规程序运行的异常(特权)级别)。
它们通常用于存储内存中线程本地存储区域的基地址。通常第一个对于在EL0中运行的程序是可读写的,但第二个可以从EL0中读取并从EL1(如内核)中写入。
mrs x0, TPIDR_EL0 ; 将 TPIDR_EL0 读入 x0
msr TPIDR_EL0, X0 ; 将 x0 写入 TPIDR_EL0
PSTATE 包含几个进程组件序列化到操作系统可见的**SPSR_ELx
特殊寄存器中,其中 X 是触发的异常的权限** **级别(这允许在异常结束时恢复进程状态)。
这些是可访问的字段:
N
、Z
、C
和 V
条件标志:
N
表示操作产生了负结果
Z
表示操作产生了零
C
表示操作进行了进位
V
表示操作产生了有符号溢出:
两个正数的和产生负结果。
两个负数的和产生正结果。
在减法中,当从较小的正数(或反之)中减去一个较大的负数,并且结果无法在给定位大小的范围内表示时。
显然,处理器不知道操作是有符号的还是无符号的,因此它将在操作中检查 C 和 V,并指示是否发生了进位。
并非所有指令都会更新这些标志。一些像**CMP
或TST
的指令会更新,而像ADDS
**这样带有 s 后缀的指令也会更新。
当前的寄存器宽度 (nRW
) 标志:如果标志的值为 0,则程序在恢复后将在 AArch64 执行状态下运行。
当前的异常级别(EL
):在 EL0 中运行的常规程序将具有值 0
单步执行标志(SS
):调试器使用此标志进行单步执行,通过异常将 SS 标志设置为 1。程序将运行一步并发出单步执行异常。
非法异常状态标志(IL
):用于标记特权软件执行无效的异常级别转移时,此标志设置为 1,处理器触发非法状态异常。
DAIF
标志:这些标志允许特权程序有选择地屏蔽某些外部异常。
如果 A
为 1,则会触发异步中止。I
配置为响应外部硬件中断请求(IRQs)。而 F 与快速中断请求(FIRs)有关。
堆栈指针选择标志(SPS
):在 EL1 及以上运行的特权程序可以在使用自己的堆栈指针寄存器和用户模型之间进行切换(例如,在 SP_EL1
和 EL0
之间)。这种切换是通过写入**SPSel
**特殊寄存器来执行的。无法从 EL0 中执行此操作。
ARM64 调用约定指定函数的前八个参数通过寄存器**x0
到 x7
传递。额外的参数通过堆栈传递。返回值通过寄存器x0
传回,如果其长度为 128 位,则也可以在x1
中传回。必须在函数调用之间保留x19
到x30
和sp
**寄存器。
在汇编中阅读函数时,查找函数序言和尾声。序言通常涉及保存帧指针(x29
),设置新的帧指针,以及分配堆栈空间。尾声通常涉及恢复保存的帧指针和从函数返回。
ARM64 指令通常具有格式 opcode dst, src1, src2
,其中**opcode
是要执行的操作**(如 add
、sub
、mov
等),dst
是将存储结果的目标寄存器,src1
和 src2
是源寄存器。也可以使用立即值代替源寄存器。
mov
:将一个值从一个寄存器移动到另一个寄存器。
示例:mov x0, x1
— 这将从 x1
移动的值到 x0
。
ldr
:将内存中的值加载到寄存器中。
示例:ldr x0, [x1]
— 这将从 x1
指向的内存位置加载的值到 x0
。
偏移模式:指示影响原始指针的偏移量,例如:
ldr x2, [x1, #8]
,这将在 x1 + 8 中加载 x2 的值
ldr x2, [x0, x1, lsl #2]
,这将在数组 x0 中的位置 x1(索引)* 4 中加载 x2 的对象
预索引模式:这将对原始进行计算,获取结果并将新原始存储在原始中。
ldr x2, [x1, #8]!
,这将在 x1 + 8
中加载 x2
并将 x1 + 8
的结果存储在 x1
中
str lr, [sp, #-4]!
,将链接寄存器存储在 sp 中并更新寄存器 sp
后索引模式:与前一个类似,但是首先访问内存地址,然后计算并存储偏移量。
ldr x0, [x1], #8
,加载 x1
到 x0
并更新 x1
为 x1 + 8
相对于 PC 的寻址:在这种情况下,相对于 PC 寄存器计算要加载的地址
ldr x1, =_start
,这将加载 _start
符号开始的地址与当前 PC 相关的 x1 中。