1376 字
4 分钟
asm04
寄存器的使用约定
| 寄存器 | 主要用途 | 调用约定 | 特殊指令 |
|---|---|---|---|
| RAX | 返回值、系统调用、算术运算 | 调用者保存 | mul, div, in, out |
| RBX | 基地址、数据存储 | 被调用者保存 | 无特殊指令 |
| RCX | 循环计数、字符串计数 | 调用者保存 | loop, rep, 移位计数 |
| RDX | I/O端口、乘除法高位 | 调用者保存 | mul, div, in, out |
| RDI | 第一个参数、目标索引 | 调用者保存 | 字符串指令目标 |
| RSI | 第二个参数、源索引 | 调用者保存 | 字符串指令源 |
RFLAGS 寄存器
这个寄存器用于存储CPU 操作状态和控制信息
寄存器布局
63 32 16 8 0+-----------------+-----------------+-----+-----+| 保留位 | 扩展标志 | FLAGS | 状态标志 |+-----------------+-----------------+-----+-----+常用标志位
- CF - 进位标志
- PF - 奇偶标志
- AF - 辅助进位
- ZF - 零标志
- SF - 符号标志
- OF - 溢出标志
程序栈
程序栈(通常简称为”栈”) 是进程内存布局中的一块连续区域,用于存储函数的调用上下文和 局部状态 。它是一种后进先出 的数据结构,可以想象成一摞盘子,你总是把新盘子放在最上面(压入),也总是从最上面拿走盘子(弹出)。
在x86_64 架构中:
- 栈的地址从高地址像低地址生长
- 栈指针寄存器
rsp始终指向栈的顶部 - 基指针寄存器
rbp通常用于指向当前函数栈帧的开始,以便稳定地访问参数和局部变量
程序栈的核心作用
- 控制流管理
call/ret依靠栈来记住返回地址,实现了函数的嵌套调用和返回
- 局部变量存储
- 函数的非静态局部变量在栈上分配。当函数返回的时,这些变量占用的空间会被释放
- 参数传递:
- 在x86_64 的 System V ABI (1) 中,前6个整型/指针 参数通过寄存器传递,如果参数多余6个,或某些参数是大型结构体那么额外的参数会通过栈传递
- 保存调用的上下文:
- 在调用一个新函数面前,调用者可能需要将一些”被调用者保存的寄存器”(rbx, r12-r15) 压栈,因此为新函数承诺不会改变这些寄存器的值
- 被调用函数也会保存 rbp 等寄存器以便返回时恢复
- 提供临时的存储空间:
- 编译器可能会使用分配的空间来存储中间计算的结果
栈的操作与指令
push <src>
- 机理:先将
rsp减去 8(在 x86_64 架构中 一个单位的栈空间为 8位), 然后将操作数存入[rsp] - 等价于
sub rsp, 8mov <src>, rsppop <dest>
- 机理: 先将
[rsp]的值存入目标操作数,然后将rsp加上8 - 等价于
mov <dest>, rspadd rsp, 8call <target>
- 机理:将下一条指令的地址(返回地址)压栈,然后跳转到目标地址
- 等价于:
push ripjmp <target>ret
- 机理: 从栈顶弹出返回地址,并跳转到该地址
- 等价于:
pop rip栈帧 Stack Frame
每一个函数在栈上占用的区域称为一个栈帧。rbp 和 rsp 共同界定了一个栈帧.
leave 指令
在 x86_64 中 leave 指令是一个符合指令,他的作用等同以下两条指令序列:
mov rsp, rbppop rbp在函数执行完毕后,准备返回。在调用ret 之前,需要先将栈恢复到调用前的状态
标准的函数序言
my_function: # === 函数序言 === push %rbp # 1. 保存旧的帧指针 mov %rsp, %rbp # 2. 建立新的帧指针(新栈帧开始) sub $16, %rsp # 3. 在栈上为局部变量分配空间
# === 函数体 === movl $0, -4(%rbp) # 使用 rbp 偏移量来访问局部变量 ... # 函数逻辑
# === 函数尾声 === leave # 1. 等效于 `mov rsp, rbp` 和 `pop rbp` # (这步会销毁当前栈帧,恢复旧的 rbp 和 rsp) ret # 2. 返回到调用者System V ABI
System B ABI 是一套详尽的规范,其定义了:
- 在特定额硬件和操作系统上,编译后的程序应该如何运行
- 不同的编译单元之间该如何交互
System V ABI包含内容
- 函数调用约定
-
参数传递(整数/指针):
- 前六个参数按顺序使用以下寄存器传递:
rdi,rsi,rdx,rcx,r8,r9- 第七个及以后的参数通过栈来传递
- 返回值:
- 整数或者指针类型的返回值存放在rax寄存器中
- 如果返回值是较大的结构体(无法放入寄存器),调用者会分配一块内存,并将地址作为隐藏的第一个参数 通过
rdi传递
- 栈对齐:
- 寄存器使用约定:
- 被调用者保存的寄存器:
rbp,rbx,r12,r13,r14,r15- 如果一个函数要使用这些寄存器,他必须在修改他们之前将他的原始值压栈,并在返回前恢复。调用者可以放心认为这些寄存器的值在函数调用前不变
- 调用者保存的寄存器:
rax,rcx,rdx,rsi,rdi,r8,r9,r10,r11- 如果一个函数希望在函数调用后还能使用这些寄存器的值,他必须在调用前自己保存他们,因为被调用者可以随意修改这些寄存器
- 被调用者保存的寄存器:
- 前六个参数按顺序使用以下寄存器传递:
程序寻址基本方式
- 顺序寻址: 指令在内存中的存储顺序执行
mov rax, 10 ; 指令地址: 0x400100add rax, 20 ; 指令地址: 0x400107mov rbx, rax ; 指令地址: 0x40010e机理:
- CPU 通过
rip指令寄存器跟踪当前指令地址 - 执行完每条指令后,
rip自动增加,指向下一条指令 - 增加的大小取决于当前指令的长度
- 直接跳转寻址: 直接指定目标地址
jmp 0x400150 ; 无条件跳转到绝对地址 0x400150call my_function ; 调用函数(函数名在链接时解析为绝对地址)je error_handler ; 条件跳转到标签- 间接跳转寻址:通过寄存器或者内存地址来跳转
jmp rax ; 跳转到 RAX 寄存器中的地址call [rsi] ; 调用 RSI 指向的地址处的函数jmp [jump_table + rdx*8] ; 通过跳转表跳转 分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时
相关文章 智能推荐










