技术

ICS 与 8086 复习 11:IA-32 指令与 AT&T 汇编语法

ICS 与 8086 汇编复习 · 第 11 篇 系列目录 · 上一篇 · 下一篇

同样是 mov,为什么在 8086 课件中写成 mov ax,bx,到了 ICS 课件却成了 movl %ebx,%eax?复习到这里,最容易混在一起的有三个问题:处理器支持什么指令、操作数有多少位、汇编器采用什么书写规则。把这三件事分开,反汇编阅读会顺畅很多。

本文以课程中的 32 位 IA32、Linux、GNU 汇编器 AT&T 语法为主。8086 的段地址计算留在实模式语境;不能把 段值×16+偏移 直接套到这里的平坦地址示例中。

先认方向和宽度

Intel 语法通常把目的操作数写在前面,AT&T 语法通常把源操作数写在前面。AT&T 中寄存器名前加 %,立即数前加 $,常见指令后缀 b/w/l/q 分别表示 8、16、32、64 位。

含义 Intel 写法 AT&T 写法
EBX 送 EAX mov eax,ebx movl %ebx,%eax
常数 5 送 EAX mov eax,5 movl $5,%eax
读取 EBX 指向的双字 mov eax,DWORD PTR [ebx] movl (%ebx),%eax
EAX 加 5 add eax,5 addl $5,%eax
以 EAX−EBX 设置标志 cmp eax,ebx cmpl %ebx,%eax

这是书写规则的对应,不代表所有指令都能机械地把操作数倒过来。多操作数指令、间接调用等还要看指令定义。GNU 汇编器的两种语法说明适合用来查这些差异。

AX 是 EAX 的低 16 位,AL 是低 8 位,AH 是第 8—15 位。写 AL 不等于重写整个 EAX。例如 EAX=12345678H 时执行 movb $0xAB,%al,结果为 123456ABH。

有效地址和访存必须分开

IA32 常见存储器操作数写成:

位移(基址寄存器,变址寄存器,比例因子)
有效地址 = 位移 + 基址 + 变址 × 比例因子
比例因子通常为 1、2、4、8

例如 8(%ebx,%esi,4)。给定 EBX=1000H、ESI=3,有效地址就是:

1000H + 3×4 + 8 = 1014H

进一步给定从 1014H 开始的四个字节为 2A 00 00 00,则下面两条指令在相同初始状态下的结果不同:

movl 8(%ebx,%esi,4),%eax   # EAX=0000002AH,读取内存
leal 8(%ebx,%esi,4),%eax   # EAX=00001014H,计算地址

LEA 不读取该地址中的数据,也不据此设置算术标志。它常被编译器用于算术。例如 leal (%eax,%eax,4),%edx 得到原 EAX 值的五倍,不能因为出现括号就认定访存。

普通 MOV 通常不允许两个操作数同时为存储器。要从一个内存位置复制到另一个位置,通常先经过寄存器,或使用具有专门语义的串操作指令。

反汇编的字节行怎样读

课件把指令分成操作码、ModR/M、可选 SIB、位移和立即数等字段。这里首先要理解字段职责:操作码确定操作类别;ModR/M 常参与选择寄存器或存储器形式;SIB 表达比例、变址和基址;后面的若干字节可能是位移,也可能是立即数。不是每条指令都具备全部字段,不能给任意机器码强行套同一长度。

比如反汇编中的一行同时给出地址、机器字节和助记符,左边的地址表示这条指令从哪里开始,中间是实际编码,右边是工具生成的符号说明。判断下一条指令地址,应数中间的实际字节,而不是按汇编文本字符数增加。对于课程给出的定长模型机则可以使用题设长度,两者必须区分。

做译码题时,推荐按“操作数长度、指令方向、寻址形式、位移或立即数”四步记录。位移和立即数若各占多个字节,还要结合小端顺序还原值。立即数编码为较短位数时,指令可能按规则符号扩展,不能一概在高位补零。

机器级程序的控制依赖还要求检查标志何时被改写。MOV 和 LEA 不改变这里讨论的算术标志,ADD、SUB 等会设置它们。如果比较之后插入另一条会改变标志的指令,紧接着的条件跳转读取的是新标志。读程序时应追溯最近一次相关标志的生产者,不能只往上找到最近的 CMP 就停止。

