ABI (Application Binary Interface) 是计算机体系结构中 运行环境(Runtime) 与 目标代码(Object Code) 之间的一套完全确定的低级接口协议。它在二进制层面强制规范了机器码执行时的所有细节:
其核心意义在于确保遵循同一规范的独立编译单元(如可执行文件与动态链接库)无需重新编译,即可在符合该规范的硬件与操作系统上实现二进制级别的互操作性(Interoperability)。
从程序运行的角度来说,往往需要关注如下的流程:
Mechanisms all implemented with machine instructions.
栈存在在内存中一段连续访问的区域:

栈底指针的位置不会随着栈的扩展而变化(并且在内存中的索引为最高位),栈的扩充过程伴随着栈顶指针的不断下移(寄存器递减栈顶指针)
栈顶指针的内存索引低于栈底指针。
例如,在 BombLab 中,函数入口处往往存在:
sub rsp,0x18
这样的指令,这就代表函数正在处理参数的输入,将栈顶指针的位置不断下移。
pushq src: Decrement %rsp by 8. 例如 pushq %rbx 代表把寄存器 RBX 的 64 位值压入栈中,对应的,栈顶指针会进行相应的移动。
popq src: Read value at address given by %rsp, and increment %rsp by 8. Store value at Dest (must be register).
Using call label to pass control flow (function call).
具体来说,当一次函数调用发生时:
%rip 寄存器的内容压入栈中,方便调用结束后继续执行。(%rip 存储的是 CPU 下一条需要执行的指令,即 call 指令后的下一条指令地址)
pushq %rbp(将调用者的栈底地址存起来)movq %rsp, %rbp(让 %rbp 指向当前的栈顶)subq $16, %rsp(通过减小栈指针,在栈上“挖”出一块空间给局部变量使用)。long mult2(long a, long b) {
long s = a * b;
return s;
}
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
.file "multstore.c"
.intel_syntax noprefix
.text
.globl mult2
.type mult2, @function
mult2:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-24], rdi
mov QWORD PTR [rbp-32], rsi
mov rax, QWORD PTR [rbp-24]
imul rax, QWORD PTR [rbp-32]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
pop rbp
ret
.size mult2, .-mult2
.globl multstore
.type multstore, @function
multstore:
push rbp
mov rbp, rsp
sub rsp, 40
mov QWORD PTR [rbp-24], rdi
mov QWORD PTR [rbp-32], rsi
mov QWORD PTR [rbp-40], rdx
mov rdx, QWORD PTR [rbp-32]
mov rax, QWORD PTR [rbp-24]
mov rsi, rdx
mov rdi, rax
call mult2
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-40]
mov rdx, QWORD PTR [rbp-8]
mov QWORD PTR [rax], rdx
nop
leave
ret
.size multstore, .-multstore
.ident "GCC: (GNU) 15.2.0"
.section .note.GNU-stack,"",@progbits
%rdi, %rsi, %rdx, %rcx, %r8, %r9: The first 6 arguments. (按照从左到右的顺序)%rax栈帧只会处理当前的函数,栈中其他函数部分在此处被冻结了。
对于全局变量,存储在栈内存的一个特定区域,
.data段专门存储已经初始化的全局变量。

%rbplong incr(long *p, long val) {
long x = *p;
long y = x + val;
*p = y;
return x;
}
long call_incr() {
long v1 = 15213;
long v2 = incr(&v1, 3000);
return v1 + v2;
}
.file "incr.c"
.intel_syntax noprefix
.text
.globl incr
.type incr, @function
incr:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-24], rdi
mov QWORD PTR [rbp-32], rsi
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax]
mov QWORD PTR [rbp-8], rax
mov rdx, QWORD PTR [rbp-8]
mov rax, QWORD PTR [rbp-32]
add rax, rdx
mov QWORD PTR [rbp-16], rax
mov rax, QWORD PTR [rbp-24]
mov rdx, QWORD PTR [rbp-16]
mov QWORD PTR [rax], rdx
mov rax, QWORD PTR [rbp-8]
pop rbp
ret
.size incr, .-incr
.globl call_incr
.type call_incr, @function
call_incr:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-16], 15213
lea rax, [rbp-16]
mov esi, 3000
mov rdi, rax
call incr
mov QWORD PTR [rbp-8], rax
mov rdx, QWORD PTR [rbp-16]
mov rax, QWORD PTR [rbp-8]
add rax, rdx
leave
ret
.size call_incr, .-call_incr
.ident "GCC: (GNU) 15.2.0"
.section .note.GNU-stack,"",@progbits
经典的函数调用三段式出现了:
call_incr:
push rbp
mov rbp, rsp
sub rsp, 16
一部分寄存器承担着固定的功能,因此建立一个统一的契约是至关重要的:
例如,如果不建立契约,寄存器的值可能会在函数调用中被修改,导致难以回退到函数调用之前的状态。


栈保证递归的实现和其他函数的调用并无二异。
long pcount_r(unsigned long x) {
if (x == 0) {
return 0;
} else {
return (x & 1) + pcount_r(x >> 1);
}
}
pcount_r:
movl $0, %eax # 将返回值寄存器 %eax 清零(准备好基准情况的返回值)
testq %rdi, %rdi # 测试参数 %rdi (n) 是否为 0
je .L6 # 如果 n == 0,直接跳转到 .L6 返回 0
pushq %rbx # 因为我们要用 %rbx 存中间值,它是被调用者保存寄存器 Callee-Saved,必须先压栈备份
movq %rdi, %rbx # 将当前的 n 复制到 %rbx
andl $1, %ebx # 取 n 的最低位:n & 1,结果存入 %ebx
shrq %rdi # 逻辑右移 1 位:n >>= 1,为递归调用准备参数
call pcount_r # 递归调用 pcount_r(n >> 1),结果会返回到 %rax 中
addq %rbx, %rax # 将之前保存的最低位 (%rbx) 加到递归返回的结果 (%rax) 上
popq %rbx # 恢复之前备份的 %rbx 值,维持栈平衡和寄存器原样
.L6:
rep; ret # 返回。rep; ret 是为了避免某些处理器在直接跳转到 ret 时产生分支预测性能问题
