04 · 机器级编程基础
来源:第 4 讲;第 5 讲 pp.2~8;小班第 4 讲;教材 §3.1~3.5。
历史与抽象层次#
x86 经 8086 的 16 位、80386 的 IA-32 32 位,发展到 x86-64;AMD64 是对 x86 的 64 位扩展,Intel 的 Itanium/IA-64 是不同路线,不能把 IA-64 当作 x86-64 的别名。历史兼容性解释了寄存器别名与多种指令形式。课件中 Coffee Lake 等参数是历史案例,不是 2026 年最新硬件。
机器码是处理器解码的字节;汇编是可读表示。x86 指令变长,反汇编必须从正确的边界开始。ISA 规定可见语义,不要求“每条指令都只用一个时钟周期”。
AT&T 语法#
指令 源,目的;寄存器加 %,立即数加 $,内存由地址表达式表示。movq $8,%rax 写常数,movq 8,%rax 读取绝对地址 8 的内存,含义完全不同。Intel 语法常是目的在前,不可混读。
后缀 b/w/l/q 对应 1/2/4/8 字节;l 是 longword,q 是 quadword。能从寄存器确定宽度时汇编器可能接受省略后缀;仅有立即数和内存时应明确宽度。寄存器名字必须匹配操作宽度,例如 movq %eax,%rbx 不合法。
全部通用寄存器与局部访问#
| 64 位 | 32 位 | 16 位 | 低 8 位 |
|---|---|---|---|
| rax/rbx/rcx/rdx | eax/ebx/ecx/edx | ax/bx/cx/dx | al/bl/cl/dl |
| rsi/rdi/rbp/rsp | esi/edi/ebp/esp | si/di/bp/sp | sil/dil/bpl/spl |
| r8~r15 | r8d~r15d | r8w~r15w | r8b~r15b |
历史高字节 ah/bh/ch/dh 是对应低 16 位的高 8 位,不能与需要 REX 前缀的某些操作数组合使用。
**写 32 位通用寄存器会清零对应 64 位寄存器高 32 位;写 8/16 位通常保留其他位。**若 rax 原为 FFFFFFFFFFFFFFFF,movb $1,%al 后为 FFFFFFFFFFFFFF01;movl $1,%eax 后为 0000000000000001。读取 eax 不会自行改写 rax。
寻址与 LEA#
D(Rb,Ri,S) 的有效地址 = D + R[Rb] + S·R[Ri]
S ∈ {1,2,4,8},省略的项按默认规则处理
Rb 是基址,Ri 是索引,D 是有符号位移。比例因子适合常见元素大小。%rsp 不能作为普通 SIB 索引寄存器。位移前不写 $。
设 rdx=0x1000、rcx=3:0x10(%rdx,%rcx,8) 地址为 0x1028。
movq 0x10(%rdx,%rcx,8),%rax从该地址读 8 字节。leaq 0x10(%rdx,%rcx,8),%rax只把计算出的 0x1028 写入 rax,不访问该地址的内存,也不更新条件码。leaq (%rax,%rax,4),%rdx是乘 5 的整数计算;LEA 的输入无需一定是有效指针。- RIP 相对寻址用于位置无关的代码和全局数据访问;以具体指令给出的 next RIP 与位移算地址。
数据传送和扩展#
普通 mov 支持立即数→寄存器/内存、寄存器→寄存器/内存、内存→寄存器,不支持两个显式内存操作数直接相搬,也不能以立即数为目的。
| 指令族 | 含义与细节 |
|---|---|
| movb/w/l/q | 同宽传送,不改变条件码 |
| movzbl / movzbq / movzwl / movzwq | 零扩展,名字中前后宽度分别指源和目标 |
| movsbl / movsbq / movswl / movswq / movslq | 符号扩展 |
| cltq | eax 符号扩展到 rax,无显式操作数 |
| cqto | 把 rax 符号扩展到 rdx:rax,准备有符号除法 |
| movabsq | 可用完整 64 位立即数装入寄存器 |
没有必要用 movzlq 做 32→64 零扩展,写 32 位寄存器自然清高位。movq 的常见立即数形式使用可符号扩展的 imm32,不能无条件认为任意 64 位立即数都能编码在此形式中;汇编器可能选择其他编码。
算术与逻辑指令#
add S,D 得到 D+S;sub S,D 得到 D-S;二/三操作数 imul 保留目标宽度内的积。inc/dec 加减 1;neg 求补码负值;not 按位取反。and/or/xor 位运算,sal/shl 左移,sar 算术右移,shr 逻辑右移。
变量移位计数通常放在 %cl;64 位移位指令使用计数低 6 位、32 位用低 5 位。这是指令规则,不改变 C 的越界移位规定。
| 操作 | 条件码影响(常用规则) |
|---|---|
| add/sub/neg/cmp | 按结果设置相关标志;neg 非零操作数使 CF=1 |
| inc/dec | 设置算术结果标志,但保持 CF |
| and/or/xor/test | 更新 ZF/SF/PF;CF=OF=0 |
| mov/lea/not | 不修改条件码 |
| shift | 计数 0 不改标志;OF 通常只在计数 1 时有定义,须按指令查表 |
不要把乘除法后的所有标志都当成可用比较结果;例如 imul 主要通过 CF/OF 指示截断,除法后相关算术标志未定义。
双倍宽乘除#
64 位单操作数 mulq S:无符号 rdx:rax = rax*S;imulq S:有符号的 128 位乘积。二操作数 imulq S,D 与单操作数形式不同,不能混淆隐含寄存器。
divq S/idivq S 用 rdx:rax 作为 128 位被除数,商写 rax、余数写 rdx。无符号 64 位被除数通常先清 rdx;有符号先 cqto。除数为零或商超出目标寄存器范围触发除法异常;TMin / -1 是常见边界。idiv 商向零截断,余数与被除数同号或为零。
# long x / long y,x 在 rdi,y 在 rsi
movq %rdi, %rax
cqto
idivq %rsi # 商 rax,余数 rdx
解读代码的方法#
为每个寄存器写一个符号表达式,逐条替换;只在内存读写时引入 M[address]。例如:
leaq (%rdi,%rdi,2), %rax # 3*x
leaq (%rax,%rsi,4), %rax # 3*x + 4*y
subq %rdx, %rax # 3*x + 4*y - z
ret
计数访问时先看题目是否排除取指。movq (%rdi),%rax 有地址寄存器读取、数据寄存器写入和一次数据内存读取;与“几个操作数”不是同一个计数口径。2025 阶段卷 swap 四条 mov 按其口径共 8 次通用寄存器访问、4 次数据内存访问。
自检#
能检查任意 mov 是否有非法操作数组合;区分地址计算和解引用;追踪子寄存器写入;解释乘除的隐含寄存器;把复杂算式还原为 C,并保留类型/溢出的前提。教材作业 3.58、3.59。