x86 与 x64 汇编入门
介绍
概念
x86 是一种 CPU 指令集架构(ISA),定义了一套指令集(Instruction Set)。1
语法
类别
x86 汇编语法通常有两种:
- AT&T
- Intel
本文使用 AT&T 语法
我们的 CPU 只关心机器码,但是不同的汇编器仅支持特定的语法(类似 Assembler 和 Compiler 的关系)。
常见的三种汇编器:
- GNU Assembler (GAS)
as-> AT&T 语法- scoop:
gcc
- scoop:
- Netwide Assembler (NASM) -> Intel 语法
- scoop:
nasm
- scoop:
- Microsoft Macro Assembler (MASM) -> Intel 语法
语法不能够混用,例如使用 NASM 去汇编一个 AT&T 语法的汇编文件会遇到诸如“error: instruction expected”的报错。
特点
- 换行严格(毕竟没有分号之类的东西),一行就是一条语句
- 空格、缩进宽松。有没有都可以;用哪个都可以
结构
将
int main() { return 0}使用 gcc -S -O0 -fno-asynchronous-unwind-tables -fno-ident 编译可得如下汇编代码:
.file "test.c" .text .globl main .type main, @functionmain: pushq %rbp movq %rsp, %rbp movl $0, %eax popq %rbp ret .size main, .-main .section .note.GNU-stack,"",@progbits删除其中的无用信息得到:
.globl mainmain: movl $0, %eax ret观察上述汇编代码,可以发现有三种语句:
- 指令:
movl $0, %eax,ret等,其中movl,ret称为指令助记符 (Instruction Mnemonic) 或助记符。 - 伪指令 (directive):
.file,.text,.size,以.开头。 - 标签:
main:,对应 C 的goto。
以及:
- 立即数:
$0 - 寄存器:
%eax,%rbp等
将精简后的代码进行汇编:
as test.s -o test.o
可得到对应目标文件
file test.otest.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
不涉及物理地址的低级代码称为可重定位的 (relocatable)。345
汇编得到的目标文件无法执行,还需要进行链接:
gcc test.o -o test
但由于程序真正的入口点是 _start,上述操作本质上 gcc 帮我们封装了一层,用 _start 调用我们写的 main。
下面绕过 C 标准库,将 main 改写为 _start:
.globl _start_start: movl $60, %eax # 指定操作为退出 movl $0, %edi # 指定退出码为 0 syscall注释:
- AT&T ->
# - Intel ->
; - 部分汇编器支持 C 风格注释 ->
//
ld test.o -o test
执行:
./test
指令
x86 汇编基础的助记符有几十个。
在 AT&T 中,助记符的后缀:
q代表 64 位,如movql代表 32 位,如movl
具体如下:
| 后缀 | 全称 | 宽度/字节 |
|---|---|---|
| b | byte | 8 |
| w | word | 16 |
| l | long | 32 |
| q | quad | 64 |
AT&T 中,指令的操作数顺序:OP src, dest
例如:movl $0, %eax 将 0 放入 eax 寄存器
其中,$0 和 %eax 分别为这条指令的两个操作数。
操作数又可以分为:
-
直接操作数
- 立即数
如
$0- 寄存器操作数
如
%eax -
间接操作数
如 (%rbx)
数据传送
mov
算数
add
mul
堆栈
push
pop
其他指令
lea
lea 返回间接操作数的地址。4248
伪指令
默认情况下,伪指令不区分大小写。445
.file
文件元信息
.globl
.type
类型标注,告诉编译工具这个符号是什么类型,如 .type main, @function 描述 main 为一个函数。
只是描述性信息,多数情况下可以删除。
.size
大小标注
.ident
编译器版本标注
寄存器
通用目的寄存器
General-Purpose Registers (GPRs)
rax 是完整的 64 位寄存器,eax 是其低 32 位寄存器,ax 是其低 16 位寄存器,
ah 是其 8-15 位寄存器,al 是其低 8 位寄存器。
后面不再赘述。
-
rax
-
rbx
-
rbp -> 基址指针 (Base Pointer)
-
rsp -> 栈指针 (Stack Pointer)
-
rdi -> (Destination Index)
其他寄存器
- rip -> 指令指针 (Instruction Pointer),即程序计数器 PC
段
段声明表示从下面的内容开始,一直到下一个段声明,中间的内容都归入该段。
.section 用于声明段,例如:
.section .text 声明一个代码段,通常简写为 .text。
代码段
在 AT&T 中代码段为 .text,在 Intel 风格中为 .code。
只读数据段
.rodata,最常见的用途为字符串常量存储。
未初始化数据段
通常用 .bss 表示。
起始于 IBM 704 汇编中“块存储开始 (Block Storage Start)”首字母缩写。5468
约定
返回值
程序真正的入口是 _start 而非 main:
_start调用mainmain将返回值 0 写入%eaxmainret返回_start从%eax拿到返回值
- 整数、指针 ->
%rax
参数
626
- 第一个参数 ->
%rdi - 第二个参数 ->
%rsi - 第三个参数 ->
%rdx - 第四个参数 ->
%rcx - 第五个参数 ->
%r8 - 第六个参数 ->
%r9
编写程序
系统调用
系统调用是内核提供的接口,我们可以通过汇编直接调用系统调用而不经过任何高级封装。
在 x86 下,系统调用由 0x80 中断完成,各个通用寄存器用于传递参数,EAX 寄存器用于表示系统调用的接口号。7385
现代的 x64 架构提供了专门的系统调用指令,分别用于从用户态到内核态、从内核态返回用户态,以下是支持情况:
| 指令 | AMD Legacy | AMD Compat | AMD 64位 | Intel Legacy | Intel Compat | Intel 64位 |
|---|---|---|---|---|---|---|
syscall81749698 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
sysret81749708 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
sysenter81769701 | ✅ | ❌ (#UD) | ❌ (#UD) | ✅ | ✅ | ✅ |
sysexit81769705 | ✅ | ❌ (#UD) | ❌ (#UD) | ✅ | ✅ | ✅ |
以 Linux 7.1 为例,系统调用号写在 arch/x86/entry/syscalls/syscall_64.tbl 中,具体的函数原型在各自的内核源码中。
编写汇编时要查看系统调用的参数等信息,可以查阅该网站。
要在 C/C++ 中调用系统调用,可以包含头文件 unistd.h
要查阅系统调用信息,可以直接查看 man 手册:man 2 read
目前有五百多个,这里列出几个常见的:
| 系统调用号 | 系统调用 |
|---|---|
| 0 | read |
| 1 | write |
| 2 | open |
| 3 | close |
| 57 | fork |
| 60 | exit |
调用一个系统调用:
movq $系统调用号, %rax movq $第一个参数, %rdi movq $第二个参数, %rsi ... syscallHello World
.section .rodata.LC0: .ascii "Hello World!\n"len = . - .LC0 .text .globl _start_start: movq $1, %rax # write movq $1, %rdi # 文件描述符 fd leaq .LC0(%rip), %rsi # buffer movq $len, %rdx # count syscall
movq $60, %rax # exit movq $0, %rdi # exit code syscall反汇编
十六进制
对于程序:
.globl _start_start: movl $60, %eax movl $0, %edi syscall编译后要查看其十六进制形式,有如下工具:
汇编
要查看其汇编码,需要进行反汇编。
objdump
objdump -d test
输出:
test: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000401000 <_start>: 401000: b8 3c 00 00 00 mov $0x3c,%eax 401005: bf 00 00 00 00 mov $0x0,%edi 40100a: 0f 05 syscallradare2
现代的 CLI 逆向工具 radare2。
进入交互式 shell:
r2 -A test
pdf
r2 默认显示 Intel 风格,要切换为 AT&T:e asm.syntax=att(该命令仅在当前会话生效)。
References
Footnotes
-
Nisan N, Schocken S. 计算机系统要素: 如何从头构建一台现代计算机[M]. 李清安, 龚奕利, 译. 原书第2版. 北京: 机械工业出版社, 2024. ↩
-
Irvine K R. 汇编语言: 基于 x86 处理器[M]. 吴为民, 译. 原书第8版. 北京: 机械工业出版社, 2021. ↩ ↩2
-
Bryant R E, O’Hallaron D R. 深入理解计算机系统[M]. 龚奕利, 贺莲, 译. 原书第3版. 北京: 机械工业出版社, 2016. ↩
-
Lu H J, Matz M, Girkar M, 等. System V Application Binary Interface AMD64 Architecture Processor Supplement[S]. 1.0. 2025. ↩
-
俞甲子, 石凡, 潘爱民. 程序员的自我修养: 链接、装载与库[M]. 北京: 电子工业出版社, 2009. ↩
-
Advanced Micro Devices, Inc. AMD64 Architecture Programmer’s Manual Volume 2: System Programming[Z]. Revision 3.44. 2026. ↩ ↩2 ↩3 ↩4
-
Intel Corporation. Intel® 64 and IA-32 Architectures Software Developer Manual Volume 2: Instruction Set Reference 2B[Z]. 2026. ↩ ↩2 ↩3 ↩4

