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 | 保存返回地址 |
除了通用寄存器,还有几个特殊寄存器你得记住:
- 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 |
成对出栈,带后索引 |
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。数字越大,权限越高。这玩意儿,我刚开始学的时候觉得抽象,后来做系统级调试才体会到它的重要性。
这张图,我建议你多看几遍。它解释了为什么你的手机能同时跑Android和指纹支付——安全世界(Secure World)跑敏感操作,非安全世界(Normal World)跑普通应用,中间通过EL3的监视器切换。
每个异常级别的切换,都依赖特定的指令:
- SMC(安全监视器调用):从非安全世界进入安全世界,必须经过EL3。
- HVC(虚拟机监视器调用):在EL1和EL2之间切换,虚拟化场景用。
- SVC(系统调用):用户态(EL0)请求内核(EL1)服务。
1.4 本章小结:打好地基
这一章的内容,说白了就是ARM64的“世界观”。寄存器模型告诉你数据在哪,指令集告诉你怎么操作数据,异常级别和安全世界告诉你谁有权限操作。
我个人觉得,学ARM64逆向,最忌讳的就是一上来就盯着指令看。先搞清楚上下文——当前在EL0还是EL1?X30里存的是什么?栈指针对齐了没有?这些基础问题搞定了,后面分析函数调用、做性能调优,才能事半功倍。
嗯,这一章就到这里。记住我上面说的那几个关键点,后面咱们开始真正动手分析函数调用。