1131 字
3 分钟
x86 汇编语言入门
你写了一段 C 代码,编译器优化后性能反而变差了。打开 -S 输出看汇编,满屏的 movl、leaq、callq 完全读不懂。或者你在调试段错误,GDB 停在了一个没有源码的系统调用里,只能看汇编指令。无论做性能优化、安全逆向还是内核调试,x86 汇编都是绕不开的基础。本文从寄存器和内存模型讲起,带你建立对 x86 汇编的系统理解。
一、x86 架构概述
1.1 x86 演进历史
| 年份 | 架构 | 位宽 | 寄存器数量 | 特性 |
|---|---|---|---|---|
| 1978 | 8086 | 16位 | 14 | 实模式 |
| 1985 | 386 | 32位 | 16 | 保护模式、分页 |
| 2003 | AMD64/x86-64 | 64位 | 18+ | 长模式、更多寄存器 |
| 2011 | AVX | 256位 | 16 XMM | 向量运算 |
| 2017 | AVX-512 | 512位 | 32 ZMM | 更宽向量 |
1.2 两种语法风格
| 特性 | AT&T | Intel (NASM) |
|---|---|---|
| 操作数顺序 | 源, 目的 | 目的, 源 |
| 寄存器前缀 | %eax | eax |
| 立即数前缀 | $42 | 42 |
| 内存操作 | offset(%base,%index,scale) | [base + index*scale + offset] |
| 指令后缀 | movl, movq | mov dword, mov qword |
| 注释 | # | ; |
本文统一使用 AT&T 语法(GCC/GDB 默认),关键示例同时给出 Intel 语法对照。
二、寄存器
2.1 通用寄存器
64 位模式下,通用寄存器及其子寄存器关系:
| 63 ... 32 | 31 ... 16 | 15 ... 8 | 7 ... 0 ||-----------|-----------|----------|---------|| RAX | EAX | AX | AH | AL || RBX | EBX | BX | BH | BL || RCX | ECX | CX | CH | CL || RDX | EDX | DX | DH | DL || RSI | ESI | SI | SIL || RDI | EDI | DI | DIL || RBP | EBP | BP | BPL || RSP | ESP | SP | SPL || R8-R15 | R8D-R15D | R8W-R15W | R8B-R15B|访问规则:
- 64 位:
%rax,%r8 - 32 位:
%eax,%r8d - 16 位:
%ax,%r8w - 8 位低:
%al,%r8b - 8 位高:
%ah(仅 AX/BX/CX/DX 有)
2.2 特殊用途寄存器
| 寄存器 | 约定用途 |
|---|---|
%rax | 函数返回值 |
%rsp | 栈指针 |
%rbp | 帧指针(调试用) |
%rip | 指令指针(程序计数器) |
%rdi | 第一个参数 |
%rsi | 第二个参数 |
%rdx | 第三个参数 / 乘除法扩展 |
%rcx | 第四个参数 / 循环计数 |
%r8 | 第五个参数 |
%r9 | 第六个参数 |
%r10 | Caller-saved / syscall 编号 |
%r11 | Caller-saved |
%rbx | Callee-saved(必须保存) |
%r12-r15 | Callee-saved(必须保存) |
2.3 标志寄存器 RFLAGS
常用标志位:
| 标志 | 位 | 含义 |
|---|---|---|
| CF | 0 | 进位标志 |
| ZF | 6 | 零标志 |
| SF | 7 | 符号标志 |
| OF | 11 | 溢出标志 |
| DF | 10 | 方向标志(字符串操作) |
三、内存寻址
3.1 寻址模式
AT&T 语法通用格式:offset(%base, %index, scale)
计算地址 = %base + %index * scale + offset
其中 scale 只能是 1、2、4、8。
# AT&Tmovl $42, %eax # 立即数movl %eax, %ebx # 寄存器movl (%rsp), %eax # 直接寻址movl 8(%rsp), %eax # 基址 + 偏移movl (%rsi, %rdi, 4), %eax # 基址 + 索引 * 比例movl 0x10(%rsi, %rdi, 4), %eax # 完整格式; Intel 等价mov eax, 42mov ebx, eaxmov eax, [rsp]mov eax, [rsp + 8]mov eax, [rsi + rdi*4]mov eax, [rsi + rdi*4 + 0x10]3.2 大端与小端
x86 采用小端序(Little-Endian):低字节存放在低地址。
# 存储 0x12345678 到内存 0x100地址: 0x100 0x101 0x102 0x103值: 0x78 0x56 0x34 0x12四、常用指令
4.1 数据传送
movq %rax, %rbx # rbx = raxmovq $42, %rax # rax = 42movq (%rsp), %rax # rax = *rspmovq %rax, (%rsp) # *rsp = raxleaq 8(%rsp), %rax # rax = rsp + 8(取地址,不读内存)xchgq %rax, %rbx # 交换 rax 和 rbx4.2 算术运算
addq %rbx, %rax # rax += rbxsubq $1, %rax # rax -= 1imulq %rbx, %rax # rax *= rbxincq %rax # rax++decq %rax # rax--negq %rax # rax = -rax4.3 逻辑与位运算
andq $0xFF, %rax # rax &= 0xFForq %rbx, %rax # rax |= rbxxorq %rax, %rax # rax = 0(常用清零)shlq $4, %rax # rax <<= 4shrq $8, %rax # rax >>= 8(逻辑右移)sarq $1, %rax # rax >>= 1(算术右移)notq %rax # rax = ~rax4.4 比较与测试
cmpq %rbx, %rax # 计算 rax - rbx,设置标志位testq %rax, %rax # 计算 rax & rax,检测是否为 04.5 条件跳转
je label # 相等 (ZF=1)jne label # 不等 (ZF=0)jl label # 有符号小于 (SF!=OF)jg label # 有符号大于 (ZF=0 且 SF=OF)jb label # 无符号低于 (CF=1)ja label # 无符号高于 (CF=0 且 ZF=0)jle label # 有符号小于等于jge label # 有符号大于等于jbe label # 无符号低于等于jae label # 无符号高于等于js label # 负数 (SF=1)jz label # 为零 (同 je)jnz label # 非零 (同 jne)4.6 条件传送
条件传送比条件跳转更高效(避免分支预测失败):
cmovle %rbx, %rax # 如果 rax <= rbx,rax = rbxcmovne %rcx, %rdx # 如果不等,rdx = rcx4.7 栈操作
pushq %rax # rsp -= 8; *rsp = raxpopq %rax # rax = *rsp; rsp += 8五、函数调用约定
5.1 System V AMD64 ABI
Linux/macOS 使用的调用约定:
参数传递:
| 参数序号 | 整数/指针 | 浮点数 |
|---|---|---|
| 1 | %rdi | %xmm0 |
| 2 | %rsi | %xmm1 |
| 3 | %rdx | %xmm2 |
| 4 | %rcx | %xmm3 |
| 5 | %r8 | %xmm4 |
| 6 | %r9 | %xmm5 |
| 7+ | 栈 | 栈 |
寄存器保存规则:
- Caller-saved(调用者保存):
%rax,%rcx,%rdx,%rsi,%rdi,%r8,%r9,%r10,%r11 - Callee-saved(被调用者保存):
%rbx,%rbp,%r12,%r13,%r14,%r15
5.2 函数栈帧
# 典型函数序言/结语pushq %rbp # 保存旧帧指针movq %rsp, %rbp # 设置新帧指针subq $16, %rsp # 分配局部变量空间
# ... 函数体 ...
movq %rbp, %rsp # 恢复栈指针popq %rbp # 恢复帧指针ret # 返回栈帧布局(从高地址到低地址):
| 返回地址 | <- call 指令压入| 旧 %rbp | <- push %rbp| 局部变量 1 | <- -8(%rbp)| 局部变量 2 | <- -16(%rbp)5.3 函数调用示例
C 代码:
int add(int a, int b) { return a + b;}
int main() { int result = add(3, 4); return result;}对应汇编:
add: leal (%rdi, %rsi), %eax # eax = edi + esi ret
main: subq $8, %rsp # 栈对齐 movl $3, %edi # 第一个参数 movl $4, %esi # 第二个参数 call add # 调用 add addq $8, %rsp # 恢复栈 ret六、系统调用
6.1 syscall 约定
Linux x86-64 系统调用约定:
| 项目 | 约定 |
|---|---|
| 指令 | syscall |
| 编号 | %rax |
| 参数 1-6 | %rdi, %rsi, %rdx, %r10, %r8, %r9 |
| 返回值 | %rax |
| 错误 | 返回值在 -4095 到 -1 之间 |
Info
注意:系统调用使用 %r10 而非 %rcx 传递第 4 个参数,因为 syscall 指令会覆盖 %rcx 和 %r11。
6.2 常用系统调用
| 编号 | 名称 | 用途 |
|---|---|---|
| 0 | read | 读取 |
| 1 | write | 写入 |
| 2 | open | 打开文件 |
| 3 | close | 关闭文件 |
| 60 | exit | 退出 |
| 39 | getpid | 获取 PID |
6.3 Hello World 示例
.datamsg: .ascii "Hello, World!\n" len = . - msg
.text.global _start_start: # write(1, msg, len) movq $1, %rax # syscall: write movq $1, %rdi # fd: stdout leaq msg(%rip), %rsi # buf: 字符串地址 movq $len, %rdx # count: 长度 syscall
# exit(0) movq $60, %rax # syscall: exit xorq %rdi, %rdi # status: 0 syscall七、浮点运算
7.1 SSE/AVX 寄存器
| 寄存器 | 宽度 | 用途 |
|---|---|---|
%xmm0 - %xmm15 | 128 位 | SSE 浮点运算 |
%ymm0 - %ymm15 | 256 位 | AVX 浮点运算 |
%zmm0 - %zmm31 | 512 位 | AVX-512 运算 |
7.2 浮点指令
# 标量浮点运算(SSE)addss %xmm0, %xmm1 # 单精度加法addsd %xmm0, %xmm1 # 双精度加法mulss %xmm0, %xmm1 # 单精度乘法divsd %xmm0, %xmm1 # 双精度除法
# 浮点比较ucomiss %xmm0, %xmm1 # 单精度比较ucomisd %xmm0, %xmm1 # 双精度比较
# 数据传送movss (%rsp), %xmm0 # 加载单精度movsd (%rsp), %xmm0 # 加载双精度八、实战技巧
8.1 使用 GDB 查看汇编
# 反汇编当前函数(gdb) disas
# 反汇编指定函数(gdb) disas main
# Intel 语法(gdb) set disassembly-flavor intel
# 查看寄存器(gdb) info registers
# 查看标志位(gdb) info registers eflags
# 单步执行(汇编级)(gdb) si # step instruction(gdb) ni # next instruction8.2 使用 objdump
# 反汇编整个文件objdump -d program
# Intel 语法objdump -d -M intel program
# 只看某个函数objdump -d program | grep -A 20 '<main>'8.3 编译器输出汇编
# 生成汇编文件gcc -S -masm=att program.c # AT&T 语法gcc -S -masm=intel program.c # Intel 语法
# 不优化gcc -O0 -S program.c
# 优化并查看gcc -O2 -S program.c
# 包含 C 源码对照gcc -g -S program.c8.4 常见优化模式
| 模式 | 优化前 | 优化后 | 说明 |
|---|---|---|---|
| 清零 | mov $0, %rax | xor %rax, %rax | 更短,更快 |
| 乘以 2 的幂 | imul $8, %rax | shl $3, %rax | 移位代替乘法 |
| 取模 2 的幂 | idiv $16 | and $15, %rax | 位与代替除法 |
| 条件传送 | je/jmp | cmov | 避免分支预测失败 |
| 循环展开 | 循环 4 次 | 4 次顺序操作 | 减少循环开销 |
九、内联汇编
9.1 GCC 内联汇编格式
asm ( "汇编模板" : 输出操作数 /* 可选 */ : 输入操作数 /* 可选 */ : 修改的寄存器 /* 可选 */);9.2 基本示例
// 读取时间戳计数器static inline unsigned long rdtsc(void) { unsigned int lo, hi; asm volatile ( "rdtsc" : "=a" (lo), "=d" (hi) ); return ((unsigned long)hi << 32) | lo;}
// 原子比较并交换bool cas(long *ptr, long oldval, long newval) { unsigned char result; asm volatile ( "lock; cmpxchgq %2, %1" : "=a" (result), "+m" (*ptr) : "r" (newval), "0" (oldval) : "memory" ); return result;}9.3 约束字符
| 约束 | 含义 |
|---|---|
"r" | 任意通用寄存器 |
"a" | %rax/%eax/%al |
"b" | %rbx/%ebx/%bl |
"c" | %rcx/%ecx/%cl |
"d" | %rdx/%edx/%dl |
"m" | 内存操作数 |
"i" | 立即数(编译时常量) |
"0" | 与第 0 个输出操作数相同 |
参考资料
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时
相关文章 智能推荐
1
PowerShell 实用指南
语言 PowerShell 实用指南——从管道对象到远程管理的自动化实践
2
综合实战:从慢代码到快代码
CPU与计算机体系结构 综合实战——性能分析工作流、哈希表优化案例、网络包处理优化、数据库缓冲池调优,运用前 16 章知识将慢代码变快。
3
Plan 9 汇编入门
语言 Plan 9 汇编入门——从指令格式到 Go 运行时的汇编实践
4
为什么 CDN 能加速访问
技术科普 深入解析 CDN 加速的核心原理,理解边缘缓存、智能解析与就近接入的技术实现。
5
Redis 面试题
面试 面试中常见的 Redis 题目——数据结构、持久化、集群方案、缓存穿透/击穿/雪崩、分布式锁等知识点整理。






