AT&T 汇编语言 — 总览
AT&T 语法是 GNU 工具链(GAS 汇编器 as、GCC 内联汇编、objdump 反汇编)默认使用的汇编语法。本知识库按模块组织:
- 01-寄存器.md — 寄存器
- 02-数据与伪指令.md — 数据定义与伪指令
- 03-寻址方式.md — 寻址方式
- 04-算术与逻辑指令.md — 算术与逻辑指令
- 05-控制流.md — 控制流(跳转、循环)
- 06-函数调用约定.md — 函数调用约定
- 07-系统调用.md — 系统调用
- 08-宏与高级伪指令.md — 宏与高级伪指令
- 09-综合示例.md — 综合示例
- 10-浮点与SIMD.md — 浮点与 SIMD(x87 / SSE / AVX)
- 11-串操作指令.md — 串操作指令
- 12-位置无关代码与动态链接.md — 位置无关代码与动态链接
- 13-原子操作与并发.md — 原子操作与并发
- 14-GDB调试汇编.md — GDB 调试汇编
- 15-CPU特性与优化.md — CPU 特性探测与优化
- 16-与C语言互操作.md — 与 C 语言互操作(结构体布局、混合编译)
- 17-常用指令速查.md — 常用指令速查表(附录)
- 18-常见错误与排障.md — 常见错误与排障(附录)
- 19-GDB进阶调试.md — GDB 进阶(core dump、反向调试、多线程)
学习路径建议:先读模块 0–4 掌握语法与指令基础 → 用模块 5–7 写出第一个完整程序 → 模块 8–9 提升工程能力 → 模块 10–13 按需深入专题 → 模块 14/19 学会调试、模块 16 学会 C 互操作;模块 17、18 是速查与排障附录,随用随查。
AT&T 与 Intel 语法对比
| 特性 | AT&T | Intel |
|---|---|---|
| 操作数顺序 | 源, 目的 |
目的, 源 |
| 寄存器前缀 | %(如 %eax) |
无 |
| 立即数前缀 | $(如 $4) |
无 |
| 指令大小后缀 | 有(b/w/l/q) |
无(靠操作数推断) |
| 内存操作数 | disp(base,idx,scale) |
[base + idx*scale + disp] |
指令大小后缀
| 后缀 | 含义 | 位数 | 示例 |
|---|---|---|---|
b |
byte | 8 | movb |
w |
word | 16 | movw |
l |
long | 32 | movl |
q |
quad | 64 | movq |
x87 浮点指令有自己的精度后缀
s/l/t(单/双/扩展精度),见模块十。跳转、syscall等指令无后缀。
数值与字符字面量
| 写法 | 含义 |
|---|---|
0x10 |
十六进制 16 |
0b1010 |
二进制 10(GAS 支持) |
010 |
八进制 8!前导 0 不是十进制 |
-5 |
负数 |
'A' |
字符常量(= 0x41) |
八进制陷阱是 GAS 新手最常见的坑之一:movb $010, %al 装入的是 8 而不是 10,反汇编可见 mov $0x8,%al。
示例(32 位):
movl $1, %eax # 把立即数 1 放入 eax(源在左,目的在右)
movl %eax, %ebx # ebx = eax
addl $4, %eax # eax = eax + 4
示例(64 位):
movq $1, %rax
movq %rax, %rbx
addq $8, %rax
语句格式
每条语句占一行,由可选的标号、指令、操作数和注释组成:
[标号:] 指令 [源, 目的] # 注释
.globl _start
_start:
movl $1, %eax # 标号、指令、注释三者俱全
- 注释以
#开头(也支持 C 风格/* ... */)。 - 标号以
:结尾,用作跳转目标或地址符号。 - 局部数字标号
1:、2:可重复使用,配合1b(向后找最近的1:)/2f(向前找)引用,见模块九。
loop_start:
decl %ecx
jnz loop_start
三类操作数(最重要的基础概念)
每条指令的操作数必属以下三类之一:
| 类型 | 写法 | 示例 | 含义 |
|---|---|---|---|
| 立即数 | $常数 |
movl $42, %eax |
常数 42 本身 |
| 寄存器 | %名字 |
movl %eax, %ebx |
寄存器里存放的值 |
| 内存 | disp(base,idx,scale) |
movl 8(%rbp), %eax |
内存地址处的值 |
"取变量 msg 的地址"是把地址当作一个数算出来(并不读内存):
leaq msg(%rip), %rdi # 推荐:RIP 相对算地址
movq $msg, %rdi # 绝对地址(仅非 PIE 程序可用)
movq msg(%rip), %rdi # 注意!这是读 msg 处 8 字节内容,不是取地址
第一个程序(5 分钟上手)
# hello.s
.section .rodata
msg: .ascii "Hello, AT&T asm!\n"
.set len, . - msg # len = 当前位置 - msg = 字符串长度
.section .text
.globl _start # 导出入口符号(ld 默认从 _start 开始执行)
_start:
movq $1, %rax # 系统调用号:write
movq $1, %rdi # 参数 1:fd = 1(stdout)
leaq msg(%rip), %rsi # 参数 2:缓冲区地址
movq $len, %rdx # 参数 3:长度
syscall # 陷入内核
movq $60, %rax # 系统调用号:exit
xorq %rdi, %rdi # 参数:退出码 0
syscall
as hello.s -o hello.o && ld hello.o -o hello && ./hello
# 输出:Hello, AT&T asm!
逐行讲解、系统调用表见模块七。
_start还是main? 用as+ld、不链接 libc 时入口是_start(系统调用全部手写);用gcc链接 libc 时入口是main(C 启动代码 crt0 会先初始化,然后调用你的main,返回后替你exit)。两种风格本书都用,注意区分。
编译与查看
# 汇编 + 链接(64 位)
as hello.s -o hello.o
ld hello.o -o hello
# 多文件链接(ld 默认入口符号是 _start,可用 -e 指定)
ld a.o b.o -o prog
ld -e my_entry a.o -o prog
# 经 gcc 驱动(.s 直接汇编;.S 大写会先经 C 预处理器)
gcc -no-pie -nostdlib hello.s -o hello
# 命令行定义符号(配合 .ifdef 做条件汇编)
as --defsym DEBUG=1 foo.s -o foo.o
# 指定 32 位
as --32 hello.s -o hello.o && ld -m elf_i386 hello.o -o hello
# 反汇编查看(默认 AT&T 语法)
objdump -d hello.o
# 反汇编并转换为 Intel 语法
objdump -d -M intel hello.o
AT&T / Intel 语法切换
GAS 允许在源文件中随时切换两种语法(.intel_syntax noprefix 的 noprefix 表示寄存器不带 %):
.intel_syntax noprefix
mov rax, rbx # 该段按 Intel 语法(目的在左)
.att_syntax
movq %rbx, %rax # 切回 AT&T
阅读混合风格的代码(如内核、旧代码)时常会用到;本知识库统一使用 AT&T。
若链接时出现 missing .note.GNU-stack section implies executable stack 警告,在源文件加一行关闭可执行栈标记即可,见模块二。
常见陷阱(务必先读)
- 操作数顺序:AT&T 是
源, 目的。movl %eax, %ebx是ebx = eax,与 Intel 语法相反,是看反汇编时最常见的误解来源。 - 立即数必须加
$:movl $0x10, %eax把常数 0x10 放入 eax;而movl 0x10, %eax是读内存地址 0x10 处的值!二者语义完全不同。 - 寄存器必须加
%:漏写%时汇编器会把寄存器名当作符号,报 undefined reference。 - 大小后缀尽量显式写:只有寄存器操作数时 GAS 能推断(
mov %eax,%ebx可汇编);一旦涉及内存或立即数,GAS 只给警告并套用默认 32 位大小("using default"),结果可能不是你要的——mov $1, (%rax)被当作 32 位。养成显式写b/w/l/q的习惯。 - 写 32 位寄存器自动清零高 32 位:
movl $0, %eax等价于把整个%rax清零(且更短);但写 8/16 位寄存器(如%al)不影响其余位。详见模块一。 - 内存到内存不允许:
movl (%eax), (%ebx)非法,必须经寄存器中转。 - 只有
mov接受 64 位立即数:movq $0x1122334455667788, %rax合法(汇编成 movabs);其他指令的立即数最多 32 位(符号扩展)。 lea不算内存:leaq (%rdi,%rsi,4), %rax只把地址算出来放进 rax,不访问内存;movq (%rdi,%rsi,4), %rax才会读内存。- 比较方向:
cmp b, a计算a - b,条件跳转据此判断,注意别把jg/jl用反。详见模块四、模块五。 - 栈对齐:
call指令执行前%rsp必须 16 字节对齐,详见模块六。