同一位串怎样扩展成 32 位

令 AL=80H,按 8 位补码解释为 −128,按无符号数解释为 128。

movsbl %al,%eax   # 符号扩展,EAX=FFFFFF80H
movzbl %al,%eax   # 零扩展,EAX=00000080H

这里分别讨论两条指令对同一初值的处理。符号扩展复制原符号位,零扩展在高位补零;决定采用哪种扩展的是源值的语义。C 语言中的整数提升会影响编译器作出的选择。

在 x86-64 中还要另外记住:写入一个 32 位通用寄存器会把对应 64 位寄存器的高 32 位清零;写入 8 位或 16 位部分则没有这个通用清零效果。不要把这条 64 位模式规则倒推成“任何部分寄存器写入都会清高位”。

算术题要检查隐含操作数

addl %ebx,%eax 的源和目的都明确;单操作数形式的乘除法则不是这样。

IA32 指令形式 被运算的值 结果位置
mull src 无符号 EAX × src EDX:EAX
单操作数 imull src 有符号 EAX × src EDX:EAX
divl src 无符号 EDX:EAX ÷ src 商 EAX,余数 EDX
idivl src 有符号 EDX:EAX ÷ src 商 EAX,余数 EDX

准备一个非负的 32 位无符号被除数时,通常要先将 EDX 清零;准备有符号的 EAX 被除数时,使用 cltd,即 Intel 语法的 CDQ,把 EAX 符号扩展到 EDX:EAX。EDX 留有旧值,会改变整个 64 位被除数。

一道移位跟踪题

以下为 16 位操作数的 IA32 指令片段,初始 AX=FF80H,各行按顺序执行:

movw %ax,%dx
salw $2,%ax
addw %dx,%ax
sarw $1,%ax

第一步保留原值,DX=FF80H。第二步左移两位并保留低 16 位,得到 AX=FE00H。第三步相加,FE00H+FF80H 的低 16 位是 FD80H。第四步算术右移一位并在高位补 1,得到 FEC0H,补码真值为 −320。

这道题可以解释为本例位宽下的 ((-128×4)+(-128))/2,结果恰好为整数。不能据此把任意带符号 C 左移表达式都视为合法乘法;也不能断言左移两位后的 OF 为 0。x86 移位次数大于 1 时 OF 未定义。标志行为应按对应指令手册核对。

复习时的易错点

把 movl $100,%eax 看成读取地址 100 的内存,是忽略了 $。把 movl 100,%eax 看成立即数,则犯了相反的错误。对带符号扩展、比例变址、隐含乘除操作数的题目,都应先写出数据宽度,再计算位串。

课程还介绍 x87、SSE 和 AVX。这里需要先掌握组织方式:x87 采用浮点寄存器栈,SSE/AVX 使用向量寄存器;标量运算处理一个元素,打包运算处理多个元素。它们的中间精度和指令选择可能影响浮点程序现象,但不能只凭一段 C 代码保证编译器一定选中某条指令。

自测与简答

  1. EAX=12345678H,执行 movw $0,%ax 后是什么?
  2. EBX=2000H、ECX=5,leal 12(%ebx,%ecx,2),%eax 得到什么?
  3. EAX 中是有符号数 −7,执行 idivl 前怎样构造相应的 EDX:EAX?

答案:① 12340000H。② 2000H+10+12=2016H,这是地址计算结果,没有读取 2016H 处的内存。③ 执行 cltd,把 EAX 的符号扩展到 EDX;仅清零 EDX 不能表达该负数的有符号 64 位扩展。

资料对应

主要依据《3.程序转换与指令系统-V1.0.pptx》第 6—9、35—46、47—65、67—88、97—115 页。页码按课件实际幻灯片序号计。寻址、语法和隐含操作数的例子为重新设计;移位例子修正了第 83 页操作数尺寸与 OF 叙述。

系列导航

CONTINUE / PATH

从这里继续

顺着当前内容继续,或者换到另一个板块。

讨论这篇文章

关于《ICS 与 8086 复习 11:IA-32 指令与 AT&T 汇编语法》的补充、不同意见或延伸话题,都可以留在这里。

滚动到这里时自动载入留言。