ARM64架构基础:寄存器模型、指令集概览、异常级别与安全世界

各位好,欢迎来到这门课的第一章。说实话,ARM64这玩意儿,我最早接触是在做手机芯片性能调优的时候。那时候刚上手,看着满屏的寄存器编号,头都大了。后来摸爬滚打几年,才慢慢理清这套架构的设计逻辑。

这一章,咱们不搞虚的。直接看ARM64最核心的三个东西:寄存器怎么用、指令怎么读、异常级别和安全世界怎么划分。搞懂这些,后面逆向分析和调优才能站得住脚。

1.1 寄存器模型:别被31个通用寄存器吓到

ARM64有31个通用寄存器,编号从X0到X30。每个都是64位宽。你可能会问:“为什么是31个,不是32个?”嗯,这里有个小故事——X31其实被用作栈指针SP或者零寄存器XZR,取决于上下文。

寄存器 别名/用途 调用约定
X0-X7 参数寄存器 / 返回值 传参、返回值放在X0
X8 间接结果寄存器 系统调用时用
X9-X15 临时寄存器 调用者保存
X16-X17 IP0/IP1(内部过程调用) PLT跳板用
X18 平台寄存器 不要乱动
X19-X28 被调用者保存 函数内必须恢复
X29 帧指针FP 指向栈帧底部
X30 链接寄存器LR 保存返回地址
个人习惯:我在逆向时,第一件事就是看X0和X30。X0告诉你函数返回值,X30告诉你谁调了它。这两个寄存器,基本能帮你快速定位函数调用链。

除了通用寄存器,还有几个特殊寄存器你得记住:

  • SP:栈指针,指向当前栈顶。注意,ARM64里SP必须16字节对齐,否则访问会触发异常。
  • PC:程序计数器,指向当前正在执行的指令。ARM64不允许直接写PC,只能通过分支指令跳转。
  • NZCV:条件标志位,放在一个叫CPSR(当前程序状态寄存器)的东西里。N负数、Z零、C进位、V溢出。

说到NZCV,我踩过一个坑。有一次调试一段加密算法,发现条件分支总是不对。查了半天,原来是前一条加法指令改了C标志,而我以为它没改。你想想看,ARM64里很多指令都会隐式更新标志位,不像x86那么明显。所以,读汇编时,一定要留意每条指令对标志位的影响。

1.2 指令集概览:精简但够用

ARM64的指令集,说白了就是“精简”二字。每条指令固定32位,没有x86那种变长指令的烦恼。这对逆向分析来说,简直是福音——你不需要猜指令边界。

我把常用指令分成几类,列个表方便你对照:

类别 示例指令 说明
数据传输 LDR X0, [X1] 从内存加载到寄存器
STR X0, [X1] 从寄存器存储到内存
算术运算 ADD X0, X1, X2 X0 = X1 + X2
SUB X0, X1, #42 X0 = X1 - 42
逻辑运算 AND X0, X1, X2 按位与
ORR X0, X1, X2 按位或
分支跳转 B label 无条件跳转
BL func 带链接的跳转(调用函数)
RET 返回,等价于 BR X30
内存访问 STP X0, X1, [SP, #-16]! 成对压栈,带前索引
LDP X0, X1, [SP], #16 成对出栈,带后索引
重点:ARM64里没有“push/pop”指令。压栈出栈全靠STP和LDP。这是和x86最大的区别之一。我在做性能调优时,经常看到编译器生成的STP指令,一次压两个寄存器,效率很高。

举个例子,一个简单的函数调用,汇编长这样:

// 函数 prologue
func:
    STP X29, X30, [SP, #-16]!   // 保存FP和LR
    MOV X29, SP                  // 设置帧指针
    // ... 函数体 ...
    LDP X29, X30, [SP], #16     // 恢复FP和LR
    RET                          // 返回

你看,STP和LDP配合前索引/后索引,一次完成压栈和栈指针调整。这种设计,说白了就是为了减少指令数量,提高执行效率。

1.3 异常级别与安全世界:四个级别的权限游戏

ARM64定义了四个异常级别(Exception Level),从EL0到EL3。数字越大,权限越高。这玩意儿,我刚开始学的时候觉得抽象,后来做系统级调试才体会到它的重要性。

ARM64 异常级别与安全世界架构 EL3:安全监视器(Secure Monitor) EL2:虚拟机监视器(Hypervisor) EL1:操作系统内核 EL1:安全内核(TrustZone) EL0:用户态应用 EL0:安全应用 非安全世界(Normal World) 安全世界(Secure World) 异常级别上升(权限增加) SMC指令:在非安全世界和安全世界之间切换,必须经过EL3 HVC指令:在EL1和EL2之间切换,用于虚拟化场景

这张图,我建议你多看几遍。它解释了为什么你的手机能同时跑Android和指纹支付——安全世界(Secure World)跑敏感操作,非安全世界(Normal World)跑普通应用,中间通过EL3的监视器切换。

每个异常级别的切换,都依赖特定的指令:

  • SMC(安全监视器调用):从非安全世界进入安全世界,必须经过EL3。
  • HVC(虚拟机监视器调用):在EL1和EL2之间切换,虚拟化场景用。
  • SVC(系统调用):用户态(EL0)请求内核(EL1)服务。
我曾经踩过的坑:有一次逆向一个安全支付模块,发现它调用了SMC指令。我当时没注意异常级别切换,直接按普通函数调用去分析,结果死活找不到返回地址。后来才意识到,SMC会切换到EL3,返回路径完全不一样。所以,看到SMC/HVC/SVC,一定要停下来想想当前在哪个异常级别。

1.4 本章小结:打好地基

这一章的内容,说白了就是ARM64的“世界观”。寄存器模型告诉你数据在哪,指令集告诉你怎么操作数据,异常级别和安全世界告诉你谁有权限操作。

我个人觉得,学ARM64逆向,最忌讳的就是一上来就盯着指令看。先搞清楚上下文——当前在EL0还是EL1?X30里存的是什么?栈指针对齐了没有?这些基础问题搞定了,后面分析函数调用、做性能调优,才能事半功倍。

嗯,这一章就到这里。记住我上面说的那几个关键点,后面咱们开始真正动手分析函数调用。