C语言:x86 64_Linux汇编笔记
Contents
x86-64 Linux 汇编笔记(Intel 语法,System V ABI)
本文结合实际 ELF 二进制反汇编输出,整理出常见指令、字段、操作类型,以及它们在函数、初始化、PLT/ GOT 中的作用。
1. 汇编文件的常见段(section)
在反汇编输出中,你会看到 .text、.plt、.init、.fini 等段:
| 段名 | 作用 |
|---|---|
.init | 可执行文件初始化代码(运行时执行 _init,比如初始化全局对象) |
.plt | Procedure Linkage Table,动态链接函数入口(跳转到动态库) |
.plt.got / .plt.sec | 用于函数重定位,跳转到 GOT(Global Offset Table)条目 |
.text | 程序主要代码(_start、main 等) |
.fini | 程序结束代码(运行 _fini,清理资源) |
反汇编中每个段通常以
<段名>开头,并显示虚拟地址和指令编码。
2. 常见指令字段
我们以反汇编 _start 段为例:
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax字段解释
地址字段:
1060:表示该指令的虚拟地址(ELF 加载地址)。机器码:
f3 0f 1e fa是 CPU 实际执行的字节序列。指令及操作数:
endbr64,xor %ebp,%ebp等。- 寄存器:以
%开头,如%rax,%rbp,%rsp。 - 立即数:
$0xfffffffffffffff0。 - 内存访问:
0x2f53(%rip)表示基于 RIP 寄存器的偏移寻址。
- 寄存器:以
3. 反汇编中的常用指令
| 指令 | 示例 | 功能说明 |
|---|---|---|
endbr64 | f3 0f 1e fa | Intel CET 指令,函数入口安全检查(硬件控制流保护)。 |
xor reg, reg | xor %ebp,%ebp | 清零寄存器,常用 xor eax,eax 清 eax。 |
mov dst, src | mov %rsp,%rdx | 赋值,可在寄存器之间或内存与寄存器间。 |
push reg / pop reg | push %rax | 栈操作,rsp 自动增减 8(64-bit)。 |
and reg, imm | and $0xfffffffffffffff0,%rsp | 位操作,用于栈对齐(16 字节对齐)。 |
lea reg, [mem] | lea 0xe4(%rip),%rdi | 载入有效地址,不访问内存,只算地址。 |
call label / call *reg/mem | call *%rax | 调用函数或间接调用(PLT 动态链接) |
ret | ret | 函数返回,弹出栈顶返回地址到 rip。 |
cmp a,b / test a,b | cmp %rdi,%rax | 比较两个值,设置标志位,不修改操作数。 |
je/jne/jg/jl/... | je 1016 | 条件跳转,根据标志位(ZF、SF、CF、OF)决定是否跳转。 |
sub/add | sub $0x8,%rsp | 栈空间分配或释放,或普通整数加减。 |
sar/shr | sar $1,%rsi | 算术右移/逻辑右移。 |
nop / nopl / nopw | nopl 0x0(%rax) | 空指令,占位,通常对齐指令流。 |
4. 寄存器用法与调用约定
关键寄存器
| 寄存器 | 用途 |
|---|---|
rax | 函数返回值 |
rbx | 被调用者保存寄存器 |
rcx | 第 4 个整数参数 |
rdx | 第 3 个整数参数 |
rsi | 第 2 个整数参数 |
rdi | 第 1 个整数参数 |
rsp | 栈指针 |
rbp | 栈帧基指针 |
r8 ~ r9 | 第 5、6 个整数参数 |
r10 ~ r11 | 调用者保存 |
r12 ~ r15 | 被调用者保存 |
在
_start中你会看到栈指针对齐、寄存器拷贝参数、清零寄存器,这都是调用约定初始化。
5. 栈操作示例
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp分析:
pop %rsi:弹出栈顶到rsi。mov %rsp,%rdx:拷贝栈指针(传递给参数)。and $0xfffffffffffffff0,%rsp:16 字节对齐。push %rax、push %rsp:栈上保存寄存器/栈指针,为函数调用做参数准备。
6. PLT / GOT 与动态链接
PLT 是程序和共享库交互的跳板:
1050: f3 0f 1e fa endbr64
1054: ff 25 76 2f 00 00 jmp *0x2f76(%rip) # 动态库 puts
105a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)说明:
jmp *0x2f76(%rip):跳转到 GOT 中对应条目。- 第一次调用时 PLT 会调用动态链接器完成符号解析。
nopw用作对齐。
初学者理解:PLT = 间接跳转表,用于函数调用动态库,GOT 保存实际地址。
7. 函数调用示例
func 和 main 段:
1149 <func>:
push %rbp
mov %rsp,%rbp
lea 0xeac(%rip),%rax
mov %rax,%rdi
call 1050 <puts@plt>
pop %rbp
ret分析:
- 建立栈帧:
push rbp/mov rbp,rsp。 - 准备参数:
lea+mov rax,rdi。 - 调用函数:
call puts@plt。 - 恢复栈帧并返回:
pop rbp/ret。
对照 C 代码:
void func() { puts("Hello"); }
int main() { func(); return 0; }8. 初始化与清理
_init:初始化,全局构造、C++ 构造函数。_fini:清理,全局析构。register_tm_clones/deregister_tm_clones:GCC 为事务内存(TMX)提供的辅助函数。__do_global_dtors_aux:全局析构辅助。
初学者可先理解为编译器/运行时生成的函数,用于初始化和清理。
9. 汇总技巧与笔记
- Intel 语法:
mov dst, src→ dst 左,src 右。 - 寄存器大小:
rax(64-bit),eax(32-bit),ax(16-bit),al/ah(8-bit)。 - 栈操作:
push/pop改变rsp,栈向低地址增长。 - 函数调用:参数用
rdi/rsi/...,返回值用rax。 - PLT/GOT:动态库函数跳转,首次解析符号。
- 对齐:调用
call前rsp保持 16 字节对齐。 - 清零寄存器:
xor reg, reg比mov reg,0更高效。 - nopl/nopw:占位/对齐,实际不影响程序逻辑。