← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

如何阅读 x86-64 寄存器与指令

从部分寄存器、数据大小、MOV 与 LEA 到机器码编码

INTERACTIVE READING MODE →

如何阅读 x86-64 寄存器与指令

本博客文章以教学为目的。 实际代码会随 CPU 世代、操作系统、ABI、汇编器、编译器与构建选项而变化,请先确认目标环境。

初次看到 rax、eax、ax、al 时,它们很像四个不同的寄存器。真正重要的不是背下所有助记符,而是追踪:读取多宽的值、值从哪里来、写到哪里,以及哪些处理器状态被改变。

分清三个层次

C 表达式        total += values[i];
汇编语言表示    add rax, qword ptr [rcx + rdx*8]
机器码字节      48 03 04 D1

C 表达类型与意图;汇编把指令和操作数写成人可读形式;机器码则是 CPU 解码的字节。源码一行与指令一条并没有一对一保证。

通用寄存器与部分寄存器

x86-64 有 rax、rbx、rcx、rdx、rsi、rdi、rbp、rsp 以及 r8 到 r15,共 16 个 64 位通用寄存器。rsp 的栈角色由硬件指令使用;哪个寄存器承载函数参数则由 ABI 决定。

名称访问范围
rax完整 64 位
eax低 32 位
ax低 16 位
al低 8 位
ah第 8~15 位的旧式名称

64 位模式下,写入 32 位通用寄存器会把高 32 位清为 0。

mov rax, 0FFFFFFFFFFFFFFFFh
mov eax, 7
; RAX = 0000000000000007h

8 位或 16 位写入则保留其余位。

mov rax, 1122334455667788h
mov al, 0AAh
; RAX = 11223344556677AAh

这也是编译器常在 setcc al 前先用 xor eax, eax 清零的原因。旧式高字节 ah、bh、ch、dh 不能出现在需要 REX 前缀的指令中,新代码通常应避免使用。

数据宽度是指令语义的一部分

mov al,  byte ptr  [rcx]    ; 1 字节
mov ax,  word ptr  [rcx]    ; 2 字节
mov eax, dword ptr [rcx]    ; 4 字节
mov rax, qword ptr [rcx]    ; 8 字节

地址相同,但访问宽度不同。宽度错误可能读写相邻字段、跨越页面边界,或破坏结构布局契约。

mov 是复制

普通 mov 不会清除源,多数形式也不能直接从一个内存位置复制到另一个。

mov rax, [rsi]
mov [rdi], rax

但这不是“x86 指令永远只能接触一个内存位置”的普遍规则;字符串和 read-modify-write 指令有自己的格式,必须查阅指令手册。

扩展方式显示 signedness

同一个字节 0xFF 可以是无符号 255,也可以是有符号 -1。

movzx eax, byte ptr [rcx]   ; 255
movsx eax, byte ptr [rcx]   ; 符号扩展为 -1
movsxd rax, eax             ; signed 32 位扩展到 64 位

movzx 用 0 填充新高位,movsx 复制原符号位。反汇编中没有 C 类型名,因此要把扩展、比较与分支一起看,才能推测有符号或无符号语义。

lea 计算地址但不读内存

lea rax, [rcx + rdx*4 + 16]

rax 得到有效地址,该地址内的值不会被读取。

lea rax, [rcx + 8]          ; rax = rcx + 8
mov rax, [rcx + 8]          ; 读取该处的 8 字节值
lea eax, [rcx + rcx*2]      ; eax = rcx * 3

编译器也会用 lea 执行不改变标志位的加法与常数倍运算。

算术指令覆盖目的操作数

add rax, rdx
sub rcx, 8
and eax, 0FFh
xor edx, edx       ; rdx 整体也变成 0

Intel 语法的双操作数指令通常是 destination = destination op source。这些指令也更新条件标志;后面的 jcc、setcc、cmovcc 必须追到真正产生标志的指令。

Intel 与 AT&T 语法

; Intel:目的在前
mov rax, qword ptr [rbx + 8]

# AT&T:源在前
movq 8(%rbx), %rax

表示法不同不代表 CPU 指令不同,但若把操作数顺序看反,数据流也会完全相反。

指令长度不是固定的

x86-64 指令长度为 1 到 15 字节,可包含 prefix、opcode、ModR/M、SIB、displacement 与 immediate。

48 89 D8                mov rax, rbx
B8 2A 00 00 00          mov eax, 42
48 8B 44 D1 10          mov rax, [rcx + rdx*8 + 16]

不必背字节,但在检查指令边界、补丁、重定位,或反汇编起点偏移一个字节时,这个结构非常重要。

追踪一个小函数

System V ABI 下,int transform(int x) { return x * 3 + 5; } 可能成为:

transform:
    lea eax, [rdi + rdi*2]
    add eax, 5
    ret

输入在 edi,返回值在 eax。但硬件环绕并不等于 C 的 signed overflow 语义;后者是未定义行为。

阅读检查表

  1. 是 Intel 还是 AT&T 语法?
  2. 操作数宽度是多少?
  3. 读取什么、覆盖什么?
  4. 32 位写入是否把高位清零?
  5. 有效地址如何计算?
  6. 是否产生或消费标志?
  7. ABI 是否赋予参数、返回或保存角色?

总结

阅读 x86-64 要从状态变化开始。部分寄存器重叠,32 位写入清除高半部;mov 复制数据,扩展指令暴露整数解释方式,而 lea 不读内存,只计算有效地址。

官方资料