四种架构函数调用 ABI 与栈帧布局对比
架构:x86-64(System V ABI)、AArch64(AAPCS64)、RISC-V64(LP64 ABI)、32-bit MIPS(O32 ABI)
本文以 Linux 常见 ABI 为准:
| 架构 | 常见 ABI |
|---|---|
| x86-64 | System V AMD64 ABI |
| AArch64 | ARM64 Procedure Call Standard (AAPCS64) |
| RISC-V64 | RISC-V ELF psABI (LP64) |
| MIPS32 | MIPS O32 ABI |
1. 总体设计差异
| 项目 | x86-64 | AArch64 | RISC-V64 | MIPS32 |
|---|---|---|---|---|
| 指令集 | CISC | RISC | RISC | RISC |
| 通用寄存器数量 | 16 × 64bit | 31 × 64bit | 32 × 64bit | 32 × 32bit |
| 栈增长方向 | 向低地址 | 向低地址 | 向低地址 | 向低地址 |
| 栈指针对齐 | 16 bytes | 16 bytes | 16 bytes | 8 bytes |
| 返回地址保存 | 寄存器 | 寄存器 | 寄存器 | 寄存器 |
| 默认参数传递 | 寄存器优先 | 寄存器优先 | 寄存器优先 | 寄存器优先 |
| caller/callee 保存划分 | 明确 | 明确 | 明确 | 明确 |
主要区别:
-
x86-64
- 历史包袱最大
- 栈帧高度依赖编译器
- 返回地址由
call自动压栈 -
ARM64 / RISC-V / MIPS
-
RISC 架构
call本质是跳转 + 保存返回地址到寄存器- 是否保存返回地址由函数决定
2. x86-64 System V ABI
2.1 寄存器分类
参数寄存器
整数参数:
| 参数 | 寄存器 |
|---|---|
| 1 | RDI |
| 2 | RSI |
| 3 | RDX |
| 4 | RCX |
| 5 | R8 |
| 6 | R9 |
浮点:
超过数量:
2.2 caller / callee 保存
Caller-saved
调用者负责保存:
特点:
函数调用后可能被破坏。
Callee-saved
被调用函数必须恢复:
2.3 栈帧布局
典型:
高地址
│
│ caller frame
│
├──────────────
│ 参数7+
│ 参数6+
├──────────────
│ 返回地址 RIP
├──────────────
│ old RBP
├──────────────
│ saved RBX/R12...
├──────────────
│ local variables
├──────────────
│ temporary
│
└──────────────
低地址
2.4 函数进入
汇编:
执行后:
2.5 返回地址特点
x86:
硬件:
所以:
3. AArch64 AAPCS64
3.1 寄存器
通用:
特殊:
| 寄存器 | 用途 |
|---|---|
| X0-X7 | 参数 |
| X8 | 间接返回地址 |
| X9-X15 | caller saved |
| X16-X17 | 临时 |
| X19-X28 | callee saved |
| X29 | FP |
| X30 | LR |
| SP | 栈指针 |
3.2 参数传递
整数:
超过:
例如:
对应:
3.3 返回地址机制
调用:
效果:
因此:
3.4 栈帧布局
典型:
高地址
previous frame
----------------
saved FP
saved LR
----------------
callee saved regs
----------------
local variables
----------------
temporary
低地址
汇编:
布局:
3.5 典型函数尾声
其中:
4. RISC-V64 LP64 ABI
4.1 寄存器
32个整数寄存器:
ABI名称:
| 寄存器 | ABI名 | 用途 |
|---|---|---|
| x0 | zero | 常量0 |
| x1 | ra | 返回地址 |
| x2 | sp | 栈指针 |
| x8 | s0/fp | frame pointer |
| x10-x17 | a0-a7 | 参数 |
| x5-x7 | t0-t2 | 临时 |
| x28-x31 | t3-t6 | 临时 |
| x8-x9 | s0-s1 | 保存 |
4.2 参数传递
整数:
即:
例如:
:
超过8个:
4.3 返回地址
调用:
执行:
类似 ARM64。
4.4 栈帧
典型:
高地址
caller frame
----------------
arguments
----------------
saved ra
saved s0(fp)
----------------
saved s registers
----------------
locals
----------------
sp
低地址
函数入口:
结果:
5. MIPS32 O32 ABI
MIPS 与前三者差异最大。
5.1 寄存器
32个:
ABI名称:
| 寄存器 | 用途 |
|---|---|
| \(a0-\)a3 | 参数 |
| \(v0-\)v1 | 返回值 |
| $ra | 返回地址 |
| $sp | 栈指针 |
| \(fp/\)s8 | 帧指针 |
| \(s0-\)s7 | callee saved |
| \(t0-\)t9 | caller saved |
5.2 参数传递
前4个:
例如:
:
第五个:
5.3 返回地址
调用:
效果:
注意:
MIPS 有 delay slot。
例如:
执行顺序:
5.4 栈帧布局
典型:
高地址
argument area
----------------
saved arguments
----------------
saved $ra
----------------
saved $fp
----------------
saved $s0-$s7
----------------
local variables
----------------
temporary
低地址
函数入口:
6. 四种 ABI 参数传递对比
整数参数
| 参数序号 | x86-64 | ARM64 | RISCV64 | MIPS32 |
|---|---|---|---|---|
| 1 | RDI | X0 | A0 | A0 |
| 2 | RSI | X1 | A1 | A1 |
| 3 | RDX | X2 | A2 | A2 |
| 4 | RCX | X3 | A3 | A3 |
| 5 | R8 | X4 | A4 | stack |
| 6 | R9 | X5 | A5 | stack |
| 7 | stack | X6 | A6 | - |
| 8 | stack | X7 | A7 | - |
7. 返回值 ABI
小整数
| 架构 | 返回寄存器 |
|---|---|
| x86-64 | RAX |
| ARM64 | X0 |
| RISCV64 | A0 |
| MIPS32 | V0 |
双返回值
| 架构 | 寄存器 |
|---|---|
| x86 | RAX,RDX |
| ARM64 | X0,X1 |
| RISCV | A0,A1 |
| MIPS | V0,V1 |
8. Frame Pointer 差异
| 架构 | FP |
|---|---|
| x86-64 | RBP |
| ARM64 | X29 |
| RISCV64 | S0 |
| MIPS32 | FP/S8 |
区别:
x86
很多时候:
直接:
ARM64
FP使用较普遍:
RISCV
FP不是必须:
MIPS
传统 ABI 强依赖:
9. 栈对齐要求
| 架构 | 要求 |
|---|---|
| x86-64 SysV | 16 bytes |
| ARM64 | 16 bytes |
| RISCV64 | 16 bytes |
| MIPS32 O32 | 8 bytes |
10. 函数调用过程对比
x86-64
返回:
ARM64
RISCV
MIPS
11. 栈溢出利用视角区别(CTF/pwn)
x86-64
常见:
覆盖:
ARM64
结构:
攻击目标:
RISC-V64
结构:
攻击目标:
MIPS32
结构:
攻击目标:
12. 核心区别总结表
| 特性 | x86-64 | ARM64 | RISCV64 | MIPS32 |
|---|---|---|---|---|
| 返回地址位置 | 栈 | LR(X30) | ra(x1) | ra($31) |
| call机制 | 硬件压栈 | 寄存器保存 | 寄存器保存 | 寄存器保存 |
| 参数寄存器数量 | 6 | 8 | 8 | 4 |
| FP | RBP | X29 | S0 | S8 |
| 保存返回地址 | callee通常不保存 | callee保存LR | callee保存ra | callee保存ra |
| 栈帧复杂度 | 最高 | 低 | 低 | 中 |
| ROP难度 | 较低 | 较高 | 较高 | 中等 |
| 逆向识别难度 | 低 | 低 | 中 | 中 |
13. 逆向分析时的识别规律
x86-64
看到:
基本确定:
ARM64
看到:
确定:
RISCV64
看到:
确定:
MIPS
看到:
确定:
x86 攻击 RIP;ARM64 攻击 LR(X30);RISC-V 攻击 ra(x1);MIPS 攻击 $ra。栈布局的核心变化来自“返回地址到底保存在哪里”。