Contents

C语言:x86 64_Linux汇编笔记


x86-64 Linux 汇编笔记(Intel 语法,System V ABI)

本文结合实际 ELF 二进制反汇编输出,整理出常见指令、字段、操作类型,以及它们在函数、初始化、PLT/ GOT 中的作用。


1. 汇编文件的常见段(section)

在反汇编输出中,你会看到 .text.plt.init.fini 等段:

段名作用
.init可执行文件初始化代码(运行时执行 _init,比如初始化全局对象)
.pltProcedure Linkage Table,动态链接函数入口(跳转到动态库)
.plt.got / .plt.sec用于函数重定位,跳转到 GOT(Global Offset Table)条目
.text程序主要代码(_startmain 等)
.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

字段解释

  1. 地址字段1060: 表示该指令的虚拟地址(ELF 加载地址)。

  2. 机器码f3 0f 1e fa 是 CPU 实际执行的字节序列。

  3. 指令及操作数endbr64xor %ebp,%ebp 等。

    • 寄存器:以 % 开头,如 %rax, %rbp, %rsp
    • 立即数$0xfffffffffffffff0
    • 内存访问0x2f53(%rip) 表示基于 RIP 寄存器的偏移寻址。

3. 反汇编中的常用指令

指令示例功能说明
endbr64f3 0f 1e faIntel CET 指令,函数入口安全检查(硬件控制流保护)。
xor reg, regxor %ebp,%ebp清零寄存器,常用 xor eax,eaxeax
mov dst, srcmov %rsp,%rdx赋值,可在寄存器之间或内存与寄存器间。
push reg / pop regpush %rax栈操作,rsp 自动增减 8(64-bit)。
and reg, immand $0xfffffffffffffff0,%rsp位操作,用于栈对齐(16 字节对齐)。
lea reg, [mem]lea 0xe4(%rip),%rdi载入有效地址,不访问内存,只算地址。
call label / call *reg/memcall *%rax调用函数或间接调用(PLT 动态链接)
retret函数返回,弹出栈顶返回地址到 rip
cmp a,b / test a,bcmp %rdi,%rax比较两个值,设置标志位,不修改操作数。
je/jne/jg/jl/...je 1016条件跳转,根据标志位(ZF、SF、CF、OF)决定是否跳转。
sub/addsub $0x8,%rsp栈空间分配或释放,或普通整数加减。
sar/shrsar $1,%rsi算术右移/逻辑右移。
nop / nopl / nopwnopl 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

分析:

  1. pop %rsi:弹出栈顶到 rsi
  2. mov %rsp,%rdx:拷贝栈指针(传递给参数)。
  3. and $0xfffffffffffffff0,%rsp:16 字节对齐。
  4. push %raxpush %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. 函数调用示例

funcmain 段:

1149 <func>:
    push   %rbp
    mov    %rsp,%rbp
    lea    0xeac(%rip),%rax
    mov    %rax,%rdi
    call   1050 <puts@plt>
    pop    %rbp
    ret

分析:

  1. 建立栈帧:push rbp / mov rbp,rsp
  2. 准备参数:lea + mov rax,rdi
  3. 调用函数:call puts@plt
  4. 恢复栈帧并返回: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. 汇总技巧与笔记

  1. Intel 语法mov dst, src → dst 左,src 右。
  2. 寄存器大小rax(64-bit), eax(32-bit), ax(16-bit), al/ah(8-bit)。
  3. 栈操作push / pop 改变 rsp,栈向低地址增长。
  4. 函数调用:参数用 rdi/rsi/...,返回值用 rax
  5. PLT/GOT:动态库函数跳转,首次解析符号。
  6. 对齐:调用 callrsp 保持 16 字节对齐。
  7. 清零寄存器xor reg, regmov reg,0 更高效。
  8. nopl/nopw:占位/对齐,实际不影响程序逻辑。