x86 与 x64 汇编入门

1830 字
9 分钟
x86 与 x64 汇编入门

介绍#

概念#

x86 是一种 CPU 指令集架构(ISA),定义了一套指令集(Instruction Set)。1

x86 汇编是这套指令集的指令的人类可读表示。一条汇编语句通常对应一条机器码。2

语法#

类别#

x86 汇编语法通常有两种:

  • AT&T
  • Intel
Note

本文使用 AT&T 语法

我们的 CPU 只关心机器码,但是不同的汇编器仅支持特定的语法(类似 Assembler 和 Compiler 的关系)。

常见的三种汇编器:

语法不能够混用,例如使用 NASM 去汇编一个 AT&T 语法的汇编文件会遇到诸如“error: instruction expected”的报错。

特点#

  • 换行严格(毕竟没有分号之类的东西),一行就是一条语句
  • 空格、缩进宽松。有没有都可以;用哪个都可以

结构#

将

test.c
int main() {
return 0
}

使用 gcc -S -O0 -fno-asynchronous-unwind-tables -fno-ident 编译可得如下汇编代码:

test.s
.file "test.c"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
popq %rbp
ret
.size main, .-main
.section .note.GNU-stack,"",@progbits

删除其中的无用信息得到:

test.s
.globl main
main:
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.o

test.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Note

不涉及物理地址的低级代码称为可重定位的 (relocatable)。345

汇编得到的目标文件无法执行,还需要进行链接:

gcc test.o -o test

但由于程序真正的入口点是 _start,上述操作本质上 gcc 帮我们封装了一层,用 _start 调用我们写的 main。

下面绕过 C 标准库,将 main 改写为 _start:

test.s
.globl _start
_start:
movl $60, %eax # 指定操作为退出
movl $0, %edi # 指定退出码为 0
syscall
Note

注释:

  • AT&T -> #
  • Intel -> ;
  • 部分汇编器支持 C 风格注释 -> //

ld test.o -o test

执行:

./test

指令#

x86 汇编基础的助记符有几十个。

在 AT&T 中,助记符的后缀:

  • q 代表 64 位,如 movq
  • l 代表 32 位,如 movl

具体如下:

后缀全称宽度/字节
bbyte8
wword16
llong32
qquad64

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)

Note

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 表示。

Note

起始于 IBM 704 汇编中“块存储开始 (Block Storage Start)”首字母缩写。5468

约定#

返回值#

Note

程序真正的入口是 _start 而非 main:

  1. _start 调用 main
  2. main 将返回值 0 写入 %eax
  3. main ret 返回
  4. _start 从 %eax 拿到返回值
  • 整数、指针 -> %rax

参数#

626

  • 第一个参数 -> %rdi
  • 第二个参数 -> %rsi
  • 第三个参数 -> %rdx
  • 第四个参数 -> %rcx
  • 第五个参数 -> %r8
  • 第六个参数 -> %r9

编写程序#

系统调用#

系统调用是内核提供的接口,我们可以通过汇编直接调用系统调用而不经过任何高级封装。

在 x86 下,系统调用由 0x80 中断完成,各个通用寄存器用于传递参数,EAX 寄存器用于表示系统调用的接口号。7385

现代的 x64 架构提供了专门的系统调用指令,分别用于从用户态到内核态、从内核态返回用户态,以下是支持情况:

指令AMD LegacyAMD CompatAMD 64位Intel LegacyIntel CompatIntel 64位
syscall81749698✅✅✅❌❌✅
sysret81749708✅✅✅❌❌✅
sysenter81769701✅❌ (#UD)❌ (#UD)✅✅✅
sysexit81769705✅❌ (#UD)❌ (#UD)✅✅✅

以 Linux 7.1 为例,系统调用号写在 arch/x86/entry/syscalls/syscall_64.tbl 中,具体的函数原型在各自的内核源码中。

编写汇编时要查看系统调用的参数等信息,可以查阅该网站。

Note

要在 C/C++ 中调用系统调用,可以包含头文件 unistd.h

要查阅系统调用信息,可以直接查看 man 手册:man 2 read

目前有五百多个,这里列出几个常见的:

系统调用号系统调用
0read
1write
2open
3close
57fork
60exit

调用一个系统调用:

movq $系统调用号, %rax
movq $第一个参数, %rdi
movq $第二个参数, %rsi
...
syscall

Hello 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

编译后要查看其十六进制形式,有如下工具:

  • xxd -> vim 包提供
  • hexdump -> util-linux 包提供
  • od -> Unix,coreutils 包提供
  • hexyl -> 现代工具,需要从 ArchLinux 官方仓库安装

汇编#

要查看其汇编码,需要进行反汇编。

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 syscall

radare2#

现代的 CLI 逆向工具 radare2。

进入交互式 shell:

r2 -A test

pdf

Note

r2 默认显示 Intel 风格,要切换为 AT&T:e asm.syntax=att(该命令仅在当前会话生效)。

References#

Footnotes#

  1. Instruction set architecture - Wikipedia ↩

  2. Assembly language - Wikipedia ↩

  3. Nisan N, Schocken S. 计算机系统要素: 如何从头构建一台现代计算机[M]. 李清安, 龚奕利, 译. 原书第2版. 北京: 机械工业出版社, 2024. ↩

  4. Irvine K R. 汇编语言: 基于 x86 处理器[M]. 吴为民, 译. 原书第8版. 北京: 机械工业出版社, 2021. ↩ ↩2

  5. Bryant R E, O’Hallaron D R. 深入理解计算机系统[M]. 龚奕利, 贺莲, 译. 原书第3版. 北京: 机械工业出版社, 2016. ↩

  6. Lu H J, Matz M, Girkar M, 等. System V Application Binary Interface AMD64 Architecture Processor Supplement[S]. 1.0. 2025. ↩

  7. 俞甲子, 石凡, 潘爱民. 程序员的自我修养: 链接、装载与库[M]. 北京: 电子工业出版社, 2009. ↩

  8. Advanced Micro Devices, Inc. AMD64 Architecture Programmer’s Manual Volume 2: System Programming[Z]. Revision 3.44. 2026. ↩ ↩2 ↩3 ↩4

  9. Intel Corporation. Intel® 64 and IA-32 Architectures Software Developer Manual Volume 2: Instruction Set Reference 2B[Z]. 2026. ↩ ↩2 ↩3 ↩4

x86 与 x64 汇编入门
https://nekoside.com/posts/asm-x86/
作者
nekoside
发布于
2026-07-16
许可协议
CC BY-NC-SA 4.0
文章目录