MS-DOS 7 下的系统编程入门 / 调试器的汇编语言命令
第 7 章 调试器的汇编语言命令
过时的 DEBUG.EXE 的命令值得花时间去熟悉吗?这个问题让人回忆起过去。半个多世纪以前,计算机实现了对多个终端的时间共享。每个终端用来启动一个独立的程序,计算机的内存必须在这些程序之间分配。为了声明内存需求,引入了程序的文件头。从那时起,那些不能自动编译文件头的汇编器就被认为是过时的。正是这种态度被 DEBUG.EXE 所继承。
更复杂的汇编器会自动编译文件头和跳转目标地址。然而,这种极大的便利也有其负面影响:对地址空间和段寄存器的操作自由度会丢失。应用程序不会因此而受到影响,但对于研究和系统任务来说,这可能是一个相当大的障碍。正是因为这个原因,MASM 汇编器无法编译本书第 9.06、9.08、9.10 部分中展示的程序示例。类似的不可接受的代码片段在 BIOS 代码中甚至在 Windows XP 操作系统的加载模块中都有发现。无法通过 MASM 的代码可以用 DEBUG.EXE 编译。
为了直接了解编程,应该首选最简单的汇编工具。除了简单之外,DEBUG.EXE 还拥有一些优点,使其成为本书最合适的工具
- 首先,它最清楚地展示了机器代码的使用;
- 其次,它结合了汇编和调试功能;
- 第三,它提供对硬件和操作系统结构的访问;
- 第四,它能够与用户交互。
虽然 DEBUG.EXE 可以将任何机器代码发送到 CPU 执行,但所有 x86 机器指令的种类非常多,可能会让新手感到困惑。因此,第 7 章介绍了一个易于理解的选择:DEBUG.EXE 版本的命令集,该版本随 Windows 95/98 发布。这些命令构成所有现代 CPU 命令的一个子集,但这个子集扮演着非常重要的角色。它包含了使用最广泛的命令。目前大约 96% 的活跃计算机都“理解”这些命令。大多数现代计算机的机器代码兼容性就是基于这里介绍的子集。
现代 CPU 中更新的命令可能有所不同。然而,一些这样的命令得到了广泛认可,并且对于优化现代计算机是必要的。作为例外,第 7 章包括对几个 DEBUG.EXE “不认识”的命令的描述。描述阐明了以机器代码形式使用这些命令的方法。在任何情况下,这都不会成为用这些代码调试程序的障碍。
DEBUG.EXE 接受汇编语言命令,当它切换到将汇编语言翻译成可执行机器代码时(6.05-02)。虽然每种汇编语言方言都有其特点,但汇编语言命令的总体组成是通用的。一行以命令名称开头,后面跟着操作数。某些命令中的操作数必须以操作数类型的标记开头。如果一个汇编语言命令包含多个操作数,则这些操作数之间用逗号隔开。生成任何数字结果的命令会将其第一个(最左边的)操作数覆盖为该结果。该寄存器(或内存单元)的先前内容将丢失。在文章 9.05、9.06、9.08、9.10 中展示了包含将 DEBUG.EXE 切换到汇编语言翻译以及大量汇编语言命令的命令文件示例。
由于备选命令规范太多,第 7 章中的一些变量和操作数被赋予易于识别的固定小写字母标识符,在所有示例中都相同。下表显示并解释了这些标识符的允许替换以及 DEBUG 汇编语言方言中使用的其他一些术语。
标识符 | 解释和允许的替代项 |
---|---|
bl |
任何 8 位寄存器:AL、CL、DL、BL、AH、CH、DH、BH。 |
bx |
任何 16 位寄存器:AX、CX、DX、BX、SP、BP、SI、DI。 |
ss |
任何段寄存器:CS、DS、ES、SS。 |
ST |
协处理器的堆栈顶寄存器 ST(0) [注 1] |
ST(0-7) |
任何协处理器的堆栈寄存器,从 ST(0) 到 ST(7) |
far |
4 字节地址(段 + 偏移量)的标记。 |
byte |
一个字节操作数的 ptr 标记(ptr = “指针”) |
word |
2 字节操作数(一个字)的 ptr 标记 |
dword |
4 字节操作数(一个双字)的 ptr 标记 |
qword |
8 字节操作数(一个四字)的 ptr 标记 |
tbyte |
10 字节操作数的 ptr 标记 |
f |
任何从 “0” 到 “F” 的十六进制数字 |
±7f |
任何从 –7Fh 到 +7Fh 的带符号十六进制数 |
ff |
任何从 00h 到 FFh 的十六进制数 |
ffff |
任何从 0000h 到 FFFFh 的十六进制数 |
aaaa |
用于 “短” 跳转的地址 [注 2] |
[bp+si+ffff] |
方括号中的表达式表示对内存中的操作数寻址。相应内存单元的偏移量需要通过计算给定的表达式来找到。允许两种表达式的形式。 [注 3][注 4] |
- 注释
- ^ 协处理器的堆栈顶寄存器通常称为 ST(0),但在一些位置,如果不能寻址其他寄存器,则 DEBUG.EXE 接受简写名称 ST。
- ^ 对于具有一个地址字节的控制转移命令,偏移量指定为 “aaaa”。该偏移量必须在下一个机器命令的 ±7Fh 范围内。如果范围条件不满足,DEBUG.EXE 将给出错误消息。
- ^ a b c 第一种表达式的形式生成偏移量,默认情况下是指向 DS 中的段地址。这些表达式可以是
仅仅是偏移量规范 [ffff]
,或者单个寄存器引用 [BX]
、[DI]
、[SI]
,或者2 个寄存器的和 [BX+DI]
、[BX+SI]
,或者带有位移量的和 [BX±7f]
,[BX+ffff]
,[BX+DI±7f]
,[BX+DI+ffff]
,[BX+SI±7f]
,[BX+SI+ffff]
,[DI±7f]
,[DI+ffff]
,[SI±7f]
,[SI+ffff]
. - ^ a b c 第二组表达式包括对 BP 寄存器的引用,并生成偏移量,默认情况下是指向 SS 中的段地址:
[BP+DI]
,[BP+SI]
,[BP±7f]
,[BP+ffff]
,[BP+DI±7f]
,[BP+DI+ffff]
,[BP+SI±7f]
,[BP+SI+ffff]
. - 表达式中的寄存器名称可以用减号隔开,但这不会影响结果:无论如何都会计算总和。
- 默认段寄存器可以通过显式指定前缀字节(7.02-01)来更改。
- 当操作数的类型标记(“byte ptr”、“word ptr”...)不在汇编命令中时,DEBUG.EXE 会根据包含另一个操作数的寄存器来确定操作数的类型:16 位寄存器(AX、BX...)设置字类型,8 位寄存器(AL、AH、BL...)设置字节类型。如果命令不指向寄存器,则操作数的类型标记成为必填项。在任何情况下,这些标记都可以截断为 2 个字符:“by”、“wo”等。
切换到汇编命令的翻译后,DEBUG.EXE 会接受命令和控制指令。后者不会被翻译成机器代码,而是会影响翻译过程,并且可能在翻译过程中发挥非常重要的作用。
DB(= Data Byte)指令通知 DEBUG.EXE 以下的字节字符串不能被视为汇编命令,而应该被视为单独的数据字节,应该“原样”写入到汇编代码中。每个字节由两个十六进制数字表示,没有尾部的“h”。字节字符串可以包括用单引号或双引号括起来的 ASCII 字符组。ASCII 字符(除包围的引号外)被逐字节翻译成十六进制代码。DB 指令后的注释不允许。这是一个 DB 指令用法示例
DB 71 6C 65 'data array'
- 注释
- 在反汇编机器代码时,可能会遇到一些字节,它们不能被识别为 DEBUG.EXE 已知的机器命令。然后,将显示“DB”作为每个此类字节的前缀。
DW(= Data Word)指令通知 DEBUG.EXE 以下的字符串不能被视为汇编命令,而应该被视为单独的数据字,应该“原样”写入到汇编代码中。每个字最多包含 4 个十六进制数字。如果一个字中少于 4 个数字,它将自动用高位零补充到 4 个数字。在汇编的机器代码中,每个字的最低有效 2 位构成第一个字节,最高有效 2 位构成后面的字节。DW 指令后的字字符串可以包括用单引号或双引号括起来的 ASCII 字符组。这些字符(除包围的引号外)被翻译成十六进制表示形式,每个字符一个字节,就像在 DB 指令(7.01-01)之后一样。DW 指令后的注释不允许。这是一个 DW 指令用法示例
DW 71A0 F01 06D5 "other key" 0FFF
ORG 指令通知 DEBUG.EXE 从下一个命令开始,汇编命令的机器代码必须写入到另一个位置,从 ORG 指令后指定的地址开始。处理器的寄存器不受 ORG 指令的影响。在交互式调试器会话过程中,ORG 指令允许您沿着汇编代码导航,以便更正错误以及那些无法预先指定的向前引用。
在调试器的示例命令文件中,ORG 指令用于固定重新启动点和跳转目标点的位置(9.02-02 中的示例)。在固定点之前预留的空闲空间有助于避免繁琐的地址重新计算,否则每次在命令文件的任何先前部分添加或删除代码字节时都需要进行此重新计算。ORG 指令后的注释允许。以下表格显示了使用示例
示例 | 执行的操作 |
---|---|
ORG ffff:ffff |
显式设置段地址和偏移量 |
ORG fff |
保持段地址不变,设置偏移量 0fffh |
ORG ss:ffff |
引用段寄存器“ss:”,设置偏移量 ffffh |
ORG ss |
引用段寄存器“ss:”,隐式偏移量为 0000h |
不包含任何命令、指令、数据和注释的空行本身被 DEBUG.EXE 视为指令,强制从第 7 章中描述的汇编命令翻译返回到正常控制,并使用第 6.05-02 到 6.05-23 文章中描述的这些命令。为了从键盘输入此指令,您必须将最后一行留空,然后按 ENTER 键。当汇编命令通过输入重定向从命令文件接收时,通过此命令文件中遇到的第一个空行来诱发返回正常控制。因此,应注意命令文件中任何汇编命令块中没有空行,以及注意标记每个汇编命令块末尾的空行的存在。
在包含汇编命令的任何行中遇到分号( ; )时,DEBUG.EXE 会将其解释为立即转到下一行汇编命令的指令,跳过分号本身以及当前命令行其余部分中所有后续字符的机器代码翻译。分号的这种作用用于将注释附加到汇编命令。
- 注释
- 以分号开头的行不被视为空行(7.01-04),并且不会强制 DEBUG.EXE 停止将汇编命令翻译成机器代码。这提供了一个机会来插入标题和多行注释。
- DEBUG 的消息“ ^ Error” 向上指向上一行中的分号意味着前一行中存在错误。最有可能的情况是,由于缺少某些必需的参数,命令行被视为不完整。
- 分号不能用于将注释附加到那些以控制指令 DB 或 DW 开头的汇编命令行,以及当 DEBUG.EXE 未切换到汇编命令翻译时。
在 x86 平台 CPU 的机器代码中,几个特定的字节被赋予了特殊的前缀状态。每个此类字节都不是单独的机器命令。但是,前缀字节在遇到机器命令之前,会强制 CPU 更改其解释或执行方式。前缀字节的影响不会扩展到下一个命令之外。
DEBUG.EXE 允许在受前缀影响的汇编命令之前的一行中指定前缀字节,例如
CS: ADD byte ptr [BX],0F
在同一行中受影响命令之前的指定前缀字节也是允许的
CS: ADD byte ptr [BX],0F
机器命令的代码前面可以最多有四个前缀,如果它们的效果彼此不矛盾。一个命令中的所有前缀必须不同,不允许重复前缀。
大多数汇编命令不包含显式段地址规范。对于此类命令,CPU 根据默认段寄存器分配(请参阅表格 7.00 的注释 [3] 和 [4])来计算绝对内存地址(请参阅 6.05-01 的注释 2)。段覆盖前缀强制 CPU 从另一个段寄存器读取段地址,而不是从最接近的下一条命令的默认段寄存器读取。当然,段覆盖前缀只能应用于那些访问内存并因此隐含绝对地址计算的命令。
DEBUG.EXE “知道” 四个段覆盖前缀,继承了相应段寄存器的名称(见下表第二列)。CS:
前缀使用的例子已经在文章 7.02 中展示。其他类似前缀使用的例子可以在汇编器文本中找到,这些文本在文章 9.06 和 9.08 中给出。
现代 CPU 并非只有四个段寄存器,而是有六个。辅助段寄存器 FS 和 GS 的段覆盖前缀不被 DEBUG.EXE “知道”,但它允许通过 DB 指令(7.01-01)将这些前缀作为数据指定,并且不会妨碍 CPU 正确解释这些前缀。当然,为了正确解释这些前缀,需要 80386 或更新的 CPU。
代码 | 示例 | 注释 |
---|---|---|
2E | CS
| |
3E | DS
| |
26 | ES
| |
36 | SS
| |
64 | DB 64 |
相对于 FS: 段寄存器 |
65 | DB 65 |
相对于 GS: 段寄存器 |
- 注释
- 段覆盖前缀不能影响 ES: 寄存器的默认分配,特别是对于字符串命令 CMPSB、CMPSW、INSB、INSW、MOVSB、MOVSB、SCASB、SCASW、STOSB、STOSW。
7.02-02 LOCK – 系统总线锁定前缀
[edit | edit source]LOCK 前缀对应于前缀字节 F0h,它会使 CPU 发送“总线忙”信号并保持其活动状态,直到执行以下命令终止。这在具有多个处理器的计算机中是必要的,以防止对共享内存资源的非协调访问。对于具有单个处理器的普通计算机,LOCK 前缀是不需要的。
LOCK 前缀可以用于使用 ADC、ADD、AND、DEC、INC、NEG、NOT、OR、SBB、SUB、XCHG、XOR 命令写入内存。但是,当相同的命令只执行读取操作或与寄存器操作时,则不应指定 LOCK 前缀。在这种情况下,CPU 会对字节 F0h 响应异常 06h,导致调用中断 INT 06 处理程序(8.01-07)。
代码 | 示例 |
---|---|
F0 | LOCK |
- 注释
- 英特尔的 CPU 不允许在单个命令中将 LOCK 前缀与任何重复前缀(7.02-03、7.02-04)组合使用。
- 现代处理器拥有超过 8 个控制寄存器,允许使用前缀字节 F0h 通过 MOV 命令访问控制寄存器。[Note 1 to 7.03-58] 在这种情况下,F0h 字节被解释为不是 LOCK 前缀,而是访问寄存器 CR8–CR15 的前缀。
7.02-03 重复前缀 REPNZ
[edit | edit source]REPNZ 代表“REPeat while Not Zero”。REPNZ 前缀会循环执行紧随其后的命令。重复循环会在至少满足以下两个条件之一时终止
- 执行的命令发现操作数相等,因此将零标志 (ZF) 设置为 ZR 状态(6.05-15)。
- CX 寄存器中的数字变为零,因为 CX 寄存器中规定的重复次数已用尽。
REPNE 前缀,代表“REPeat while Not Equal”,被 DEBUG.EXE 接受为等效于 REPNZ,因为它们都对应于相同的前缀字节 F2h,这迫使 CPU 循环执行以下操作
- 检查 CX==0 条件。如果满足,则终止循环。如果没有,则将 CX 寄存器中的数字减 1。
- 将零标志重置为 NZ 状态。
- 执行该命令,该命令由重复前缀先行。
- 检查零标志是否设置为 ZR 状态。如果是,则终止循环;如果不是,则从 CX==0 检查开始重复。
只要这两个条件都没有满足,CPU 就继续循环。一旦至少满足一个条件,CPU 就会退出循环并继续执行下一个命令,该命令紧随在循环中执行的命令之后。
重复前缀与字符串命令一起使用,这些命令在每次迭代时会自动增加或减少索引寄存器(SI、DI 或两者)的内容。因此,它们的操作数的实际地址在每次迭代中都会发生变化。字符串命令 CMPSB、CMPSW、SCASB、SCASW 不仅影响索引,还影响 ZF 标志的状态。因此,将重复前缀与这些命令一起使用可以分析字节或字的字符串。当将重复前缀与不影响 ZF 标志的命令(INSB、INSW、MOVSB、MOVSW、OUTSB、OUTSW、STOSB、STOSW)一起使用时,这些命令将只是重复 CX 寄存器中预设的次数。
代码 | 示例 |
---|---|
F2 | REPNE |
F2 | REPNZ |
- 注释
- 当一个命令前面有几个前缀,包括一个重复前缀时,旧式处理器(比 80386 更旧)有时无法在中断后恢复执行重复循环。如果不能避免这种前缀组合,则必须显式重新检查循环终止条件,或者在循环执行期间使用 CLI 命令(7.03-12)禁止中断。
- 重复前缀不应用于非字符串命令,因为这会导致那些字节组合,这些字节组合可能被现代处理器解释为操作码扩展(7.02-08),特别是表示 SSE 命令。
- 当重复前缀与操作数大小覆盖前缀(7.02-06)一起使用时,规定的重复次数不是从 16 位 CX 寄存器读取的,而是从 32 位 ECX 寄存器读取的。对于这种情况,重要的是要提醒在 ECX 寄存器的高位部分(位 31–16)准备一个适当的值。
7.02-04 重复前缀 REPZ
[edit | edit source]REPZ 代表“REPeat while Zero”。REPZ 前缀会导致对其先行命令的迭代执行。重复循环会在至少满足以下两个条件之一时终止
- 执行的命令发现操作数不同,因此将零标志 (ZF) 重置为 NZ 状态(6.05-15)。
- CX 寄存器中的数字变为零,因为 CX 寄存器中规定的重复次数已用尽。
前缀名称 REP(=REPeat)、REPE(= REPeat while Equal)和 REPZ 被 DEBUG.EXE 视为等效:它们中的任何一个都对应于相同的前缀字节 F3h。遇到字节 F3h 后,处理器会执行与 REPNZ 前缀(7.02-03)相同的操作序列,只是 ZF 标志的初始状态反转为 ZR,ZF 标志的目标状态相反(NZ)。所有其他与 REPNZ 前缀一起执行命令的特殊性(在文章 7.02-03 和以下注释中描述)同样也存在于使用 REPZ 前缀执行命令中。
代码 | 示例 |
---|---|
F3 | REP |
F3 | REPE |
F3 | REPZ |
- 注释
- REPZ 前缀通常与 CMPSB 和 CMPSW 命令一起使用,用于比较两个字符字符串 - 名称、路径、签名。当比较循环终止时,结果由 ZR 标志的状态表示:设置状态 (ZR) 证明了同一性,重置状态 (NZ) 证明了差异。
7.02-05 同步前缀 WAIT 和 FWAIT
[edit | edit source]前缀名称 WAIT 和 FWAIT 对应于相同的前缀字节 9Bh。它与暗示 CPU 和异步协处理器之间代码传输的命令一起使用。字节 9Bh 强制 CPU 等待协处理器向 CPU 的“BUSY”引脚发送就绪确认信号。特别是,如果机器代码要由没有内部算术协处理器的 CPU 执行,则前缀字节 9Bh 应该先行于 ESC 命令(7.03-22)和协处理器的命令(7.04)。
对于现代 CPU,它们包含一个集成的算术协处理器,具有硬件同步机制,因此前缀字节 9Bh 的以前任务不再需要。如果控制寄存器 CR0(A.11-4)中的“协处理器同步”位 01h 被重置为零,则忽略字节 9Bh。默认情况下,位 01h 被设置,然后字节 9Bh 可能会导致调用 INT 07 处理程序,如果同时任务切换标志(CR0 中的位 03h)也被设置。任务切换与处理协处理器注册的异常的必要性相结合。INT 07 处理程序可以负责此任务,然后通过 WAIT 前缀的适当使用可以确保其完成。
代码 | 示例 |
---|---|
9B | FWAIT |
9B | WAIT |
7.02-06 操作数大小覆盖前缀
[edit | edit source]在实模式下,现代 CPU 默认情况下会模拟过时处理器 8086 的 16 位操作。但实际上,现代 CPU 具有 32 位通用寄存器。有时希望访问整个 32 位寄存器,而 CPU 仍保持在实模式下。这可以通过操作数大小覆盖前缀字节 66h 来实现,所有属于 x86 平台的 32 位 CPU 都能正确“理解”它。
由于 DEBUG.EXE 不“知道”操作数大小覆盖前缀,因此应通过 DB 指令(7.01-01)引入它,例如
DB 66 SHR AX,CL
在所示的示例中,前缀字节 66h 的存在会修改 SHR 命令(7.03-83)的动作,使其影响整个 32 位寄存器 EAX。特别是,如果 CL 寄存器中预设的移位次数为 10h(= 十进制 16),则 EAX 中的位 31–16 的内容将被移入位 15–0,并将作为普通 16 位操作数在 AX 中变得可访问。
在由前缀字节 66h 领先的情况下,堆栈命令 PUSH、PUSHF、POP、POPF 会一次操作四个字节。字节按降序重要性被推入堆栈:从最高有效位到最低有效位。EAX 寄存器中位 31–16 的内容可以通过以下方式从堆栈中读取
DB 66 PUSH AX POP BX POP BX
在这个例子中,前缀字节 66h 强制将 32 位寄存器 EAX 的全部内容压入堆栈。然后第一个 POP 命令将 EAX 的两个最低有效字节移入 BX 寄存器,但这些字节仅来自 AX,因此不需要。下一个 POP 命令使用 EAX 内容的所需最高有效字节覆盖 BX 寄存器中的先前数据。
CMP 命令(7.03-14)带操作数大小覆盖前缀比较四个字节的操作数,包括存储在 32 位寄存器中的操作数。对于带有操作数大小覆盖前缀的命令,存储在内存中的操作数以及直接在可执行代码中指定的操作数也必须是 DWORD 类型,即 4 个字节长。不幸的是,DEBUG.EXE 无法为带有 DWORD 类型附加操作数的 CPU 组装机器命令。如有必要,可以通过 DB 指令(7.01-01)附加额外的字节。
- 注释
- 使用操作数大小覆盖前缀的程序无法由 16 位 CPU 执行。
- 操作数大小覆盖前缀不能在带有单个字节操作数的命令之前指定,包括那些在单个字节寄存器(AH、AL、BH 等)中的操作数,也不能在单个字节字符串命令(CMPSB、INSB、LODSB、MOVSB、OUTSB、SCASB、STOSB)之前指定。这些代码组合可能被现代处理器解释为 SSE 命令。
- 操作数大小覆盖前缀不能在带有段寄存器中操作数的命令之前指定,因为这些寄存器在 16 位和 32 位处理器中都是 16 位寄存器。但是,此限制不适用于读取段地址以访问特定内存单元的命令。
- 当使用“继续”(6.05-14)或“跟踪”(6.05-17)命令测试程序时,DEBUG.EXE 不会将前缀字节 66h 后的那个作为下一个机器命令显示。尽管如此,32 位 CPU 始终接受前缀字节 66h 与后面的机器命令一起,并在一步执行这两个命令。
- 操作数大小覆盖前缀始终强制使用非默认操作数大小。当代码段描述符[注释 5 到 A.12-2] 字节 06h 中的第 6 位指定默认 32 位操作数大小时,前缀字节 66h 强制使用 16 位操作数大小。此处以及本书的后续部分均隐含使用默认的 16 位操作数大小,就像 CPU 在开机后进入实模式时,CPU 的“影子”寄存器自动设置的那样。
为了测试内存和其他一些任务,需要在没有受保护模式下 32 位寻址固有限制的情况下访问整个地址空间。在实模式中,可以访问整个地址空间,但这需要设置最大 4 GB 段大小(参见文章 9.10-01),此外还需要允许非默认的 32 位寻址。地址大小覆盖前缀字节 67h 允许对紧跟其后的单个命令使用非默认的 32 位寻址。
前缀字节 67h 不为 DEBUG.EXE “所知”。因此,它必须通过 DB 指令(7.01-01)作为数据引入。当一个命令之前有几个前缀时,前缀字节 67h 在操作数大小覆盖前缀(7.02-06)之前指定,但在段覆盖前缀(7.02-01)之后。当然,将前缀字节 67h 与那些不涉及内存单元的命令组合在一起毫无意义。
前缀字节 67h 会影响许多机器命令的代码长度,并更改表 7.00 注释[3] 和[4] 中列出的所有间接表达式解释。只有带有隐式间接寻址的命令保持不变:CMPSB、CMPSW、LODSB、LODSW、MOVSB、MOVSW、SCASB、SCASW、STOSB、STOSW。对于使用前缀字节 67h 组装所有其他命令,DEBUG.EXE 的功能不足。
下表显示了间接表达式的原始解释(在左列)与受地址大小覆盖前缀字节 67h 影响的解释(在右列)之间的比较。该表仅列出那些与执行相同操作的相同长度的机器命令相对应的解释。因此,所示的解释可以交换,从而可以“欺骗”DEBUG.EXE。您可以自由地指定 DEBUG.EXE 左列的间接表达式,该表达式对应于您在右列选择的所需的 CPU 解释。
原始形式 | 受前缀 67h 影响 |
---|---|
[BP+DI] |
[EBX]
|
[BX] |
[EDI]
|
[BP+DI±7f] |
[EBX±7f]
|
[BX±7f] |
[EDI±7f]
|
[BP±7f] |
[ESI±7f]
|
[DI±7f] |
[EBP±7f]
|
- 注释
- 使用地址大小覆盖前缀的程序无法由 16 位 CPU 执行。
- 当使用“继续”(6.05-14)或“跟踪”(6.05-17)命令测试程序时,DEBUG.EXE 不会将前缀字节 67h 后的那个作为下一个机器命令显示。尽管如此,32 位处理器始终接受前缀字节 67h 与后面的机器命令一起,并在一步执行这两个命令。
- 地址大小覆盖前缀始终强制使用非默认地址大小。当代码段描述符[注释 5 到 A.12-2] 字节 06h 中的第 6 位指定默认 32 位地址大小时,前缀字节 67h 会强制对紧跟其后的命令使用 16 位地址大小。此处以及本书的后续部分均隐含使用默认的 16 位地址大小,就像 CPU 在开机后进入实模式时,CPU 的“影子”寄存器自动设置的那样。
x86 平台 CPU 的操作码是在长期演变过程中形成的。在演变的每个阶段,一组机器命令都用新命令进行补充。因此,一些字节被用作操作码扩展前缀,影响了 CPU 指令解码器中操作码的解释。这些前缀没有共同的特定功能,除了每个这样的前缀都能引入一组不同的机器命令。
过去,主要是在大型机上,字节 FFh 被赋予了操作码扩展前缀的功能。现在,它被视为第 7.03 部分中描述的许多不同机器命令操作码中的第一个字节。后来的字节 D8h–DFh 专用于引入第 7.04 部分中描述的算术协处理器命令。在 1990 年代,前缀字节 0Fh 引入了用于奔腾处理器的全新命令;表 6.05-18 中提到了其中的一些命令。
如今,不再有可以充当前缀的空闲字节。对于现代处理器,SSE 组的新命令是通过操作码与前缀 66h(7.02-06)、F2h(7.02-03)、F3h(7.02-04)的组合引入的,这些组合之前被认为是无效的。新的 64 位处理器将字节 40h–4Fh 转移到前缀类别,而所有其他 x86 平台处理器将这些字节解释为命令 DEC(7.03-20)和 INC(7.03-27)。即使本书的目标仅限于了解 DOS 下的 16 位编程,也无法忽略这些变化。关于确保 16 位代码与现代处理器兼容的建议,请参见受影响机器命令描述中的注释。
AAA 代表 Adjust After Addition(加法后调整)。AAA 命令将 AX 寄存器中未打包十进制数字相加后获得的二进制和转换为适当的未打包十进制字,每个字节包含一位十进制数字(有关打包十进制格式的和的校正,请参见 7.03-18)。AAA 命令检查 AX 中的二进制和是否违反十进制溢出条件。违反表现为要么 AF 标志设置为 AC 状态,要么 AL(低位字节)的最低 4 位上的数字超过 9。如果未发生十进制溢出,则 AAA 命令不执行任何操作,但会清除 CF 标志(将其重置为 NC 状态)。否则,AAA 命令执行 AL =(AL + 6)、AH =(AH + 1),并将 AF 和 CF 标志分别设置为 AC 和 CY 状态。在任何情况下,AL(高位字节)中的最高 4 位都将被清除为零。OF、SF、ZF 和 PF 标志获得不确定的状态。
代码 | 示例 |
---|---|
37 | AAA |
AAD 指令(AAD = Adjust AX for Division)表示 AX 寄存器包含一个非压缩十进制字,即每个字节代表一个十进制数字。AAD 指令将此十进制字转换为二进制形式,以便可以进行二进制除法(7.03-21)。AAD 指令计算 AL = AL + (10 • AH),然后将 AH 清零。根据结果设置标志 SF、ZF、PF。标志 OF、AF、CF 处于不确定的状态。
代码 | 示例 |
---|---|
D5 0A | AAD |
- 注释
- 机器代码 "D5 (1-F)(0-9,B-F)" 被 DEBUG.EXE 错误地反汇编为 "AAD ff" 指令。
- 压缩十进制格式的数字不能由 AAD 指令处理,必须先解压缩。
AAM 代表 Adjust After Multiplication。假设 AX 寄存器包含两个字节的二进制乘积,每个字节代表一个非压缩十进制数字,并且最高四位(高级别)为零。AAM 指令将 AX 中的乘积除以 10,将商写入 AH,将余数写入 AL,结果是一个适当的非压缩十进制字,每个字节包含一个十进制数字。根据结果设置标志 SF、ZF、PF。标志 OF、AF、CF 处于不确定的状态。以类似的方式,AAM 指令能够将任何二进制数(最多 63h)转换为非压缩十进制字。
代码 | 示例 |
---|---|
D4 0A | AAM |
- 注释
- 机器代码 "D4 (1-F)(0-9,B-F)" 被 DEBUG.EXE 错误地反汇编为 "AAM ff" 指令。
- 压缩十进制字节的乘积不能由 AAM 指令校正。压缩十进制数必须在乘法之前解压缩。
AAS 指令(AAS = Adjust After Subtraction)将非压缩十进制数字减法后在 AX 寄存器中获得的二进制余数转换为适当的非压缩十进制字,每个字节包含一个十进制数字(有关压缩十进制格式中余数的校正,请参见 7.03-19)。
AAS 指令检查 AX 中的二进制余数是否违反了十进制溢出条件。违规情况体现在 AF 标志被设置为 AC 状态,或者 AL 的最低四位(低级别)超过 9。如果未发生十进制溢出,则 AAS 指令不执行任何操作,而是清除 CF 标志(将其重置为 NC 状态)。否则,AAS 指令将减去 AL = (AL - 6),AH = AH - 1,并将标志 AF 和 CF 分别设置为状态 AC 和 CY。无论哪种情况,AL 中的最高四位(高级别)都会被清零。标志 OF、SF、ZF 和 PF 处于不确定的状态。
代码 | 示例 |
---|---|
3F | AAS |
ADC 指令执行指定整数的加法,考虑最低位的进位。进位反映了先前操作的结果,并且必须保留,由 CF 标志的状态表示。加法后,标志 OF、SF、ZF、AF、PF、CF 根据总和获取新的状态,总和替换 ADC 指令的第一个操作数。
ADC 是一个二进制操作,但有两个例外。如果第一个操作数在 AX 寄存器中,则 ADC 指令可以应用于非压缩十进制数,并且 AX 中获得的二进制总和应由 AAA 指令转换为适当的非压缩十进制数(7.03-01)。如果第一个操作数在 AL 寄存器中,则 ADC 指令可以应用于压缩十进制字节,并且 AL 中获得的二进制总和应由 DAA 指令转换为适当的压缩十进制字节(7.03-18)。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
10 | (0-B)(0-F) | 0-2 | ADC [bp+si+ffff],bl
|
10 | (C-F)(0-F) | ADC bl,bl
| |
11 | (0-B)(0-F) | 0-2 | ADC [bp+si+ffff],bx
|
11 | (C-F)(0-F) | ADC bx,bx
| |
12 | (0-B)(0-F) | 0-2 | ADC bl,[bp+si+ffff]
|
13 | (0-B)(0-F) | 0-2 | ADC bx,[bp+si+ffff]
|
14 | 1 | ADC AL,ff
| |
15 | 2 | ADC AX,ffff
| |
80 | (1,5,9)(0-7) | 1-3 | ADC byte ptr [bp+si+ffff],ff
|
80 | D(1-7) | 1 | ADC bl,ff
|
81 | (1,5,9)(0-7) | 2-4 | ADC word ptr [bp+si+ffff],ffff
|
81 | D(1-7) | 2 | ADC bx,ffff
|
83 | (1,5,9)(0-7) | 1-3 | ADC word ptr [bp+si+ffff],±7f
|
83 | D(1-7) | 1 | ADC bx,±7f
|
- 注释
- 机器代码 "1(2,3) (C-F)(0-F)" 和 "82 (1,5,9,D)(0-7)" 也被 DEBUG.EXE 反汇编为 ADC 指令。
ADD 指令执行指定整数的加法,忽略由 CF 标志状态表示的最低位的进位。加法后,标志 OF、SF、ZF、AF、PF、CF 根据总和获取新的状态,总和替换 ADD 指令的第一个操作数。
ADD 是一个二进制操作,但有两个例外。如果第一个操作数在 AX 寄存器中,则 ADD 指令可以应用于非压缩十进制数,并且 AX 中获得的二进制总和应由 AAA 指令转换为适当的非压缩十进制数(7.03-01)。如果第一个操作数在 AL 寄存器中,则 ADD 指令可以应用于压缩十进制字节,并且 AL 中获得的二进制总和应由 DAA 指令转换为适当的压缩十进制字节(7.03-18)。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
00 | (0-B)(0-F) | 0-2 | ADD [bp+si+ffff],bl
|
00 | (C-F)(0-F) | ADD bl,bl
| |
01 | (0-B)(0-F) | 0-2 | ADD [bp+si+ffff],bx
|
01 | (C-F)(0-F) | ADD bx,bx
| |
02 | (0-B)(0-F) | 0-2 | ADD bl,[bp+si+ffff]
|
03 | (0-B)(0-F) | 0-2 | ADD bx,[bp+si+ffff]
|
04 | 1 | ADD AL,ff
| |
05 | 2 | ADD AX,ffff
| |
80 | (0,4,8)(0-7) | 1-3 | ADD byte ptr [bp+si+ffff],ff
|
80 | C(1-7) | 1 | ADD bl,ff
|
81 | (0,4,8)(0-7) | 2-4 | ADD word ptr [bp+si+ffff],ffff
|
81 | C(1-7) | 2 | ADD bx,ffff
|
83 | (0,4,8)(0-7) | 1-3 | ADD word ptr [bp+si+ffff],±7f
|
83 | C(1-7) | 1 | ADD bx,±7f
|
- 注释
- 机器代码 "0(2,3) (C-F)(0-F)" 和 "82 (0,4,8,C)(0-7)" 也被 DEBUG.EXE 反汇编为 ADD 指令。
AND 指令分析两个操作数中对应位的对。如果在一对中至少有一个位处于 FALSE(零)状态,则结果的对应位被清零。如果一对中的两个位都处于 TRUE 状态,则结果的对应位也被设置为 TRUE 状态。结果替换第一个操作数。标志 SF、ZF、PF 根据结果获取新的状态。标志 CF 和 OF 被分别清除为状态 NC(无进位)和 NV(无溢出)。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
20 | (0-B)(0-F) | 0-2 | AND [bp+si+ffff],bl
|
20 | (C-F)(0-F) | AND bl,bl
| |
21 | (0-B)(0-F) | 0-2 | AND [bp+si+ffff],bx
|
21 | (C-F)(0-F) | AND bx,bx
| |
22 | (0-B)(0-F) | 0-2 | AND bl,[bp+si+ffff]
|
23 | (0-B)(0-F) | 0-2 | AND bx,[bp+si+ffff]
|
24 | 1 | AND AL,ff
| |
25 | 2 | AND AX,ffff
| |
80 | (2,6,A)(0-7) | 1-3 | AND byte ptr [bp+si+ffff],ff
|
80 | E(1-7) | 1 | AND bl,ff
|
81 | (2,6,A)(0-7) | 2-4 | AND word ptr [bp+si+ffff],ffff
|
81 | E(1-7) | 2 | AND bx,ffff
|
83 | (2,6,A)(0-7) | 1-3 | AND word ptr [bp+si+ffff],±7f
|
83 | E(1-7) | 1 | AND bx,±7f
|
- 注释
- 机器代码 "2(2-3) (C-F)(0-F)" 和 "82 (2,6,A,E)(0-7)" 也被 DEBUG.EXE 反汇编为 AND 指令。
CALL 指令将返回地址保存到堆栈中,然后根据指定的目标地址跳转到子程序。标志的状态不会因 CALL 指令而改变。
CALL 指令有几种形式。调用调用程序代码段之外的子程序是 CALL FAR,它使用完整的 4 字节目标地址(段:偏移量)。调用调用程序代码段内的子程序是 "近" CALL,它不改变当前代码段,只使用 2 字节目标偏移量。
"近" CALL 指令有两种不同的形式,机器代码分别为 FFh 和 E8h。
机器代码为 FFh 的 "近" CALL 指令将 IP(指令指针)寄存器的当前内容压入堆栈,然后用目标偏移量覆盖 IP 寄存器,目标偏移量是从内存或通用寄存器读取的。
机器代码为 E8h 且后跟数据字的 "近" CALL 指令则不同:在将 IP 的内容保存到堆栈中之后,它将此数据字添加到 IP 中的偏移量。在 CALL 指令之后找到明确的目标偏移量后,DEBUG.EXE 会自动计算给定目标偏移量与当前位于 IP 寄存器中的下一个机器指令偏移量之间的差。该差值构成紧随 E8h 字节之后写入汇编的可执行代码中的数据字。
由于 "近" CALL 指令的两种形式都执行当前代码段内的跳转,因此从用 "近" CALL 指令调用的子程序返回到调用程序必须由 RET 指令(7.03-73)执行,该指令仅从堆栈中恢复 IP 寄存器的内容。
CALL FAR 命令将 CS(代码段)和 IP 寄存器的值压入堆栈。CALL FAR 命令的双字操作数替换 CS 和 IP 寄存器中的前一个值。因此,执行了跳转到另一个段的操作。因此,从使用 CALL FAR 命令调用的子程序返回到调用程序,必须使用 RETF 命令(7.03-74)执行,该命令从堆栈中恢复 CS 和 IP 寄存器的值。
第一 byte |
第二个字节 | 数据 字节 |
示例 | 注释 |
---|---|---|---|---|
9A | 4 | CALL FAR ffff:ffff |
[注意 1] | |
E8 | 2 | CALL ffff
| ||
FF | (1,5,9)(0-7) | 0-2 | CALL [bp+si+ffff] |
[注意 2] |
FF | (1,5,9)(8-F) | 0-2 | CALL FAR [bp+si+ffff] |
[注意 2] |
FF | D(0-7) | CALL bx |
[注意 3] |
- 注释
- ^ 在所示示例中,第一个数字是目标段地址,第二个数字是目标偏移量。此行中允许指定标记 FAR,但不是必需的:在任何情况下,都会执行 CALL FAR。
- ^ a b 当从内存中读取目标地址时,操作取决于是否存在标记 FAR。如果存在,则读取一个 4 字节的目标地址,并执行 CALL FAR。如果未指定标记 FAR,则读取一个 2 字节的目标偏移量,并执行“近”调用。
- ^ 如果 CALL 命令的操作数在寄存器中,则必须事先将目标偏移量写入该寄存器。CALL 命令对 16 位寄存器的调用始终会导致“近”调用。
- 机器代码“FF D(8-F)”由 DEBUG.EXE 解码为“CALL far bx”。
- 重复前缀 F2h (7.02-03)、F3h (7.02-04) 不能应用于 CALL 命令。
CBW 命令(CBW = Convert Byte to Word)将 AL 寄存器中的带符号字节转换为 AX 寄存器中的带符号字(2 字节),方法是用原始带符号字节的符号位填充 AX 的 AH 部分。CBW 命令不会改变标志的状态。
代码 | 示例 |
---|---|
98 | CBW |
CLC 命令将进位标志 CF 清除为默认的“NC”(无进位)状态,这通常称为 CF=0。
代码 | 示例 |
---|---|
F8 | CLC |
CLD 命令(CLD = CLear Direction)将方向标志 DF 重置为其默认状态“UP”。这会导致在执行字符串操作(CMPSB、LODSB、MOVSB、SCASB、STOSB 等)期间,索引寄存器(DI 和/或 SI)中的偏移量计数方向为升序。
代码 | 示例 |
---|---|
FC | CLD |
CLI 命令(CLI = Clear Interrupt Flag)将中断标志 IF 重置为“DI”(= 禁用中断)状态。CLI 命令强制 CPU 忽略外部中断,除了不可屏蔽中断 INT 02 (8.01-03)。
代码 | 示例 |
---|---|
FA | CLI |
- 注释
- 无论 IF 标志状态如何,可编程中断始终通过 INT 命令(7.03-28)执行。
- 如果当前程序的特权级别低于标志寄存器(A.11-4)中位 0Ch 和 0Dh 定义的 I/O 操作特权级别,则不会执行 CLI 命令。
CMC 命令(CMC = CompleMentary Carry)将进位标志 CF 的任何当前状态更改为相反状态:NC(无进位)更改为 CY(进位),反之亦然。
代码 | 示例 |
---|---|
F5 | CMC |
CMP 命令根据第一个操作数(被减数)和第二个操作数(减数)之间的差值设置标志 OF、SF、ZF、AF、PF、CF。差值本身不会被保存。两个操作数都保持不变。
CMP 命令留下的标志状态的解释取决于操作数是带符号数还是无符号数。比较无符号数后,应使用条件跳转命令 JA、JB、JBE、JNB。比较带符号数后,应使用其他条件跳转命令 JG、JGE、JL、JLE。所有条件跳转命令和循环命令的全名反映了 CMP 命令第一个(左侧)操作数与第二个(右侧)操作数之间的状态关系。例如,JA =“如果大于则跳转”表示 CMP 命令的左侧操作数必须大于或等于右侧操作数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
38 | (0-B)(0-F) | 0-2 | CMP [bp+si+ffff],bl
|
38 | (C-F)(0-F) | CMP bl,bl
| |
39 | (0-B)(0-F) | 0-2 | CMP [bp+si+ffff],bx
|
39 | (C-F)(0-F) | CMP bx,bx
| |
3A | (0-B)(0-F) | 0-2 | CMP bl,[bp+si+ffff]
|
3B | (0-B)(0-F) | 0-2 | CMP bx,[bp+si+ffff]
|
3C | 1 | CMP AL,ff
| |
3D | 2 | CMP AX,ffff
| |
80 | (3,7,B)(8-F) | 1-3 | CMP byte ptr [bp+si+ffff],ff
|
80 | F(9-F) | 1 | CMP bl,ff
|
81 | (3,7,B)(8-F) | 2-4 | CMP word ptr [bp+si+ffff],ffff
|
81 | F(9-F) | 2 | CMP bx,ffff
|
83 | (3,7,B)(8-F) | 1-3 | CMP word ptr [bp+si+ffff],±7f
|
83 | F(9-F) | 1 | CMP bx,±7f
|
- 注释
- 机器代码“3(A,B) (C-F)(0-F)”和“82 (3,7,B,F)(8-F)”也由 DEBUG.EXE 解码为 CMP 命令。
虽然 CMPSB 代表“CoMPare Strings of Bytes”(比较字节字符串),但 CMPSB 命令实际上只比较一对字节。要比较的字节的地址必须事先加载到 DS:SI 和 ES:DI 寄存器对中。如果字节相等,则 CF(进位标志)被清除为 NC(无进位)状态,ZF(零标志)被设置为 ZR 状态。如果字节不相等,则 CF 标志被设置为 CY 状态,ZF 标志被清除为 NZ(非零)状态。标志 OF、SF、AF、PF 接收与比较字节之间的差值相对应,但差值本身不会被保存。
比较后,SI(源索引)寄存器和 DI(目标索引)寄存器中的两个偏移量都会增加 1 或减少 1,具体取决于方向标志 DF 的状态(“UP”或“DN”)。DF 标志的状态可以通过 CLD(7.03-11)和 STD(7.03-85)命令更改。索引寄存器内容的自动更改为比较下一对字节做准备。
CMPSB 命令之前通常会有重复前缀 F2h (7.02-03) 或 F3h (7.02-04),这使得它可以循环执行,从而比较字节字符串。CMPSB 命令也可以在前面加上一个段覆盖前缀(2Eh 或 26h 或 36h,参见 7.02-01);它使能够引用其他段寄存器,而不是用于比较字节之一的段寄存器 DS。对于另一个比较字节,默认段寄存器 ES 不能被前缀覆盖。
代码 | 示例 |
---|---|
A6 | CMPSB |
- 注释
- 当 CMPSB 命令前面有重复前缀 F2h 或 F3h 时,循环内的操作顺序包括分配标志状态,然后增加(或减少)索引寄存器的内容,之后是循环终止条件检查。因此,在循环终止时,索引寄存器中的偏移量不会指向导致循环终止的那些字节,而是指向下一对字节。
CMPSW 指令(CMPSW = CoMPare Strings of Words)比较一对字,然后将 SI 和 DI 索引寄存器的值增加(或减少)2,从而准备地址以比较下一对字。操作数大小覆盖前缀 66h(7.02-06)强制 CMPSW 指令比较一对四字节操作数(DWORD 类型)并将索引寄存器的内容增加(或减少)4。CMPSW 指令执行的所有其他特性与 CMPSB 指令相同(7.03-15)。
代码 | 示例 |
---|---|
A7 | CMPSW |
7.03-17 CWD – 字到双字转换。
[edit | edit source]CWD 指令(CWD = Convert Word into Double word)将 AX 寄存器中的带符号字转换为一个四字节带符号数(DWORD 类型)。DX 寄存器专用于 dword 操作数的两个最高有效字节。转换是通过用原始带符号字的符号位填充 DX 寄存器来完成的。CWD 指令不会改变标志状态。
代码 | 示例 |
---|---|
99 | CWD |
7.03-18 DAA – 打包和的十进制修正
[edit | edit source]DAA 指令(DAA = Decimal Adjustment after Addition)将 AL 寄存器中打包的十进制字节的二进制和转换为一个正确的打包的十进制字节,表示和的 2 个十进制数字(有关非打包和的十进制修正,请参见 7.03-01)。
打包的十进制字节的二进制和可能违反 AL 寄存器的低位和高位 4 位部分(半字节)中的十进制溢出条件。首先检查低位部分:如果那里的值超过 9 或 AF 标志设置为 AC 状态,那么 DAA 指令将添加 AL = (AL + 6)。之后,对 AL 寄存器的高位 4 位部分(半字节)应用类似的检查:如果那里的值超过 9Fh 或 CF 标志设置为 CY 状态,那么 DAA 指令将添加 AL = (AL + 60h)。AF、CF、SF、ZF、PF 标志根据结果获取新的状态。OF 标志保持在不确定的状态。
代码 | 示例 |
---|---|
27 | DAA |
7.03-19 DAS – 打包余数的十进制修正
[edit | edit source]DAS 指令(DAS = Decimal Adjustment after Subtraction)将 AL 寄存器中打包的十进制字节的二进制差转换为一个正确的打包的十进制字节,表示余数的 2 个十进制数字(有关非打包差的十进制修正,请参见 7.03-04)。
打包的十进制字节的二进制差可能违反 AL 寄存器的低位和高位 4 位部分(半字节)中的十进制溢出条件。首先检查低位部分:如果那里的值超过 9 或 AF 标志设置为 AC 状态,那么 DAS 指令将减去 AL = (AL – 6)。之后,对 AL 寄存器的高位 4 位部分(半字节)应用类似的检查:如果那里的值超过 9Fh 或 CF 标志设置为 CY 状态,那么 DAS 指令将减去 AL = (AL – 60h)。AF、CF、SF、ZF、PF 标志根据结果获取新的状态。OF 标志保持在不确定的状态。
代码 | 示例 |
---|---|
2F | DAS |
7.03-20 DEC – 单位减量
[edit | edit source]DEC 指令将它的操作数减 1。OF、SF、ZF、AF、PF 标志根据结果获取新的状态。CF 标志保留其以前的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
4(8-F) | DEC bx
| ||
FE | (0,4,8)(8-E) | 0-2 | DEC byte ptr [bp+si+ffff]
|
FE | C(8-F) | DEC bl
| |
FF | (0,4,8)(8-E) | 0-2 | DEC word ptr [bp+si+ffff]
|
- 注释
- 字节 48h–4Fh 可以被 64 位处理器解释为前缀。因此,2 字节代码 "FF C(8-F)" 应该优先于 1 字节代码 4(8-F)。代码 "FF C(8-F)" 被所有 x86 平台的处理器解释为 "DEC bx" 指令,并且被 DEBUG.EXE 正确地反汇编,但在汇编时,这些代码应该通过 DB 指令(7.01-01)作为数据提供给 DEBUG.EXE。
处理器,并且被 DEBUG.EXE 正确地反汇编,但在汇编时,这些代码应该通过 DB 指令(7.01-01)作为数据提供给 DEBUG.EXE。
7.03-21 DIV – 无符号整数的除法
[edit | edit source]DIV 指令执行无符号二进制整数的除法(有关带符号整数的除法,请参见 7.03-24)。显式操作数是除数。如果除数是字节,则被除数被隐式地认为存在于 AX 寄存器中,商被保留在 AL 中,余数被放置在 AH 中。如果除数是字,则被除数被隐式地认为存在于 DX 寄存器(最高有效 2 个字节)和 AX 寄存器(最低有效 2 个字节)中,商被保留在 AX 中,余数被放置在 DX 中。OF、SF、ZF、AF、PF、CF 标志获取不确定的状态。
虽然 DIV 是一个二进制运算,但非打包十进制字可以被二进制除法运算,如果它们事先通过 AAD 指令(7.03-02)转换为可接受的准二进制形式。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
F6 | (3,7,B)(0-7) | 0–2 | DIV byte ptr [bp+si+ffff]
|
F6 | F(0-7) | DIV bl
| |
F7 | (3,7,B)(0-7) | 0–2 | DIV word ptr [bp+si+ffff]
|
F7 | F(0-7) | DIV bx
|
- 注释
- 如果除法运算导致商寄存器溢出,CPU 将自动生成一个异常:调用 INT 00 处理程序(8.01-01)。结果取决于该处理程序。
7.03-22 ESC – 代码传输到异步协处理器
[edit | edit source]最初,ESC(= ESCape)指令用于将数据和指令从 CPU 发送到外部异步协处理器。每个 ESC 指令之前都是 WAIT 前缀(7.02-05),强制 CPU 等待协处理器到达 CPU 的 "BUSY" 引脚的准备就绪确认信号。后来,算术协处理器的指令获得了它们特有的名称(7.04),但其余的机器代码(以字节 D8h–DFh 开头)仍然被 DEBUG.EXE 反汇编为 ESC 指令
- D9 (0,4,8)(8-F), D9 D(1-7), DA (C-F)(0-F), DB (0,4,8,C,D,F)(8-F),
- DB (2,3,6,7,A-D,F)(0-7), DB E(4 – F), DD (0,2,6)(8-F),
- DD (E,F)(0-F), DE D(8,A-F), DF (0,4,8)(8-F), DF (E,F)(0-F).
上述代码中的一些已被分配给现代算术协处理器的新的指令。作为所有以字节 D8h–DFh 开头的协处理器指令,如果控制寄存器 CR0(A.11-4)的位 02h("协处理器仿真")被清除为零,则 ESC 指令由 CPU 执行。但如果位 02h 被设置,则 CPU 对每个这样的指令的响应是调用 INT 07 处理程序(8.01-08)。这意味着应该加载一个特殊的 INT 07 处理程序,它能够仿真算术协处理器或其他异步设备的功能。
理论上,ESC 指令可以用于将数据和指令发送到外部异步设备,但对于现代处理器,这个功能没有被记录在案。ESC 指令的第一个操作数是一个十六进制数,第二个操作数从指定的源读取。这两个操作数的解释是每个特定目标设备的权利。ESC 指令不会改变标志状态。
第一 byte |
第二个字节 | 示例 |
---|---|---|
DA | C(0-7) | ESC 10,bl
|
DA | C(8-F) | ESC 11,bl
|
DA | D(0-7) | ESC 12,bl
|
DA | D(8-F) | ESC 13,bl
|
DA | E(0-7) | ESC 14,bl
|
DA | E(8-F) | ESC 15,bl
|
DA | F(0-7) | ESC 16,bl
|
DA | F(8-F) | ESC 17,bl
|
7.03-23 HLT – 将 CPU 设置为停止状态
[edit | edit source]HLT 指令(= HaLT)强制处理器停止。处理器停止后,会保留 CS:IP 寄存器的值和标志状态,从而确保可以正确激活。处理器可以通过重新启动或通过外部中断信号(通过 NMI 引脚(8.01-03)或中断控制器(8.01-09)接收)恢复正常运行。
代码 | 示例 |
---|---|
F4 | HLT |
- 注释
- HLT 指令可以在最高特权级别运行的程序中使用。在最高特权级别之外,HLT 指令会被忽略。
- 确定将 CPU 从停止状态恢复的特定外部中断的方法在文章 8.01-09 中介绍。
7.03-24 IDIV – 带符号整数的除法
[edit | edit source]IDIV (= Integer DIVision) 命令执行带符号二进制整数的除法(无符号整数的除法见 7.03-21)。显式操作数是除数。如果除数是字节,则被除数隐含地存在于 AX 寄存器中,商留在 AL 中,余数放在 AH 中。如果除数是字,则被除数隐含地存在于 DX 寄存器中(较重要的 2 个字节)以及 AX 寄存器中(较不重要的 2 个字节),商留在 AX 中,余数放在 DX 中。余数的符号始终与被除数的符号相同。标志 OF、SF、ZF、AF、PF、CF 进入不确定的状态。尽管 IDIV 是一个二进制运算,但如果在预先使用 AAD 命令(7.03-02)将其转换为可接受的准二进制形式,则可以对非压缩十进制字进行二进制除法。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
F6 | (3,7,B)(8-F) | 0-2 | IDIV byte ptr [bp+si+ffff]
|
F6 | F(8-F) | IDIV bl
| |
F7 | (3,7,B)(8-F) | 0-2 | IDIV word ptr [bp+si+ffff]
|
F7 | F(8-F) | IDIV bx
|
- 注释
- 如果除法运算导致商寄存器溢出,CPU 会自动生成异常:调用 INT 00 处理程序(8.01-01)。结果取决于该处理程序。
7.03-25 IMUL – 带符号整数的乘法
[edit | edit source]IMUL(Integer MULtiplcation)命令将带符号整数相乘(无符号整数见 7.03-61)。IMUL 命令的显式操作数表示乘数。如果此操作数是字节,则另一个操作数隐含地存在于 AL 寄存器中;乘法后,积留在 AX 寄存器中。如果显式操作数是字,则另一个操作数必须存在于 AX 寄存器中;乘法后,积的较不重要的 2 个字节留在 AX 寄存器中,积的较重要的 2 个字节留在 DX 寄存器中。
如果 AH 或 DX 寄存器中积的最高有效部分表示非零值,则 IMUL 命令会相应地将 OF 和 CF 标志设置为 OV 和 CY 状态。相反,这些标志的清除状态 NV 和 NC 表示积的最高有效部分仅填充了符号位。标志 SF、ZF、AF、PF 进入不确定的状态。
IMUL 命令可以应用于二进制整数和非压缩十进制数。压缩十进制操作数必须先解压缩。解压缩十进制数的积需要通过 AAM 命令(7.03-03)转换为解压缩十进制格式。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
F6 | (2,6,A)(8-F) | 0-2 | IMUL byte ptr [bp+si+ffff]
|
F6 | E(8-F) | IMUL bl
| |
F7 | (2,6,A)(8-F) | 0-2 | IMUL word ptr [bp+si+ffff]
|
F7 | E(8-F) | IMUL bx
|
- 注释
- DEBUG.EXE 不支持具有 2 个显式操作数的 IMUL 命令的其他形式(代码 69h 和 6Bh)。
7.03-26 IN – 从端口输入数据
[span>edit | edit source]执行 IN 命令时,CPU 会发出信号,将 CPU 的总线从内存切换到 I/O 端口,并启用异步数据传输。IN 命令的第一个操作数指定接收到的数据应写入的寄存器。应根据接收到的数据的格式选择此寄存器:如果要接收字节,则为字节寄存器 AL,如果要接收字,则为双字节寄存器 AX。IN 命令的第二个操作数定义端口地址。后者可以明确地指定为两位十六进制数,也可以间接指定——作为 DX 寄存器的内容。CPU 标志的状态不会因 IN 命令而改变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
E4 | 1 | IN AL,ff
| |
E5 | 1 | IN AX,ff
| |
EC | IN AL,DX
| ||
ED | IN AX,DX
|
- 注释
- 选定的端口地址显示在附录 A.14-1 中。IN 命令的直接形式不允许超过 FFh 的端口地址。通过 DX 寄存器进行间接寻址不受此限制。
- 如果当前程序的特权级别低于标志寄存器中位 0Ch 和 0Dh 定义的 I/O 操作的特权级别(A.11-4),则不会执行 IN 命令。
7.03-27 INC – 单位增量
[edit | edit source]INC 命令将操作数增加 1。标志 OF、SF、ZF、AF、PF 根据结果获取新的状态。CF 标志保留其以前的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
4(0-7) | INC bx
| ||
FE | (0,4,8)(0-7) | 0-2 | INC byte ptr [bp+si+ffff]
|
FE | C(0-7) | INC bl
| |
FF | (0,4,8)(0-7) | 0-2 | INC word ptr [bp+si+ffff]
|
- 注释
- 64 位处理器可以将字节 40h–47h 解释为前缀。因此,2 字节代码“FF C(0-7)”应优先于 1 字节代码 4(0-7)。代码“FF C(0-7)”被所有 x86 平台处理器解释为“INC bx”命令,并由 DEBUG.EXE 正确地反汇编,但在汇编期间,这些代码应通过 DB 指令(7.01-01)作为数据提供给 DEBUG.EXE。
7.03-28 INT – 调用中断处理程序
[edit | edit source]INT (= Interrupt) 命令将控制权转移到该中断处理程序,该处理程序的编号由 INT 命令的操作数定义。但在将控制权转移之前,INT 命令会为中断处理程序任务完成后进一步返回到当前程序做好准备。因此,INT 命令会执行以下操作:
- 标志寄存器的当前状态保存在堆栈中;
- CS 寄存器(段地址)的当前状态保存在堆栈中;
- 计算下一个命令的偏移量,并将其保存在堆栈中,以便进一步恢复 IP 寄存器的状态;
- IF 标志被清除为 DI 状态,因此通过中断控制器接收的中断请求被阻止;
- CPU 中预取的命令队列被重置;
- 将中断号乘以 4,得到存储中断处理程序地址的内存单元的地址;
- 将中断处理程序的地址(段和偏移量)从内存单元复制到 CS:IP 寄存器中,将控制权转移到处理程序。
INT 命令在堆栈中留下的数据的顺序,可以通过 IRET 命令(7.03-30)返回到当前程序,IRET 命令必须是每个中断处理程序执行的最后一个命令。中断程序的恢复执行将从 INT 命令后面的命令开始。
几乎每个中断处理程序都需要满足某些特定条件,或者在 CPU 寄存器或内存中存在一些必要的数据才能执行其任务。这些条件和数据必须在执行 INT 命令之前预先准备好。本书第 8 章描述了所选中断处理程序的相关要求。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
CC | INT 3
| ||
CD | 1 | INT ff
|
- 注释
- 中断标志 IF 的原始状态不会影响 INT 命令的执行。IF 标志只影响通过中断控制器接收的外部中断请求。
- INT 3 命令(代码 CCh)的一个独特之处在于它不依赖于特权级别:在任何特权级别下执行,就像在实模式一样。
- 下一个命令的偏移量仅由 INT 和 INTO 命令(7.03-29)存储在堆栈中。所有其他内部中断(异常)都在堆栈中保留当前命令的偏移量。
- 堆栈中的数据可供中断处理程序使用。如果在控制权转移后立即将堆栈指针 (SP) 的状态保存在 BP 寄存器中,则 [BP+00] 地址指向返回偏移量,[BP+02] 地址指向返回段,[BP+04] 地址指向被中断程序的标志状态。
7.03-29 INTO – 调用溢出处理程序
[edit | edit source]通过 INT 00(8.01-01)对溢出的立即响应有时并不方便。INTO 命令(INTO = INTerrupt if Overflow)可以提供对溢出更灵活和延迟的响应。如果 OF 标志的 OV (= OVerflow) 状态指示溢出,则 INTO 命令会调用中断 INT 04 处理程序(8.01-05),该处理程序必须设计用于处理溢出错误。INTO 命令对 INT 04 处理程序的调用包括 INT 命令(7.03-28)采取的所有预防措施。
代码 | 示例 |
---|---|
CE | INTO |
- 注释
- 默认的 INT 04 处理程序除了将控制权返回给调用程序外,什么也不做。为了获得对溢出的理想响应,用户必须准备另一个 INT 04 处理程序,而不是默认的处理程序。新处理程序从其地址被写入中断表(8.02-18)开始生效。
7.03-30 IRET – 从中断处理程序返回
[edit | edit source]IRET (= Interrupt RETurn) 命令从堆栈中恢复所有数据,以便返回到调用程序的执行:CS 寄存器中的段地址、标志的以前状态以及 IP 寄存器中准备好的下一个命令的偏移量。IRET 命令必须是每个中断处理程序执行的最后一个命令。
代码 | 示例 |
---|---|
CF | IRET |
- 注释
- IRET 命令对标志状态的恢复不受对 POPF 命令(7.03-68)施加的限制。因此,IRET 命令提供了一种绕过这些限制的机会。
- IRET 命令重置 CPU 中预取的命令队列。这是因为中断处理程序的命令解码规则可能与调用程序的命令解码规则不同。
7.03-31 JA – 如果大于则跳转
[edit | edit source]JA 命令将它的数据字节加到 IP 寄存器的值上,如果 CF 和 ZF 标志都清除到 NC(无进位)和 NZ(无零)状态。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JA 命令最常用于无符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在有符号整数运算之后,相同的“大于”条件由 JG 命令检查,7.03-35)。
DEBUG.EXE 接受 JA 命令的另一个名称:JNBE - “如果小于或等于则跳转”,但代码 77h 始终被反汇编为 JA。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
77 | 1 | JA aaaa
|
7.03-32 JB - 如果小于则跳转
[edit | edit source]JB 命令将它的数据字节加到 IP 寄存器的值上,如果 CF 标志设置为 CY(进位)状态。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JB 命令用于在各种失败后执行跳转,这些失败通过将 CF 标志设置为 CY 状态来标记。JB 命令也用于无符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在有符号整数运算之后,相同的“小于”条件由 JL 命令检查,7.03-37)。
DEBUG.EXE 接受 JB 命令的另外两个名称:JNAE -“如果不大于或等于则跳转”和 JC - “如果进位则跳转”,但代码 72h 始终被反汇编为 JB。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
72 | 1 | JB aaaa
|
7.03-33 JBE - 如果小于或等于则跳转
[edit | edit source]JBE 命令将它的数据字节加到 IP 寄存器的值上,如果 CF 标志设置为 CY(进位)状态或 ZF 标志设置为 ZR(零)状态。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JBE 命令用于无符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在有符号整数运算之后,相同的“小于或等于”条件由 JLE 命令检查,7.03-38)。
DEBUG.EXE 接受此命令的另一个名称:JNA - “如果不大于则跳转”,但代码 76h 始终被反汇编为 JBE。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
76 | 1 | JBE aaaa
|
7.03-34 JCXZ - 如果 CX 为零则跳转
[edit | edit source]JCXZ 命令将它的数据字节加到 IP 寄存器的值上,如果 CX 寄存器中的值为零。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
由于 CX 寄存器通常用作迭代计数器,因此 JCXZ 命令可以绕过循环,如果在进入循环之前不满足必要条件。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
E3 | 1 | JCXZ aaaa
|
7.03-35 JG - 如果大于则跳转
[edit | edit source]JG 命令将它的数据字节加到 IP 寄存器的值上,如果 ZF 标志清除到 NZ(无零)状态,并且 SF 和 OF 标志处于相同状态,即它们都清除或都设置。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JG 命令用于有符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在无符号整数运算之后,相同的“大于”条件由 JA 命令检查,7.03-31)。
DEBUG.EXE 接受 JG 命令的另一个名称:JNLE - “如果小于或等于则跳转”,但代码 7Fh 始终被反汇编为 JG。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
7F | 1 | JG aaaa
|
7.03-36 JGE - 如果大于或等于则跳转
[edit | edit source]JGE 命令将它的数据字节加到 IP 寄存器的值上,如果 SF 和 OF 标志处于相同状态,即它们都清除或都设置。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JGE 命令用于有符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在无符号整数运算之后,相同的“大于或等于”条件由 JNB 命令检查,7.03-40)。
DEBUG.EXE 接受此命令的另一个名称:JNL - “如果不大于则跳转”,但代码 7Dh 始终被反汇编为 JGE。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
7D | 1 | JGE aaaa
|
7.03-37 JL - 如果小于则跳转
[edit | edit source]JL 命令将它的数据字节加到 IP 寄存器的值上,如果 SF 和 OF 标志处于不同状态,即当其中一个清除时,另一个设置。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JL 命令用于有符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在无符号整数运算之后,相同的“小于”条件由 JB 命令检查,7.03-32)。
DEBUG.EXE 接受此命令的另一个名称:JNGE - “如果不大于或等于则跳转”,但代码 7Ch 始终被反汇编为 JL。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
7C | 1 | JL aaaa
|
7.03-38 JLE - 如果小于或等于则跳转
[edit | edit source]JLE 命令将它的数据字节加到 IP 寄存器的值上,如果 ZF 标志设置为 ZR(零)状态或 SF 和 OF 标志处于不同状态,即当其中一个清除时,另一个设置。由于数据字节代表目标和当前偏移量之间的差,它的加法会导致一个“短”转移(跳转)到指定目标偏移量,该偏移量在最接近的下一个命令的 ±7Fh 范围内。
JLE 命令用于有符号整数运算之后,特别是 CMP、SBB、SUB 命令之后(在无符号整数运算之后,相同的“小于或等于”条件由 JBE 命令检查,7.03-33)。
DEBUG.EXE 接受 JLE 命令的另一个名称:JNG - “如果不大于则跳转”,但代码 7Eh 始终被反汇编为 JLE。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
7E | 1 | JLE aaaa
|
7.03-39 JMP - 无条件跳转
[edit | edit source]JMP 指令通过更改 IP(指令指针)寄存器或同时更改 CS(代码段)和 IP 寄存器的原有内容,执行到另一个机器指令(跳转)。如果 JMP 指令给定一个双字操作数,则它将作为“JMP FAR”执行:第一个字替换 CS 寄存器中以前的段地址,第二个字替换 IP 寄存器中以前的偏移量。带有单字操作数的 JMP 指令仅替换 IP 寄存器中的偏移量,从而在同一段内执行“近”跳转。带有单个字节数据的 JMP 指令执行“短”跳转,否则:它将数据字节添加到 IP 寄存器中的当前偏移量。
当 CPU 在实模式下运行时,JMP 指令不会影响标志。
第一 byte |
第二个字节 | 数据 字节 |
示例 | 注释 |
---|---|---|---|---|
E9 | 2 | JMP ffff |
注释 1 | |
EA | 4 | JMP ffff:ffff |
注释 2 | |
EB | 1 | JMP aaaa |
注释 1 | |
FF | (2,6,A)(0-7) | 0-2 | JMP [bp+si+ffff] |
注释 3 |
FF | (2,6,A)(8-F) | 0-2 | JMP FAR [bp+si+ffff] |
注释 3 |
FF | E(0-7) | JMP bx |
注释 4 |
- 注释
- ^ a b 在汇编指令行中给定目标偏移量后,DEBUG.EXE 会自动计算指定目标偏移量与下一条指令偏移量之间的差值。如果此差值不超过 ±7fh,则 JMP 指令将转换为机器代码 EBh(“短”跳转),否则将转换为机器代码 E9h(“近”跳转)。
- ^ 在所示示例中,第一个数字是段地址,第二个数字是目标偏移量。在这样的指令行中允许使用 FAR 标记,但并非必需:无论如何都会执行 FAR 跳转。
- ^ a b 当 JMP 指令通过间接寻址获取目标地址时,跳转类型取决于是否指定 FAR 标记:如果指定,则将从内存中读取一个 4 字节完整地址,并执行远跳转。如果未指定 FAR 标记,则将从内存中读取一个 2 字节字。这个字将被解释为目标偏移量,并将执行“近”跳转。
- ^ 如果 JMP 指令引用寄存器,则必须事先在该寄存器中准备目标偏移量。JMP 指令对 16 位寄存器的引用始终会导致“近”跳转。
- 几乎每一次 CPU 从实模式切换到保护模式或从保护模式切换回实模式后,都会紧跟着一条 JMP FAR 指令,将控制权转移到同一代码段中的下一条指令。这条 JMP 指令并非为了跳转,而是用于其他目的。首先,它使 CS 寄存器中的字(段地址或选择器)状态与 CPU 的模式相一致。其次,这条 JMP 指令会重置 CPU 中的预取指令队列,因为这些指令是根据 CPU 之前模式的规则进行解码的。
- 代码“FF E(8-F)”被 DEBUG.EXE 解汇编为指令“JMP far bx”。
7.03-40 JNB – 若不低于则跳转
[edit | edit source]如果 CF 标志被清除为 NC(无进位)状态,则 JNB 指令将其数据字节添加到 IP 寄存器的内容。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
JNB 指令用于在成功结束(由将 CF 标志清除为 NC 状态标记)后执行跳转。JNB 指令也用于无符号整数的操作之后,特别是 CMP、SBB、SUB 指令之后(在有符号整数的操作之后,相同的“大于或等于”条件由 JGE 指令检查,7.03-36)。
DEBUG.EXE 接受 JNB 指令的另外两个名称:JAE – “若大于或等于则跳转” 和 JNC – “若无进位则跳转”,但代码 73h 始终被解汇编为 JNB。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
73 | 1 | JNB aaaa
|
7.03-41 JNO – 若无溢出则跳转
[edit | edit source]如果 OF 标志被清除为 NV(无溢出)状态,则 JNO 指令将其数据字节添加到 IP 寄存器的内容。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
71 | 1 | JNO aaaa
|
7.03-42 JNS – 若无符号则跳转
[edit | edit source]如果 SF 标志被清除为 PL 状态,则 JNS 指令将其数据字节添加到 IP 寄存器的内容,PL 状态表示先前操作的结果为正整数。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
79 | 1 | JNS aaaa
|
7.03-43 JNZ – 若不为零则跳转
[edit | edit source]如果 ZF 标志被清除为 NZ(非零)状态,则 JNZ 指令将其数据字节添加到 IP 寄存器的内容,NZ 状态表示先前操作的结果不等于或不为零。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
DEBUG.EXE 接受 JNZ 指令的另外一个名称:JNE – “若不等于则跳转”,但代码 75h 始终被解汇编为 JNZ。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
75 | 1 | JNZ aaaa
|
7.03-44 JO – 若溢出则跳转
[edit | edit source]如果 OF 标志被设置为 OV(溢出)状态,则 JO 指令将其数据字节添加到 IP 寄存器的内容。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
70 | 1 | JO aaaa
|
7.03-45 JPE – 若奇偶性为偶数则跳转
[edit | edit source]如果 PF 标志被设置为 PE(奇偶性为偶数)状态,则 JPE 指令将其数据字节添加到 IP 寄存器的内容,PE 状态表示先前操作结果的最低有效字节中的位数之和为偶数(不考虑结果的其他字节)。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
DEBUG.EXE 接受 JPE 指令的另外一个名称:JP – “若奇偶性为偶数则跳转”,但代码 7Ah 始终被解汇编为 JPE。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
7A | 1 | JPE aaaa
|
7.03-46 JPO – 若奇偶性为奇数则跳转
[edit | edit source]如果 PF 标志被清除为 PO(奇偶性为奇数)状态,则 JPO 指令将其数据字节添加到 IP 寄存器的内容,PO 状态表示先前操作结果的最低有效字节中的位数之和为奇数(不考虑结果的其他字节)。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
DEBUG.EXE 接受 JPO 指令的另外一个名称:JNP – “若奇偶性不为偶数则跳转”,但代码 7Bh 始终被解汇编为 JPO。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
7B | 1 | JPO aaaa
|
7.03-47 JS – 若有符号则跳转
[edit | edit source]如果 SF 标志被设置为 NG 状态,则 JS 指令将其数据字节添加到 IP 寄存器的内容,NG 状态表示先前操作的结果为负整数。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
78 | 1 | JS aaaa
|
7.03-48 JZ – 若为零则跳转
[edit | edit source]如果 ZF 标志被设置为 ZR(零)状态,则 JZ 指令将其数据字节添加到 IP 寄存器的内容,ZR 状态表示先前操作的结果相等或为零。由于数据字节表示目标和当前偏移量之间的差值,因此添加它会导致“短”跳转(转移)到指定目标偏移量,该偏移量位于最近下一条指令的 ±7Fh 范围内。
DEBUG.EXE 接受 JZ 指令的另一个名称:JE - "jump if equal",但代码 74h 始终被反汇编为 JZ。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
74 | 1 | JZ aaaa
|
LAHF 指令 (LAHF = Load AH with Flags) 将标志寄存器低字节的标志状态复制到 AH 寄存器。AH 中的位 0 对应于 CF (进位标志),位 2 对应于 PF (奇偶校验标志),位 4 对应于 AF (辅助标志),位 6 对应于 ZF (零标志),位 7 对应于 SF (符号标志)。位 5、3、1 没有相应的标志。位 1 始终设置为二进制 1,位 5 和位 3 始终清零。
代码 | 示例 |
---|---|
9F | LAHF |
LDS 指令将其第二个操作数视为双字的地址。该双字中的字节 1 和 2 被解释为偏移量,字节 3 和 4 被解释为段地址。LDS 指令将此段地址复制到 DS 段寄存器,并将偏移量复制到指定为 LDS 指令第一个操作数的寄存器。因此,该寄存器与 DS 段寄存器一起,就可以作为段:偏移量对被引用。LDS 指令不会改变标志的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
C5 | (1,5,9)(8-F) | 0-2 | LDS bx,[bp+si+ffff]
|
- 注释
- 代码 "C5 (C-F)(0-F)" 也被 DEBUG.EXE 反汇编为 LDS 指令。
- 默认情况下,DS:SI 寄存器对表示源地址;因此,SI 寄存器是 LDS 指令示例中 "bx" 的最常见替代。
- DS 寄存器中的段和指定寄存器中的偏移量都可用于寻址,并且可以在同一个操作中重新分配;例如,命令 DS: LDS SI,[SI] 是有效的。
LEA 指令 (LEA = Load Effective Address) 计算方括号内的表达式,该表达式作为第二个操作数给出。计算结果表示一个特定的偏移量。该偏移量被写入到指定为 LEA 指令第一个操作数的寄存器中。LEA 指令不会改变标志的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
8D | (0-B)(0-F) | 0-2 | LEA bx,[bp+si+ffff]
|
LES 指令将其第二个操作数视为双字的地址。该双字中的字节 1 和 2 被解释为偏移量,字节 3 和 4 被解释为段地址。LES 指令将此段地址复制到 ES 段寄存器,并将偏移量复制到指定为 LES 指令第一个操作数的寄存器。因此,该寄存器与 ES 段寄存器一起,就可以作为段:偏移量对被引用。LES 指令不会改变标志的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
C4 | (1,5,9)(8-F) | 0-2 | LES bx,[bp+si+ffff]
|
- 注释
- 代码 "C4 (C-F)(0-F)" 也被 DEBUG.EXE 反汇编为 LES 指令。
- 默认情况下,ES:DI 寄存器对表示目标地址;因此,DI 寄存器是 LES 指令示例中 "bx" 的最常见替代。
- ES 寄存器中的段和指定寄存器中的偏移量都可用于寻址,并且可以在同一个操作中重新分配;例如,命令
ES: LES DI,[DI]
是有效的。
虽然 LODSB 的名称代表 "LOaD String of Bytes",但实际上 LODSB 指令将根据预先写入 DS:SI 寄存器对的地址从内存中读取单个字节并复制到 AL 寄存器。复制后,SI (源索引) 寄存器中的偏移量将增加 1 或减少 1:这取决于方向标志 DF 的状态 ("UP" 或 "DN")。DF 标志的状态可以通过 CLD (7.03-11) 和 STD (7.03-85) 指令改变。SI 寄存器内容的自动更改为复制下一个字节准备了条件。LODSB 指令不会改变标志的状态。
LODSB 指令可以由段覆盖前缀 (7.02-01) précéder ; 它允许引用其他段寄存器,而不是默认的源段寄存器 DS。
代码 | 示例 |
---|---|
AC | LODSB |
LODSW 指令 (LODSW = LOaD a String of Words) 将单个字复制到 AX 寄存器,然后将 SI 索引寄存器的内容增加 (或减少) 2,从而为复制下一个字准备偏移量。操作数大小覆盖前缀 66h (7.02-06) 强制 LODSW 指令复制四字节操作数 (DWORD 类型) 并将 SI 寄存器的内容增加 (或减少) 4。LODSW 指令执行的其他所有特殊情况与 LODSB 指令 (7.03-53) 相同。
代码 | 示例 |
---|---|
AD | LODSW |
LOOP 指令首先将 CX 寄存器中的一个整数减少 1,然后检查余数是否为零。只要余数不为零,LOOP 指令就会将其数据字节添加到 IP 寄存器中的当前偏移量。因此,在最近的下一条指令的 ±7fh 范围内执行 "短" 跳转。但是,当 CX 寄存器中的余数变为零时,LOOP 指令不会执行任何操作,因此 CPU 退出循环并继续执行循环体之外的最近的下一条指令。LOOP 指令不会改变标志的状态。
CX 寄存器中的迭代次数基于 LOOP 指令位于循环体之后的假设。在这种情况下,循环体在循环进入条件第一次被 LOOP 指令检查之前执行一次。为了防止循环体不受控制的执行,循环体之前应加上 JCXZ 指令 (7.03-34)。通过带有导出体的循环可以获得相同的结果,但在这种情况下,CX 寄存器中预设的整数必须比所需的迭代次数大 1。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
E2 | 1 | LOOP aaaa
|
LOOPNZ 指令 (LOOPNZ = Loop if Not Zero) 首先将 CX 寄存器中的一个整数减少 1,不影响标志,然后检查两个条件 : CX 寄存器中的余数是否为零以及 ZF 标志是否被设置为 ZR (ZeRo) 状态。只要这两个条件都满足,LOOPNZ 指令就会将其数据字节添加到 IP 寄存器中的当前偏移量。因此,在最近的下一条指令的 ±7fh 范围内执行 "短" 跳转。但是,当其中一个条件满足时,LOOPNZ 指令不会执行任何操作,因此 CPU 退出循环并继续执行循环体之外的最近的下一条指令。使用 LOOPNZ 指令安排循环的其他特殊情况与 LOOP 指令 (7.03-55) 相同。
DEBUG.EXE 接受 LOOPNZ 指令的另一个名称:LOOPNE (= loop, if not equal),但代码 E0h 始终被反汇编为 LOOPNZ。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
E0 | 1 | LOOPNZ aaaa
|
LOOPZ 指令 (LOOPZ = Loop if Zero) 首先将 CX 寄存器中的一个整数减少 1,不影响标志,然后检查两个条件 : CX 寄存器中的余数是否为零以及 ZF 标志是否被清零为 NZ (No Zero) 状态。只要这两个条件都满足,LOOPZ 指令就会将其数据字节添加到 IP 寄存器中的当前偏移量。因此,在最近的下一条指令的 ±7fh 范围内执行 "短" 跳转。但是,当其中一个条件满足时,LOOPZ 指令不会执行任何操作,因此 CPU 退出循环并继续执行循环体之外的最近的下一条指令。使用 LOOPZ 指令安排循环的其他特殊情况与 LOOP 指令 (7.03-55) 相同。
DEBUG.EXE 接受 LOOPZ 指令的另一个名称:LOOPE (loop, if equal),但代码 E1h 始终被反汇编为 LOOPZ。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
E1 | 1 | LOOPZ aaaa
|
7.03-58 MOV - 数据复制指令
[edit | edit source]MOV 命令将由第二个操作数直接或间接指定的一个字节或一个字复制到由第一个操作数指定的寄存器或内存单元中。当可以通过涉及的寄存器的大小确定要复制的数据的大小(字节或字)时,不需要明确指定要复制的数据的大小。除了调用控制、调试和测试 CPU 寄存器的形式之外,普通形式的 MOV 命令不会更改标志的状态。这些形式,如以下 [注释 1] 中所示,可能会将标志 OF、SF、ZF、AF、PF、CF 留在不确定的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
88 | (0-B)(0-5, 7-F) | 0-2 | MOV [bp+si+ffff],bl
|
88 | (C-F)(0-F) | MOV bl,bl
| |
89 | (0-B)(0-5, 7-F) | 0-2 | MOV [bp+si+ffff],bx
|
89 | (C-F)(0-F) | MOV bx,bx
| |
8A | (0-B)(0-5, 7-F) | 0-2 | MOV bl,[bp+si+ffff]
|
8B | (0-B)(0-5, 7-F) | 0-2 | MOV bx,[bp+si+ffff]
|
8C | (0,1,4,5,8,9)(0-F) | 0-2 | MOV [bp+si+ffff],ss
|
8C | (C,D,E)(0-F) | MOV bx,ss
| |
8E | (0,1,4,5,8,9)(0-F) | 0-2 | MOV ss,[bp+si+ffff]
|
8E | (C,D,E)(0-F) | MOV ss,bx
| |
A0 | 2 | MOV AL,[ffff]
| |
A1 | 2 | MOV AX,[ffff]
| |
A2 | 2 | MOV [ffff],AL
| |
A3 | 2 | MOV [ffff],AX
| |
B(0-7) | 1 | MOV bl,ff
| |
B(8-F) | 2 | MOV bx,ffff
| |
C6 | (0,4,8)(0-7) | 1-3 | MOV byte ptr [bp+si+ffff],ff
|
C7 | (0,4,8)(0-7) | 2-4 | MOV word ptr [bp+si+ffff],ffff
|
- 注释
- ^ a b DEBUG.EXE 不“知道”这些形式的 MOV 命令,这些命令调用 32 位 CPU 的调试和控制寄存器,但这些命令的代码可以通过 DB 指令(7.01-01)作为数据输入。这些命令的代码长度为 3 个字节,以 OFh 字节开头。第二个字节定义复制的方向
- 20h – 来自控制寄存器 (CR0, CR2–CR4)
- 21h – 来自调试寄存器 (DR0–DR3, DR6, DR7)
- 22h – 进入控制寄存器 (CR0, CR2–CR4)
- 23h – 进入调试寄存器 (DR0–DR3, DR6, DR7)
C0h – CR0 或 DR0,例如, 0F 20 C0 = MOV EAX,CR0 C8h – DR1,例如, 0F 23 C8 = MOV DR1,EAX D0h – CR2 或 DR2,例如, 0F 20 D0 = MOV EAX,CR2 D8h – CR3 或 DR3,例如, 0F 20 D8 = MOV EAX,CR3 E0h – CR4,例如, 0F 22 E0 = MOV CR4,EAX F0h – DR6,例如, 0F 21 F0 = MOV EAX,DR6 F8h – DR7,例如, 0F 23 F8 = MOV DR7,EAX 为了使用 EAX 以外的其他寄存器,您必须在以下列表中将该寄存器的编号(从 00h 到 07h)加到第三个字节中
EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI,例如 0F 20 C3 = MOV EBX,CR0 - DEBUG.EXE 不“知道”调用 32 位 CPU 的段寄存器 GS 和 FS 的命令,但这些命令的代码可以通过 DB 指令(7.01-01)作为数据输入。这些命令的代码长度为 2 个字节
8C E0 = MOV AX,FS 8C E8 = MOV AX,GS 8E E0 = MOV FS,AX 8E E8 = MOV GS,AX 为了使用 AX 以外的其他寄存器,您必须在 表 7.00 的第二行中给出的列表中将该寄存器的编号(从 00h 到 07h)加到第二个字节中,例如
8E E3 = MOV FS,BX - MOV 命令不能将数据复制到 CS 段寄存器;这只能通过控制转移命令(CALL、JMP、RETF 等)来完成。
- 将数据复制到 SS 寄存器的 MOV 命令会在执行下一个命令的时间内触发硬件阻塞外部中断。这意味着下一个命令必须将新的偏移量写入 SP 寄存器。只有这种命令顺序才能排除在切换到另一个堆栈时由外部中断引起的事故。
- 代码 8(A,B) (C-F)(0-F)、8(C,E)(2,3,6,7,A,B,F)(0-F) 和 C(6,7) (C-F)(0-F) 也由 DEBUG.EXE 反汇编为 MOV 命令。
7.03-59 MOVSB – 字节串的串行复制
[edit | edit source]虽然 MOVSB 代表“移动字节串”,但 MOVSB 命令实际上只复制一个字节。源字节地址必须事先加载到 DS:SI 寄存器对中;目标地址加载到 ES:DI 寄存器中。复制后,SI(源索引)寄存器和 DI(目标索引)寄存器中的两个偏移量都会增加 1 或减少 1:这取决于方向标志 DF 的状态(“UP”或“DN”)。DF 标志的状态可以通过 CLD(7.03-11)和 STD(7.03-85)命令更改。索引寄存器内容的自动更改为在下一个内存单元中复制下一个字节准备了条件。MOVSB 命令不会更改标志的状态。
MOVSB 命令通常以重复前缀 F2h(7.02-03)或 F3h(7.02-04)开头,这些前缀可以循环执行它,从而复制一个字节串。MOVSB 命令也可以以段覆盖前缀(7.02-01)开头;它可以引用其他段寄存器而不是默认的源段寄存器 DS。目标段寄存器 ES 无法通过前缀更改。
代码 | 示例 |
---|---|
A4 | MOVSB |
7.03-60 MOVSW – 字的串行复制
[edit | edit source]MOVSW 命令(MOVSW = 移动字串)复制一个字,然后将 SI 和 DI 索引寄存器的内容增加(或减少) 2,从而将源和目标偏移量准备为将下一个字复制到下一对内存单元中。操作数大小覆盖前缀 66h(7.02-06)强制 MOVSW 命令复制 4 字节的操作数(DWORD 类型)并将索引寄存器的内容增加(或减少) 4。MOVSW 命令执行的其他所有特性与 MOVSB 命令(7.03-59)相同。
代码 | 示例 |
---|---|
A5 | MOVSW |
7.03-61 MUL – 无符号整数的乘法
[edit | edit source]MUL 命令(MUL = 乘法)将无符号整数相乘(有关有符号整数的乘法,请参见 7.03-25)。MUL 命令的显式操作数表示乘数。如果此操作数是一个字节,则隐式认为另一个操作数存在于 AL 寄存器中;乘法后,积保留在 AX 寄存器中。如果显式操作数是一个字,则隐式认为另一个操作数存在于 AX 寄存器中;乘法后,积的低 2 个字节保留在 AX 寄存器中,积的高 2 个字节保留在 DX 寄存器中。
如果积在 AH 或 DX 寄存器中的最高部分表示非零值,则 MUL 命令会将 OF 和 CF 标志分别设置为 OV 和 CY 状态。相反,这些标志的清除状态 NV 和 NC 表示积的最高部分已用零填充。标志 SF、ZF、AF、PF 将获取不确定的状态。
MUL 命令可应用于二进制整数和未打包的十进制字节。打包十进制操作数必须事先解包。未打包的十进制字节的积需要通过 AAM 命令(7.03-03)转换为未打包的十进制格式。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
F6 | (2,6,A)(0-7) | 0-2 MUL byte ptr [bp+si+ffff] | |
F6 | E(0-7) | MUL bl
| |
F7 | (2,6,A)(0-7) | 0-2 MUL word ptr [bp+si+ffff] | |
F7 | E(0-7) | MUL bx
|
7.03-62 NEG – 操作数的符号反转
[edit | edit source]NEG 命令(NEG = 否定)从零中减去其操作数。因此,非零操作数的符号被反转,但零操作数保持不变。标志(OF、SF、ZF、AF、PF、CF)根据结果获取新的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
F6 | (1,5,9)(8-F) | 0-2 | NEG byte ptr [bp+si+ffff]
|
F6 | D(8-F) | NEG bl
| |
F7 | (1,5,9)(8-F) | 0-2 | NEG word ptr [bp+si+ffff]
|
F7 | D(8-F) | NEG bx
|
7.03-63 NOP – 空操作
[edit | edit source]虽然 NOP 命令(NOP = 无操作)已知什么也不做,但它实际上将 IP(指令指针)增加 1,因此 IP 指向下一条命令。
代码 | 示例 |
---|---|
90 | NOP |
7.03-64 NOT – 操作数位的反转
[edit | edit source]NOT 命令对操作数中的每一位执行逻辑非操作。NOT 命令不会更改标志的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例(“aaaa” - 目标偏移量) |
---|---|---|---|
F6 | (1,5,9)(0-7) | 0-2 | NOT byte ptr [bp+si+ffff]
|
F6 | D(0-7) | NOT bl
| |
F7 | (1,5,9)(0-7) | 0-2 | NOT word ptr [bp+si+ffff]
|
F7 | D(0-7) | NOT bx
|
7.03-65 OR – 逻辑或运算
[edit | edit source]OR 命令分析两个操作数中对应位的对。如果一对中至少一位处于 TRUE 状态,则结果的对应位也被设置为 TRUE 状态。如果一对中的两个位都被清除为 FALSE 状态,则结果的对应位也被清除为 FALSE 状态。结果替换第一个操作数。标志 SF、ZF、PF 根据结果获取新的状态。标志 CF 和 OF 分别被清除为状态 NC(无进位)和 NV(无溢出)。标志 AF 获取不确定的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
08 | (0-B)(0-F) | 0-2 | OR [bp+si+ffff],bl
|
08 | (C-F)(0-F) | OR bl,bl
| |
09 | (0-B)(0-F) | 0-2 | OR [bp+si+ffff],bx
|
09 | (C-F)(0-F) | OR bx,bx
| |
0A | (0-B)(0-F) | 0-2 | OR bl,[bp+si+ffff]
|
0B | (0-B)(0-F) | 0-2 | OR bx,[bp+si+ffff]
|
0C | 1 | OR AL,ff
| |
0D | 2 | OR AX,ffff
| |
80 | (0,4,8)(8-F) | 1-3 | OR byte ptr [bp+si+ffff],ff
|
80 | C(9-F) | 1 | OR bl,ff
|
81 | (0,4,8)(8-F) | 2-4 | OR word ptr [bp+si+ffff],ffff
|
81 | C(9-F) | 2 | OR bx,ffff
|
83 | (0,4,8)(8-F) | 1-3 | OR word ptr [bp+si+ffff],±7f
|
83 | C(9-F) | 1 | OR bx,±7f
|
- 注释
- 代码“0(A,B) (C-F)(0-F)” 和 “82 (0,4,8,C)(8-F)” 也会被 DEBUG.EXE 反汇编为 OR 命令。
- 当 OR 命令应用于相等的操作数时,这些操作数不会被更改。例如,OR AX,AX 命令通常仅用于设置标志。
执行 OUT 命令时,CPU 生成一个信号,该信号将 CPU 的总线从内存切换到 I/O 端口。OUT 命令的第一个操作数明确地指定目标端口地址,以两位十六进制数表示,或间接地指定 DX 寄存器的内容。OUT 命令的第二个操作数定义数据源寄存器:如果要发送字节,则为字节寄存器 AL;如果要发送字,则为双字节寄存器 AX。CPU 标志的状态不受 OUT 命令影响。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
E6 | 1 | OUT ff,AL
| |
E7 | 1 | OUT ff,AX
| |
EE | OUT DX,AL
| ||
EF | OUT DX,AX
|
- 注释
- 选定的端口地址显示在附录 A.14-1 中。OUT 命令的直接形式不允许端口地址高于 FFh。通过 DX 寄存器进行的间接寻址不受此限制。
- 如果当前程序的特权级别低于标志寄存器中位 0Ch 和 0Dh 定义的 I/O 操作的特权级别 (A.11-4),则不会执行 OUT 命令。
POP 命令将一个数据字(2 字节)从堆栈顶部复制到指定的寄存器或内存单元,然后通过将 SP 寄存器(堆栈指针)中的偏移量增加 2 来移动堆栈顶部。标志的状态不受 POP 命令影响。
第一 byte |
第二个字节 | 数据 字节 |
示例 | 注释 |
---|---|---|---|---|
07 | POP ES
| |||
0F | A1 | DB 0F A1 |
= POP FS
| |
0F | A9 | DB 0F A9 |
= POP GS
| |
17 | POP SS
| |||
1F | POP DS
| |||
5(8-F) | POP bx
| |||
8F | (0,8)(0-7) | 0-2 | POP [bp+si+ffff]
|
- 注释
- 从堆栈中弹出数据到 FS 和 GS 段寄存器的命令不被 DEBUG.EXE “识别”,但可以通过 DB 指令 (7.01-01) 输入。DEBUG.EXE 无法反汇编这些命令的代码。然而,如果程序由 32 位处理器执行,DEBUG.EXE 允许调试包含这些代码的程序。
- 代码“8F (C-F)(0-F)” 被 DEBUG.EXE 反汇编为“POP bx”。
POPF 命令将数据字(2 字节)从堆栈顶部复制到标志寄存器,然后通过将 SP 寄存器(堆栈指针)中的偏移量增加 2 来移动堆栈顶部。标志获取新的状态,这些状态由弹出的数据字的位定义。
代码 | 示例 |
---|---|
9D | POPF |
- 注释
- 如果当前程序在任何非最高特权级别执行,POPF 命令无法更改标志寄存器中 I/O 特权级别字段(位 0Ch 和 0Dh)的状态 (A.11-4)。
- 如果当前程序的特权级别低于标志寄存器中位 0Ch 和 0Dh 定义的 I/O 操作的特权级别 (A.11-4),POPF 命令无法更改 IF 标志的状态。
- 在操作数大小覆盖前缀 66h (7.02-06) 之前,POPF 命令从堆栈中弹出 4 个字节到扩展的 32 位标志寄存器中。但是,这种访问 V86 模式标志的方法被硬件阻止(有关更多信息,请参阅A.11-4 的注释 4 和 5)。
PUSH 命令将 SP 寄存器(堆栈指针)递减 2,从而为新数据扩展堆栈两个内存单元。然后,数据从 PUSH 命令的操作数定义的源中复制到这些内存单元中。标志的状态不受 PUSH 命令影响。
第一 byte |
第二个字节 | 数据 字节 |
示例 | 注释 |
---|---|---|---|---|
06 | PUSH ES
| |||
0E | PUSH CS
| |||
0F | A0 | DB 0F A0 |
= PUSH FS
| |
0F | A8 | DB 0F A8 |
= PUSH GS
| |
16 | PUSH SS
| |||
1E | PUSH DS
| |||
5(0-7) | PUSH bx
| |||
68 | 2 | DB 68 ff ff |
= PUSH ffff
| |
6A | 1 | DB 6A ff |
= PUSH 00ff
| |
FF | (3,7,B)(0-7) | 0-2 | PUSH [bp+si+ffff]
|
- 注释
- 将显式整数和段地址从 FS 和 GS 寄存器中压入的命令不被 DEBUG.EXE “识别”,但可以通过 DB 指令 (7.01-01) 作为数据输入。DEBUG.EXE 无法反汇编这些命令的代码。然而,如果程序由 32 位处理器执行,DEBUG.EXE 允许调试包含这些代码的程序。
- 建议避免 PUSH SP 操作。旧的 CPU 先递减 SP,然后复制其值。大多数现代 CPU 存储原始 SP 内容。因此,在某些计算机中,PUSH SP 操作可能会导致不可预测的程序行为。
- 代码“FF F(0-7)” 也会被 DEBUG.EXE 反汇编为“PUSH bx”。
PUSHF 命令(PUSHF = PUSH Flags)将两个字节从标志寄存器复制到堆栈中,就像 PUSH 命令 (7.03-69) 复制一个数据字一样。执行的所有特殊之处都相同。
代码 | 示例 |
---|---|
9C | PUSHF |
- 注释
- 在操作数大小覆盖前缀 66h (7.02-06) 之前,PUSHF 命令将扩展的 32 位标志寄存器中的 4 个字节复制到堆栈中。[注释 4 到 A.11-4] 但是,PUSHF 命令复制 V86 模式标志状态的操作被硬件阻止。
RCL 命令(RCL = Rotate through Carry Leftward)通过进位标志向左排列其第一个操作数的循环移位,朝向更重要的位位置。在每一步,最高有效位都变成 CF 标志的状态,而操作数的最低有效位则获得 CF 标志的先前状态。OV 标志的状态也可能改变,但其他标志保留其以前的状态。
第二个操作数(1 或 CL)定义向左移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (1,5,9)(0-7) | 0-2 | RCL byte ptr [bp+si+ffff],1
|
D0 | D(0-7) | RCL bl,1
| |
D1 | (1,5,9)(0-7) | 0-2 | RCL word ptr [bp+si+ffff],1
|
D1 | D(0-7) | RCL bx,1
| |
D2 | (1,5,9)(0-7) | 0-2 | RCL byte ptr [bp+si+ffff],CL
|
D2 | D(0-7) | RCL bl,CL
| |
D3 | (1,5,9)(0-7) | 0-2 | RCL word ptr [bp+si+ffff],CL
|
D3 | D(0-7) | RCL bx,CL
|
RCR 命令(RCR = Rotate through Carry to the Right)通过进位标志向右排列其第一个操作数的循环移位,朝向更不重要的位位置。在每一步,最低有效位都变成 CF 标志的状态,而操作数的最高有效位则获得 CF 标志的先前状态。OV 标志的状态也可能改变,但其他标志保留其以前的状态。
第二个操作数(1 或 CL)定义向右移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (1,5,9)(8-F) | 0-2 | RCR byte ptr [bp+si+ffff],1
|
D0 | D(8-F) | RCR bl,1
| |
D1 | (1,5,9)(8-F) | 0-2 | RCR word ptr [bp+si+ffff],1
|
D1 | D(8-F) | RCR bx,1
| |
D2 | (1,5,9)(8-F) | 0-2 | RCR byte ptr [bp+si+ffff],CL
|
D2 | D(8-F) | RCR bl,CL
| |
D3 | (1,5,9)(8-F) | 0-2 | RCR word ptr [bp+si+ffff],CL
|
D3 | D(8-F) | RCR bx,CL
|
RET 命令从子程序中执行返回到调用程序,子程序位于同一个代码段中,并通过 CALL 命令调用,目标地址为双字节(对于通过 CALL FAR 命令调用,目标地址为 4 字节的子程序,必须使用 RETF 命令,7.03-74)。
RET 命令表示堆栈寄存器顶部包含返回偏移量,即调用程序中下一条指令的偏移量。如果终止子程序在堆栈中不留任何内容,则不需要 RET 命令的操作数。但是,子程序可能通过堆栈接受参数,在终止时,必须删除这些参数。因此,RET 命令的操作数定义了要从堆栈中删除的字节数。RET 命令将返回偏移量从堆栈弹出到 IP(指令指针)寄存器,然后将其操作数加到 SP(堆栈指针)寄存器的内容中。这样就执行了返回到调用程序的操作,并且恢复了堆栈顶部的原始位置。RET 命令不会更改标志的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
C2 | 2 | RET ffff
| |
C3 | RET
|
- 注释
- 如果通过 RET FFFE 命令执行返回,则可以将返回偏移量处的堆栈顶部位置保留。
- 如果通过 DEBUG.EXE 汇编的代码将在调试器的环境中执行,则可以使用 RET 命令来终止此代码的执行(9.02-03 中的示例)。
RETF 命令 (RETF = RETurn Far) 执行 RET 命令 (7.03-73) 的所有操作,此外,它还从堆栈中恢复 CS 寄存器中的段地址。因此,实现了从另一个代码段返回到调用程序的操作。
RETF 命令用作那些通过 CALL FAR 命令 (7.02-08) 从其他代码段调用的子程序和驱动程序的退出,这些子程序和驱动程序使用完整的 4 字节地址,以便将调用程序的原始段地址保存到堆栈中。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
CA | 2 | RETF ffff
| |
CB | RETF
|
ROL 命令 (ROL = ROtate Leftward) 对其第一个操作数进行循环左移,向更重要的位位置移动。在每一步中,最低有效位获取最有效位的“弹出”的先前状态。CF 和 OV 标志的状态根据结果更改,但所有其他标志保持其先前状态。
第二个操作数(1 或 CL)定义向左移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (0,4,8)(0-7) | 0-2 | ROL byte ptr [bp+si+ffff],1
|
D0 | C(0-7) | ROL bl,1
| |
D1 | (0,4,8)(0-7) | 0-2 | ROL word ptr [bp+si+ffff],1
|
D1 | C(0-7) | ROL bx,1
| |
D2 | (0,4,8)(0-7) | 0-2 | ROL byte ptr [bp+si+ffff],CL
|
D2 | C(0-7) | ROL bl,CL
| |
D3 | (0,4,8)(0-7) | 0-2 | ROL word ptr [bp+si+ffff],CL
|
D3 | C(0-7) | ROL bx,CL
|
ROR 命令 (ROR = ROtate to the Right) 对其第一个操作数进行循环右移,向最低有效位位置移动。在每一步中,最有效位获取最低有效位的“弹出”的先前状态。CF 和 OV 标志的状态根据结果更改,但所有其他标志保持其先前状态。
第二个操作数(1 或 CL)定义向右移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (0,4,8)(8-F) | 0-2 | ROR byte ptr [bp+si+ffff],1
|
D0 | C(8-F) | ROR bl,1
| |
D1 | (0,4,8)(8-F) | 0-2 | ROR word ptr [bp+si+ffff],1
|
D1 | C(8-F) | ROR bx,1
| |
D2 | (0,4,8)(8-F) | 0-2 | ROR byte ptr [bp+si+ffff],CL
|
D2 | C(8-F) | ROR bl,CL
| |
D3 | (0,4,8)(8-F) | 0-2 | ROR word ptr [bp+si+ffff],CL
|
D3 | C(8-F) | ROR bx,CL
|
SAHF 命令 (SAHF = Store AH in Flags) 将 AH 寄存器中的一个字节复制到标志寄存器的下半部分。位 7 将定义 SF(符号标志)的状态,位 6 – ZF(零标志)的状态,位 4 – AF(辅助标志)的状态,位 2 – PF(奇偶标志)的状态,位 0 – CF(进位标志)的状态。虽然 SAHF 命令没有定义溢出标志 OF 的状态,但它可能会获得不确定的状态。AH 寄存器中的位 5、3、1 不对应于实际标志,它们的状态将被忽略。
代码 | 示例 |
---|---|
9E | SAHF |
SAR 命令 (SAR = Shift Arithmetic to the Right) 将其有符号整数操作数右移,向最低有效位位置移动。在每一步移位中,最右边的位的状态将丢失,最左边的位将获得符号位的状态。ZF、PF、CF 标志根据结果获取新的状态。OF 和 AF 标志获得不确定的状态。
第二个操作数(1 或 CL)定义向右移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (3,7,B)(8-F) | 0-2 | SAR byte ptr [bp+si+ffff],1
|
D0 | F(8-F) | SAR bl,1
| |
D1 | (3,7,B)(8-F) | 0-2 | SAR word ptr [bp+si+ffff],1
|
D1 | F(8-F) | SAR bx,1
| |
D2 | (3,7,B)(8-F) | 0-2 | SAR byte ptr [bp+si+ffff],CL
|
D2 | F(8-F) | SAR bl,CL
| |
D3 | (3,7,B)(8-F) | 0-2 | SAR word ptr [bp+si+ffff],CL
|
D3 | F(8-F) | SAR bx,CL
|
SBB 命令从第一个操作数(被减数)中减去其第二个操作数(减数),同时考虑前一个操作后留下的借位,该借位由 CF(进位标志)的状态表示。余数将替换第一个操作数。OF、SF、ZF、AF、PF、CF 标志根据结果获取新的状态。
SBB 命令留下的标志状态的解释取决于操作数是有符号数还是无符号数。在减去无符号数后,应使用条件跳转命令 JA、JB、JBE、JNB。在减去有符号数后,应使用其他条件跳转命令 JG、JGE、JL、JLE。所有条件跳转和循环命令的全名反映了 SBB 命令的第一个(左)操作数与第二个(右)操作数的状态关系。例如,JA = “如果大于则跳转”意味着 SBB 命令的左操作数(被减数)必须大于或等于右操作数(减数)。
SBB 是一个二进制操作,但有两个例外。如果第一个操作数位于 AX 寄存器中,则 SBB 命令可以应用于非压缩十进制字:AX 寄存器中非压缩十进制字的二进制差可以通过 AAS 命令 (7.03-04) 转换为有效的非压缩十进制字。如果第一个操作数位于 AL 寄存器中,则 SBB 命令可以应用于压缩十进制字节:AL 寄存器中压缩十进制字节的二进制差可以通过 DAS 命令 (7.03-19) 转换为有效的压缩十进制字节。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
18 | (0-B)(0-F) | 0-2 | SBB [bp+si+ffff],bl
|
18 | (C-F)(0-F) | SBB bl,bl
| |
19 | (0-B)(0-F) | 0-2 | SBB [bp+si+ffff],bx
|
19 | (C-F)(0-F) | SBB bx,bx
| |
1A | (0-B)(0-F) | 0-2 | SBB bl,[bp+si+ffff]
|
1B | (0-B)(0-F) | 0-2 | SBB bx,[bp+si+ffff]
|
1C | 1 | SBB AL,ff
| |
1D | 2 | SBB AX,ffff
| |
80 | (1,5,9)(8-F) | 1-3 | SBB byte ptr [bp+si+ffff],ff
|
80 | D(9-F) | 1 | SBB bl,ff
|
81 | (1,5,9)(8-F) | 2-4 | SBB word ptr [bp+si+ffff],ffff
|
81 | D(9-F) | 2 | SBB bx,ffff
|
83 | (1,5,9)(8-F) | 1-3 | SBB word ptr [bp+si+ffff],±7f
|
83 | D(9-F) | 1 | SBB bx,±7f
|
- 注释
- 代码“1(A,B) (C-F)(0-F)” 和 “82 (1,5,9,D)(8-F)” 也被 DEBUG.EXE 作为 SBB 命令进行反汇编。
7.03-80 SCASB – 搜索特定字节
[edit | edit source]尽管 SCASB 的名称代表“扫描字节字符串”,但实际上 SCASB 命令将 AL 寄存器中的一个字节与另一个字节进行比较,另一个字节从内存中读取。另一个字节的地址必须事先加载到 ES:DI 寄存器对中。如果字节相等,ZF(零标志)被设置为 ZR 状态。如果字节不相等,ZF 标志被清除为 NZ(非零)状态。标志 OF、SF、AF、PF、CF 根据比较字节之间的差异获取状态,但差异本身不会被保存。
比较后,DI(目标索引)寄存器中的偏移量增加或减少 1:这取决于方向标志 DF 的状态(“向上”或“向下”)。DF 标志的状态可以通过 CLD(7.03-11)和 STD(7.03-85)命令更改。索引寄存器内容的自动更改为将 AL 内容与下一个内存单元中的字节进行比较准备条件。
SCASB 命令通常由重复前缀 F2h(7.02-03)或 F3h(7.02-04)引导,这使它能够循环执行,从而在字节字符串中搜索特定字节。该字节字符串的默认段寄存器 ES 无法通过段覆盖前缀更改。
代码 | 示例 |
---|---|
AE | SCASB |
- 注释
- 当 SCASB 命令由重复前缀 F2h 或 F3h 引导时,循环内操作的顺序包括分配标志状态,然后增加(或减少)索引寄存器的内容,然后检查循环终止条件。因此,DI 寄存器中循环终止时的偏移量指向的不是导致循环终止的数据字节,而是下一个字节。
7.03-81 SCASW – 搜索特定字
[edit | edit source]SCASW 命令(SCASW = 扫描字字符串)将 AX 寄存器中的一个字与另一个字进行比较,另一个字从内存中读取,然后将另一个字在 DI 索引寄存器中的偏移量增加(或减少) 2,从而准备将 AX 内容与下一个内存单元中的字进行比较。操作数大小覆盖前缀 66h(7.02-06)强制 SCASW 命令将 EAX 寄存器中的一个四字节操作数与相同 DWORD 类型的另一个操作数进行比较,并将 DI 索引寄存器中的偏移量增加(或减少) 4。SCASW 命令执行的其他所有特殊之处与 SCASB 命令(7.03-80)相同。
代码 | 示例 |
---|---|
AF | SCASW |
7.03-82 SHL – 左移
[edit | edit source]SHL 命令将它的第一个操作数逐步左移,移向更重要的位位置。在每一步中,最高有效位的状态被移入进位标志 CF,而最低有效位获得零(清除)状态。标志 SF、ZF、PF 根据结果获取新状态。标志 AF 和 OV 获取不确定的状态。
第二个操作数(1 或 CL)定义向左移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (2,6,A)(0-7) | 0-2 | SHL byte ptr [bp+si+ffff],1
|
D0 | E(0-7) | SHL bl,1
| |
D1 | (2,6,A)(0-7) | 0-2 | SHL word ptr [bp+si+ffff],1
|
D1 | E(0-7) | SHL bx,1
| |
D2 | (2,6,A)(0-7) | 0-2 | SHL byte ptr [bp+si+ffff],CL
|
D2 | E(0-7) | SHL bl,CL
| |
D3 | (2,6,A)(0-7) | 0-2 | SHL word ptr [bp+si+ffff],CL
|
D3 | E(0-7) | SHL bx,CL
| |
C0 | E(0-7) | 1 | 参见 注释 2 |
C1 | E(0-7) | 1 | 参见 注释 2 |
- 注释
- SHL 命令与其他汇编器接受的 SAL 命令完全等效,但 DEBUG.EXE 不接受 SAL 名称。
- ^ a b 自 CPU 模型 80286 起,处理器执行 SHL 命令时明确指定了移位步数。DEBUG.EXE 不知道这种形式的 SHL 命令,但可以通过 DB 指令(7.01-01)作为数据输入。例如,左移 4 步的机器码可能如下所示:
C0 E0 04 = SHL AL,4 C1 E0 04 = SHL AX,4
7.03-83 SHR – 右移
[edit | edit source]SHR 命令将它的第一个操作数逐步右移,移向较低的位位置。在每一步中,最低有效位的状态被移入进位标志 CF,而最高有效位获得零(清除)状态。标志 SF、ZF、PF 根据结果获取新状态。标志 AF 和 OV 获取不确定的状态。
第二个操作数(1 或 CL)定义向左移位的步数。当移位步数从 CL 寄存器中读取时,只考虑 5 个最低有效位;因此,最大移位步数为 31。CL 寄存器中预设的移位步数保持不变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D0 | (2,6,A)(8-F) | 0-2 | SHR byte ptr [bp+si+ffff],1
|
D0 | E(8-F) | SHR bl,1
| |
D1 | (2,6,A)(8-F) | 0-2 | SHR word ptr [bp+si+ffff],1
|
D1 | E(8-F) | SHR bx,1
| |
D2 | (2,6,A)(8-F) | 0-2 | SHR byte ptr [bp+si+ffff],CL
|
D2 | E(8-F) | SHR bl,CL
| |
D3 | (2,6,A)(8-F) | 0-2 | SHR word ptr [bp+si+ffff],CL
|
D3 | E(8-F) | SHR bx,CL
| |
C0 | E(8-F) | 1 | 参见 注释 1 |
C1 | E(8-F) | 1 | 参见 注释 1 |
- 注释
- ^ a b 自 CPU 模型 80286 起,处理器执行 SHR 命令时明确指定了移位步数。DEBUG.EXE 不知道这种形式的 SHL 命令,但可以通过 DB 指令(7.01-01)作为数据输入。例如,右移 4 步的机器码可能如下所示:
C0 E8 04 = SHR AL,4 C1 E8 04 = SHR AX,4
7.03-84 STC – 设置进位标志
[edit | edit source]STC 命令将进位标志 CF 设置为“CY”(进位)状态,通常称为 CF=1。
代码 | 示例 |
---|---|
F9 | STC |
7.03-85 STD – 设置方向标志
[edit | edit source]STD 命令将方向标志 DF 设置为它的非默认状态“DN”。这意味着在执行字符串操作(CMPSB、LODSB、MOVSB、SCASB、STOSB 等)期间,索引寄存器(DI 和/或 SI)中的偏移量计数递减。
代码 | 示例 |
---|---|
FD | STD |
7.03-86 STI – 设置中断标志
[edit | edit source]STI 命令将中断标志 IF 设置为它的默认“EI”(= 启用中断)状态,从而启用通过中断控制器接收中断请求。
代码 | 示例 |
---|---|
FB | STI |
- 注释
- 如果当前程序的特权级别低于标志寄存器(A.11-4)中位 0Ch 和 0Dh 定义的 I/O 操作的特权级别,则 STI 命令不会执行。
7.03-87 STOSB – 用字节填充内存
[edit | edit source]尽管 STOSB 的名称代表“存储字节字符串”,但实际上 STOSB 命令将 AL 寄存器中的一个字节复制到一个内存单元中。该内存单元的地址必须事先加载到 ES:DI 寄存器对中。STOSB 命令不会更改标志的状态。
复制后,DI(目标索引)寄存器中的偏移量增加或减少 1:这取决于方向标志 DF 的状态(“向上”或“向下”)。DF 标志的状态可以通过 CLD(7.03-11)和 STD(7.03-85)命令更改。索引寄存器内容的自动更改为将 AL 寄存器中的一个字节复制到下一个内存单元中准备条件。STOSB 命令通常由重复前缀 F2h(7.02-03)或 F3h(7.02-04)引导,这使它能够循环执行,从而用相同字节的副本填充一系列内存单元。这些内存单元的默认段寄存器 ES 无法通过段覆盖前缀更改。
代码 | 示例 |
---|---|
AA | STOSB |
7.03-88 STOSW – 用字填充内存
[edit | edit source]STOSW 命令(STOSW = 存储字字符串)将 AX 寄存器中的一个字复制到内存中,内存地址由 ES:DI 寄存器对给出,然后将 DI 索引寄存器中的偏移量增加(或减少) 2,从而准备将 AX 内容复制到下一个内存单元中。操作数大小覆盖前缀 66h(7.02-06)强制 STOSW 命令将 EAX 寄存器中的一个四字节 DWORD 类型的操作数复制到内存中,并将 DI 索引寄存器中的偏移量增加(或减少) 4。STOSW 命令执行的其他所有特殊之处与 STOSB 命令(7.03-87)相同。
代码 | 示例 |
---|---|
AB | STOSW |
SUB 命令从第一个操作数(被减数)中减去第二个操作数(减数),忽略 CF 标志(借位)的状态。余数替换第一个操作数。标志 OF、SF、ZF、AF、PF、CF 根据结果获取新的状态。
SUB 命令留下的标志状态的解释取决于操作数是有符号数还是无符号数。在减去无符号数之后,应使用条件跳转命令 JA、JB、JBE、JNB。其他条件跳转命令 JG、JGE、JL、JLE 应在减去有符号数之后使用。所有条件跳转和循环命令的全名反映了 SUB 命令的第一个(左侧)操作数与第二个(右侧)操作数的状态关系。例如,JA = "如果大于则跳转" 表示 SUB 命令的左侧操作数(被减数)必须大于或等于右侧操作数(减数)。
SUB 是一个二元运算,但有两个例外。如果第一个操作数在 AX 寄存器中,那么 SUB 命令可以应用于非压缩十进制字:AX 寄存器中非压缩十进制字的二进制差可以通过 AAS 命令 (7.03-04) 转换为有效的非压缩十进制字。如果第一个操作数在 AL 寄存器中,那么 SUB 命令可以应用于压缩十进制字节:AL 寄存器中压缩十进制字节的二进制差可以通过 DAS 命令 (7.03-19) 转换为有效的压缩十进制字节。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
28 | (0-B)(0-F) | 0-2 | SUB [bp+si+ffff],bl
|
28 | (C-F)(0-F) | SUB bl,bl
| |
29 | (0-B)(0-F) | 0-2 | SUB [bp+si+ffff],bx
|
29 | (C-F)(0-F) | SUB bx,bx
| |
2A | (0-B)(0-F) | 0-2 | SUB bl,[bp+si+ffff]
|
2B | (0-B)(0-F) | 0-2 | SUB bx,[bp+si+ffff]
|
2C | 1 | SUB AL,ff
| |
2D | 2 | SUB AX,ffff
| |
80 | (2,6,A)(8-F) | 1-3 | SUB byte ptr [bp+si+ffff],ff
|
80 | E(9-F) | 1 | SUB bl,ff
|
81 | (2,6,A)(8-F) | 2-4 | SUB word ptr [bp+si+ffff],ffff
|
81 | E(9-F) | 2 | SUB bx,ffff
|
83 | (2,6,A)(8-F) | 1-3 | SUB word ptr [bp+si+ffff],±7f
|
83 | E(9-F) | 1 | SUB bx,±7f
|
- 注释
- 代码“2(A,B) (C-F)(0-F)” 和 “82 (2,6,A,E)(8-F)” 也被 DEBUG.EXE 反汇编为 SUB 命令。
TEST 命令根据对操作数进行按位逻辑 AND 运算的结果设置标志 SF(符号标志)、ZF(零标志)和 PF(奇偶标志),但该结果本身不会保存。TEST 命令的两个操作数都保持不变。进位标志 CF 和溢出标志 OF 被 TEST 命令分别清除为 NC(无进位)和 NV(无溢出)状态。AF 标志获得不确定的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
84 | (0-B)(0-F) | 0-2 | TEST [bp+si+ffff],bl
|
84 | (C-F)(0-F) | TEST bl,bl
| |
85 | (0-B)(0-F) | 0-2 | TEST [bp+si+ffff],bx
|
85 | (C-F)(0-F) | TEST bx,bx
| |
A8 | 1 | TEST AL,ff
| |
A9 | 2 | TEST AX,ffff
| |
F6 | (0,4,8)(0-7) | 1-3 | TEST byte ptr [bp+si+ffff],ff
|
F6 | C(1-7) | 1 | TEST bl,ff
|
F7 | (0,4,8)(0-7) | 2-4 | TEST word ptr [bp+si+ffff],ffff
|
F7 | C(1-7) | 2 | TEST bx,ffff
|
XCHG 命令交换指定寄存器之间或内存单元与寄存器之间的内容。标志的状态不会被 XCHG 命令改变。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
86 | (0-B)(0-F) | 0-2 | XCHG [bp+si+ffff],bl
|
86 | (C-F)(1-7,9-F) | XCHG bl,bl
| |
87 | (0-B)(0-F) | 0-2 | XCHG [bp+si+ffff],bx
|
87 | (C-F)(1-7,9-F) | XCHG bx,bx
| |
9(1-7) | XCHG bx,AX
|
XLAT 命令计算一个和 (AL + BX),然后将 DS:(AL + BX) 地址处的字节复制到 AL 寄存器中,替换其以前的内容。标志的状态和 BX 寄存器的内容不会被 XLAT 命令改变。
XLAT 命令用于通过代码表 (最多 256 字节长) 转换代码,该代码表必须事先从 DS:BX 地址开始加载。如果 XLAT 命令之前有适当的段覆盖前缀 (7.02-01),则可以使用其他段寄存器代替默认的段寄存器 DS。
代码 | 示例 |
---|---|
D7 | XLAT |
XOR 命令分析两个操作数中对应位的对。如果一对中两个位的状态相同(都设置或都清除),那么结果的对应位被清除为 FALSE(零)。如果分析的这对中位的状态不同,那么结果的对应位被设置为 TRUE 状态。结果替换第一个操作数。
标志 SF、ZF、PF 根据结果获取新的状态。标志 CF 和 OF 分别被清除为 NC(无进位)和 NV(无溢出)状态。标志 AF 获取不确定的状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
30 | (0-B)(0-F) | 0-2 | XOR [bp+si+ffff],bl
|
30 | (C-F)(0-F) | XOR bl,bl
| |
31 | (0-B)(0-F) | 0-2 | XOR [bp+si+ffff],bx
|
31 | (C-F)(0-F) | XOR bx,bx
| |
32 | (0-B)(0-F) | 0-2 | XOR bl,[bp+si+ffff]
|
33 | (0-B)(0-F) | 0-2 | XOR bx,[bp+si+ffff]
|
34 | 1 | XOR AL,ff
| |
35 | 2 | XOR AX,ffff
| |
80 | (3,7,B)(0-7) | 1-3 | XOR byte ptr [bp+si+ffff],ff
|
80 | F(1-7) | 1 | XOR bl,ff
|
81 | (3,7,B)(0-7) | 2-4 | XOR word ptr [bp+si+ffff],ffff
|
81 | F(1-7) | 2 | XOR bx,ffff
|
83 | (3,7,B)(0-7) | 1-3 | XOR word ptr [bp+si+ffff],±7f
|
83 | F(1-7) | 1 | XOR bx,±7f
|
- 注释
- XOR 命令使用相同的源作为两个操作数中的每一个的操作数规范通常用于将该源清除为零。
- 代码“3(2,3) (C-F)(0-F)” 和 “82 (3,7,B,F)(0-7)” 也被 DEBUG.EXE 反汇编为 XOR 命令。
那些汇编器命令,其名称以字母 "F"(浮点数)开头,被转移到数学协处理器以执行。所有现代计算机都能够执行这些命令,因为它们的协处理器集成在主 CPU 中。
具有过时处理器的计算机,包括一些 486 型号,可能没有数学协处理器。然后可以通过软件模拟执行协处理器的命令,但这必须满足两个条件
- 首先,必须确保在响应每个协处理器的命令时生成对 INT 07 处理程序 (8.01-08) 的调用。这是通过在控制寄存器 CR0 (A.11-4) 中设置位 02h(“协处理器模拟”)来实现的。
- 其次,必须加载适当的 INT 07 处理程序,该处理程序能够模拟执行协处理器的命令。
在一些旧计算机中,这两个条件会由于其 BIOS 系统而自动满足,而在其他一些计算机中,用户必须对此进行处理。在任何情况下,INT 11 处理程序 (8.01-36, A.11-1) 都报告了算术协处理器的存在与否。
如果您的程序有可能由具有独立协处理器芯片的旧 CPU 执行,那么每个协处理器的命令都应以 WAIT 前缀 (7.02-05) 开头,该前缀将命令从 CPU 同步到协处理器。现代 CPU 具有集成在硬件中的协处理器,具有硬件同步机制。因此,对于现代 CPU,WAIT 前缀是不需要的,允许其存在,但很可能被忽略。
F2XM1 命令计算一个级数的和,用于在幂指数从 -1 到 +1 的范围内近似 2 的分数次幂函数。幂指数隐含地准备在协处理器的顶部堆栈寄存器 ST(0) 中。计算的级数和替换 ST(0) 寄存器中的幂指数。最终结果可以通过公式 ST(0) = -1+2^ST(0) 表示。
代码 | 示例 |
---|---|
D9 F0 | F2XM1 |
FABS 指令将协处理器顶部堆栈寄存器 ST(0) 中的符号位清零,从而使 ST(0) 中的操作数变为正值。
代码 | 示例 |
---|---|
D9 E1 | FABS |
7.04-03 FADD – 实数加法
[edit | edit source]FADD 指令将从内存中读取的实数值或协处理器寄存器中的任何实数值(如果指定为第二个操作数)添加到协处理器顶部堆栈寄存器 ST(0) 中的另一个实数值或任何其他寄存器 ST(1-7) 中(如果后者指定为第一个操作数)。 如果指定为第一个操作数,则和将替换 ST(0) 或 ST(1-7) 中的先前值。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (0,4,8)(0-7) | 0-2 | FADD dword ptr [bp+si+ffff]
|
D8 | C(0-7) | FADD ST,ST(0-7)
| |
DC | (0,4,8)(0-7) | 0-2 | FADD qword ptr [bp+si+ffff]
|
DC | C(0-7) | FADD ST(1-7),ST
|
7.04-04 FADDP – 加法和堆栈上移
[edit | edit source]FADDP 指令(FADDP = “ADD and Pop”)将协处理器顶部堆栈寄存器 ST(0) 中的第二个操作数添加到任何其他指定的堆栈寄存器 ST(1-7) 中的第一个操作数。 和将替换指定的 ST(1-7) 堆栈寄存器中的先前值,然后协处理器堆栈指针加 1,因此对先前 ST(0) 的访问将丢失,所有其他堆栈寄存器 ST(1-7),包括存储和的寄存器,将被重新命名为 ST(0-6),就好像它们的编号减 1 一样。
代码 | 示例 |
---|---|
DE C(0-7) | FADDP ST(1-7),ST
|
7.04-05 FBLD – 二进制转换加载
[edit | edit source]FBLD 指令(FBLD = “Binary LoaD”)从内存中读取从指定地址开始的 10 字节压缩十进制整数,每个字节包含两位十进制数。 此十进制整数将转换为实数二进制值,并加载到协处理器寄存器 ST(7) 中,该寄存器此时必须为空。 然后 FBLD 指令将协处理器堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),这样最后加载的值将出现在顶部堆栈寄存器 ST(0) 中。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DF | (2,6,A)(0-7) | 0-2 | FBLD tbyte ptr [bp+si+ffff]
|
- 注释
- FBLD 指令不检查其操作数是否真的是压缩十进制数。 如果不是,则执行的二进制转换结果无效。
- 默认的 10 字节二进制实数格式包括尾数(位 0-63)、幂指数(位 64-78)和符号位 79。 通过更改 CWR 寄存器中精度控制字段的内容 (7.04-35 的注释 2),协处理器可以切换到 8 字节双精度格式(52 位 - 尾数,11 位 - 幂指数)或 4 字节单精度格式(23 位 - 尾数,8 位 - 幂指数)。
7.04-06 FBSTP – 十进制转换存储
[edit | edit source]FBSTP 指令(FBSTP = Binary STore and Pop)将协处理器顶部堆栈寄存器 ST(0) 中的实数二进制值转换为 10 字节压缩十进制整数,每个字节包含两位十进制数。 转换包括对小数部分进行舍入。 转换后的整数将写入内存,从指定的偏移量开始。 然后 FBSTP 指令将协处理器堆栈指针加 1,因此对先前 ST(0) 的访问将丢失,寄存器 ST(1-7) 被重新命名为 ST(0-6)。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DF | (3,7,B)(0-7) | 0-2 | FBSTP tbyte ptr [bp+si+ffff]
|
7.04-07 FCHS – 改变符号
[edit | edit source]FCHS 指令反转协处理器顶部堆栈寄存器 ST(0) 中的实数二进制值的符号。
代码 | 示例 |
---|---|
D9 E0 | FCHS |
7.04-08 FCLEX – 清除异常标志
[edit | edit source]FCLEX 指令清除协处理器状态字寄存器 SWR 中用于注册协处理器状态和异常的位。 特别是,以下位被清除
位 0 | – 无效操作标志 |
位 1 | – 非规格化操作数标志 |
位 2 | – 除以零标志 |
位 3 | – 协处理器溢出标志 |
位 4 | – 反溢出(丢失结果)标志 |
位 5 | – 丢失精度标志 |
blt 7 | – 中断请求标志 |
位 15 | – “协处理器繁忙”标志 |
代码 | 示例 |
---|---|
DB E2 | FCLEX |
7.04-09 FCOM – 实数比较
[edit | edit source]FCOM 指令将协处理器顶部堆栈寄存器 ST(0) 中的实数值与指定寄存器或内存单元的内容进行比较。 协处理器寄存器 SWR 中的标志 C0、C2 和 C3 根据结果获取新的状态。 首先应检查 C2 标志的状态:C2 = 1 表示不可比较的操作数,因此没有进一步检查的意义。 如果操作数相等,则 C3 = 1。 如果 ST(0) 中的值小于另一个操作数,则 C0 = 1。 检查协处理器 SWR 寄存器中的标志 C0、C2 和 C3 的方法在文章 7.04-64 中描述。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (1,5,9)(0-7) | 0-2 | FCOM dword ptr [bp+si+ffff]
|
D8 | D(0-7) | FCOM ST(0-7)
| |
DC | (1,5,9)(0-7) | 0-2 | FCOM qword ptr [bp+si+ffff]
|
- 注释
- 代码“DC D(0-7)”也被 DEBUG.EXE 解码为 FCOM ST(0-7)。
7.04-10 FCOMP – 比较和堆栈上移
[edit | edit source]FCOMP 指令执行比较的方式与 FCOM 操作相同(7.04-09),但随后将协处理器堆栈指针加 1,因此对先前 ST(0) 的访问将丢失,寄存器 ST(1-7) 被重新命名为 ST(0-6)。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (1,5,9)(8-F) | 0-2 | FCOMP dword ptr [bp+si+ffff]
|
D8 | D(8-F) | FCOMP ST(0-7)
| |
DC | (1,5,9)(8-F) | 0-2 | FCOMP qword ptr [bp+si+ffff]
|
- 注释
- 代码“DC D(8-F)”和“DE D(0-7)”也被 DEBUG.EXE 解码为 FCOMP 指令。
7.04-11 FCOMPP – 比较和两次堆栈上移
[edit | edit source]FCOMPP 指令比较协处理器堆栈寄存器 ST(0) 和 ST(1) 中的操作数,然后将协处理器堆栈指针加 2,因此对 ST(0) 和 ST(1) 中先前操作数的访问将丢失。 寄存器 ST(2-7) 被重新命名为 ST(0-5)。 比较结果会影响协处理器 SWR 寄存器中标志 C0、C2 和 C3 的状态,就像在 FCOM 指令(7.04-09)之后一样。
代码 | 示例 |
---|---|
DE D9 | FCOMPP |
- 注释
- DEBUG.EXE 将代码“DE D9”解码为“FCOMPP ST(1)”,但在汇编时不接受“ST(1)”。
7.04-12 FDECSTP – 递减堆栈顶部指针
[edit | edit source]FDECSTP 指令将协处理器堆栈指针减 1,因此寄存器 ST(0-6) 被重命名为 ST(1-7)。 最后一个堆栈寄存器 ST(7) 被重命名为 ST(0)。 所有堆栈寄存器的内容仍然可以访问,并且不会被更改。
代码 | 示例 |
---|---|
D9 F6 | FDECSTP |
- 注释
- 协处理器堆栈指针是一个三级可逆计数器,涉及状态字寄存器 SWR 的位 11、12 和 13。
- 大多数将数据推入协处理器堆栈的命令都是通过将数据复制到 ST(7) 寄存器并将协处理器堆栈指针减 1 来执行的,这样 ST(7) 寄存器将被重新命名为 ST(0)。 如果 ST(7) 寄存器最初不为空,则所有此类命令都无法执行。
7.04-13 FDISI – 禁用中断
[edit | edit source]FDISI 指令禁用过时的 8087 算术协处理器的中断。 由于模型 80287 协处理器不需要此命令并忽略它。
代码 | 示例 |
---|---|
DB E1 | FDISI |
7.04-14 FDIV – 实数除法
[edit | edit source]FDIV 命令将协处理器栈顶寄存器 ST(0) 中的实数值,或任何非栈顶寄存器 ST(1-7) 中的实数值(如果指定为第一个操作数),除以指定内存单元或协处理器其他寄存器中的实数除数(如果指定为第二个操作数)。商将替换 ST(0) 中的被除数,或其他栈寄存器 ST(1-7) 中的被除数,如果它被指定为第一个操作数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (3,7,B)(0-7) | 0-2 | FDIV dword ptr [bp+si+ffff]
|
D8 | F(0-7) | FDIV ST,ST(0-7)
| |
DC | (3,7,B)(0-7) | 0-2 | FDIV qword ptr [bp+si+ffff]
|
DC | F(8-F) | FDIV ST(1-7),ST
|
7.04-15 FDIVP – 除并向上移栈
[edit | edit source]FDIVP 命令将协处理器非栈顶寄存器 ST(1-7) 中的实数值除以栈顶寄存器 ST(0) 中的除数。商将替换非栈顶寄存器 ST(1-7) 中的被除数。然后 FDIVP 命令将协处理器栈指针加 1,因此寄存器 ST(1-7) 被重新命名为 ST(0-6),包括写入商的那一个。ST(0) 寄存器被重新命名为 ST(7) 并宣布为空闲。无法访问其以前的内容(除数)。
代码 | 示例 |
---|---|
DE F(8-F) | FDIVP ST(1-7),ST |
7.04-16 FDIVR – 反序除法
[edit | edit source]FDIVR 命令将从内存单元中读取的实数值,或从协处理器栈寄存器中读取的实数值(如果指定为第二个操作数),除以协处理器栈顶寄存器 ST(0) 中的实数除数,或任何非栈顶寄存器 ST(1-7) 中的实数除数(如果指定为第一个操作数)。商将替换 ST(0) 寄存器中的除数,或非栈顶寄存器 ST(1-7) 中的除数,如果它被指定为第一个操作数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (3,7,B)(8-F) | 0-2 | FDIVR dword ptr [bp+si+ffff]
|
D8 | F(8-F) | FDIVR ST,ST(0-7)
| |
DC | (3,7,B)(8-F) | 0-2 FDIVR qword ptr [bp+si+ffff] | |
DC | F(0-7) | FDIVR ST(1-7),ST
|
7.04-17 FDIVRP – 反序除并向上移栈
[edit | edit source]FDIVRP 命令将协处理器栈顶寄存器 ST(0) 中的实数值除以任何其他栈寄存器 ST(1-7) 中的除数,用商替换非栈顶寄存器 ST(1-7) 中的除数,然后将协处理器栈指针加 1,因此寄存器 ST(1-7) 被重新命名为 ST(0-6),包括写入商的那一个。ST(0) 寄存器被重新命名为 ST(7) 并宣布为空闲。无法访问其以前的内容(被除数)。
代码 | 示例 |
---|---|
DE F(0-7) | FDIVRP ST(1-7),ST |
7.04-18 FENI – 启用中断
[edit | edit source]FENI 命令为过时的 8087 算术协处理器启用中断。由于 80287 协处理器不需要此命令,因此会忽略它。
代码 | 示例 |
---|---|
DB E0 | FENI |
7.04-19 FFREE – 宣布寄存器为空闲
[edit | edit source]FFREE 命令通过将“11”二进制值写入协处理器标记寄存器 TWR 的对应位,将指定的协处理器栈寄存器标记为空闲。
代码 | 示例 |
---|---|
DD C(0-7) | FFREE ST(0-7) |
- 注释
- 代码“DF C(0-7)”也被 DEBUG.EXE 解码为 FFREE 命令。
7.04-20 FIADD – 整数加法
[edit | edit source]FIADD 命令将指定内存单元中的整数加到协处理器栈顶寄存器 ST(0) 中的实数值。和是一个实数值,它替换 ST(0) 寄存器中的以前内容。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (0,4,8)(0-7) | 0-2 | FIADD dword ptr [bp+si+ffff]
|
DE | (0,4,8)(0-7) | 0-2 | FIADD word ptr [bp+si+ffff]
|
7.04-21 FICOM – 整数比较
[edit | edit source]FICOM 命令从指定内存单元中读取整数,将其转换为实数值,并将结果与协处理器栈顶寄存器 ST(0) 中的实数值进行比较。比较本身与 FCOM 命令 (7.04-09) 所执行的比较相同。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (1,5,9)(0-7) | 0-2 | FICOM dword ptr [bp+si+ffff]
|
DE | (1,5,9)(0-7) | 0-2 | FICOM word ptr [bp+si+ffff]
|
7.04-22 FICOMP – 与整数比较并向上移栈
[edit | edit source]FICOMP 命令从指定内存单元中读取整数,将其转换为实数值,并将结果与协处理器栈顶寄存器 ST(0) 中的实数值进行比较。比较本身与 FCOM 命令 (7.04-09) 所执行的比较相同,但随后 FICOMP 命令将协处理器栈指针加 1,因此寄存器 ST(1-7) 被重新命名为 ST(0-6)。ST(0) 寄存器被重新命名为 ST(7) 并宣布为空闲。无法访问其以前的内容。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (1,5,9)(8-F) | 0-2 | FICOMP dword ptr [bp+si+ffff]
|
DE | (1,5,9)(8-F) | 0-2 | FICOMP word ptr [bp+si+ffff]
|
7.04-23 FIDIV – 整数除法
[edit | edit source]FIDIV 命令将协处理器栈顶寄存器 ST(0) 中的实数值除以从指定内存单元中读取的整数除数。商将替换栈顶寄存器 ST(0) 中的被除数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (3,7,B)(0-7) | 0-2 | FIDIV dword ptr [bp+si+ffff]
|
DE | (3,7,B)(0-7) | 0-2 | FIDIV word ptr [bp+si+ffff]
|
7.04-24 FIDIVR – 反序整数除法
[edit | edit source]FIDIVR 命令执行从指定内存单元中读取的整数被除数除以协处理器栈顶寄存器 ST(0) 中的除数。商将替换栈顶寄存器 ST(0) 中的除数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (3,7,B)(8-F) | 0-2 | FIDIVR dword ptr [bp+si+ffff]
|
DE | (3,7,B)(8-F) | 0-2 | FIDIVR word ptr [bp+si+ffff]
|
7.04-25 FILD – 整数加载
[edit | edit source]FILD 命令将从指定内存单元中读取的整数转换为实数值,并将此值加载到协处理器栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FILD 命令将协处理器栈指针减 1;因此寄存器 ST(0-6) 被重新命名为 ST(1-7),寄存器 ST(7) 被重新命名为 ST(0),因此最后加载的值将在栈顶寄存器 ST(0) 中找到。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DB | (0,4,8)(0-7) | 0-2 | FILD dword ptr [bp+si+ffff]
|
DF | (0,4,8)(0-7) | 0-2 | FILD word ptr [bp+si+ffff]
|
DF | (2,6,A)(8-F) | 0-2 | FILD qword ptr [bp+si+ffff]
|
7.04-26 FIMUL – 整数乘法
[edit | edit source]FIMUL 命令将协处理器顶端堆栈寄存器 ST(0) 中的实数乘以从指定内存单元读取的整数值。乘积将替换协处理器顶端堆栈寄存器 ST(0) 中的原值。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (0,4,8)(8-F) | 0-2 | FIMUL dword ptr [bp+si+ffff]
|
DE | (0,4,8)(8-F) | 0-2 | FIMUL word ptr [bp+si+ffff]
|
7.04-27 FINCSTP – 增量堆栈顶指针
[edit | edit source]FINCSTP 命令将协处理器的堆栈指针增量 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6)。顶端堆栈寄存器 ST(0) 被重命名为 ST(7)。所有堆栈寄存器的内容仍然可以访问且不会被更改。
代码 | 示例 |
---|---|
D9 F7 | FINCSTP |
- 注释
- 协处理器的堆栈指针是一个三级可逆计数器,涉及状态字寄存器 SWR 的位 11、12 和 13。
- 当其他命令执行协处理器的堆栈指针增量时,顶端堆栈寄存器 ST(0) 在被重命名为 ST(7) 后将获得空寄存器的状态(标记 11b)。因此,对 ST(0) 的原内容的访问将丢失。此操作通常被称为将 ST(0) 的内容弹出堆栈。
7.04-28 FINIT – 设置协处理器的初始状态
[edit | edit source]FINIT 命令将初始状态写入算术协处理器的 CWR、SWR、TWR、IPR 和 DPR 寄存器。控制字寄存器 CWR 获取状态 037Fh:它定义操作数的 80 位格式、所有异常的屏蔽和四舍五入到最接近的整数。标签寄存器 TWR 被设置为 FFFFh 状态,这意味着所有协处理器的堆栈寄存器都是空闲的。其他协处理器寄存器(SWR、IPR 和 DPR)被清除为 0000h。
代码 | 示例 |
---|---|
DB E3 | FINIT |
7.04-29 FIST – 存储一个整数
[edit | edit source]FIST 命令(FIST = Integer STore)从协处理器的顶端堆栈寄存器 ST(0) 读取一个实数,将其转换为整数,根据指定的格式四舍五入,并将结果写入指定的内存地址。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DB | (1,5,9)(0-7) | 0-2 | FIST dword ptr [bp+si+ffff]
|
DF | (1,5,9)(0-7) | 0-2 | FIST word ptr [bp+si+ffff]
|
7.04-30 FISTP – 存储一个整数并向上移动堆栈
[edit | edit source]FISTP 命令将 ST(0) 寄存器中转换后的值存储到内存中,就像 FIST 命令一样(7.04-29)。除此之外,FISTP 命令将协处理器的堆栈指针增量 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6)。对顶端堆栈寄存器 ST(0) 中的原值的访问将丢失。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DB | (1,5,9)(8-F) | 0-2 | FISTP dword ptr [bp+si+ffff]
|
DF | (1,5,9)(8-F) | FISTP word ptr [bp+si+ffff]
| |
DF | (3,7,B)(8-F) | 0-2 | FISTP qword ptr [bp+si+ffff]
|
7.04-31 FISUB – 整数减法
[edit | edit source]FISUB 命令从协处理器的顶端堆栈寄存器 ST(0) 中的实数 - 被减数 - 减去存储在指定内存单元中的整数减数。余数将替换顶端堆栈寄存器 ST(0) 中的被减数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (2,6,A)(0-7) | 0-2 | FISUB dword ptr [bp+si+ffff]
|
DE | (2,6,A)(0-7) | 0-2 | FISUB word ptr [bp+si+ffff]
|
7.04-32 FISUBR – 反向顺序减法
[edit | edit source]FISUBR 命令执行协处理器顶端堆栈寄存器 ST(0) 中的实数减数从存储在指定内存单元中的整数被减数的减法。余数将替换顶端堆栈寄存器 ST(0) 中的原减数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DA | (2,6,A)(8-F) | 0-2 | FISUBR dword ptr [bp+si+ffff]
|
DE | (2,6,A)(8-F) | 0-2 | FISUBR word ptr [bp+si+ffff]
|
7.04-33 FLD – 加载实数
[edit | edit source]FLD 命令从指定的寄存器或指定的内存单元读取一个实数,并将此值加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后,FLD 命令将协处理器的堆栈指针减量 1;因此,寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),这样最终加载的实数就位于顶端堆栈寄存器 ST(0) 中。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (0,4,8)(0-7) | 0-2 | FLD dword ptr [bp+si+ffff]
|
D9 | C(0-7) | FLD ST(0-7)
| |
DB | (2,6,A)(8-F) | 0-2 | FLD tbyte ptr [bp+si+ffff]
|
DD | (0,4,8)(0-7) | 0-2 | FLD qword ptr [bp+si+ffff]
|
7.04-34 FLD1 – 加载一个单位常数
[edit | edit source]FLD1 命令将一个单位常数加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后,FLD1 命令将协处理器的堆栈指针减量 1;因此,寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),这样最终加载的常数就被存储到顶端堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 E8 | FLD1 |
7.04-35 FLDCW – 加载 CWR 寄存器
[edit | edit source]FLDCW 命令(FLDCW = LoaD Control Word)将 FSTCW 命令(7.04-55)保存的数据字从指定的内存单元复制到协处理器的控制字寄存器 CWR 中。如果此数据字中选定的位已被有意更改,则在使用 FLDCW 命令加载后,更改将生效。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (2,6,A)(8-F) | 0-2 | FLDCW word ptr [bp+si+ffff]
|
- 注释
- CWR 寄存器中的位 5-0 代表 SWR 寄存器(7.04-08)中对应位 5-0 中的异常标志的掩码。默认情况下,CWR 寄存器中的掩码被设置,但如果清除掩码,则异常的发生将调用中断处理程序 INT 75(8.03-75)的请求 IRQ 13,该中断处理程序必须能够处理问题。
- ^ CWR 寄存器中的位 9 和 8 代表 PC (= 精度控制) 字段。PC 字段的默认状态 11b 定义操作数的 10 字节格式。PC 字段的状态 10b 定义四舍五入到 8 字节格式,状态 00b - 四舍五入到 4 字节格式(7.04-05)。但是,降低精度并不会使计算速度更快。
7.04-36 FLDENV – 加载到服务寄存器
[edit | edit source]FLDENV(= LoaD ENVironment)根据记录恢复协处理器服务寄存器(CWR、SWR、TWR、IPR、DPR)的状态,该记录从指定地址开始从内存读取。此记录必须由 FSTENV 命令(7.04-56)预先形成并存储。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (2,6,A)(0-7) | 0-2 | FLDENV word ptr [bp+si+ffff]
|
7.04-37 FLDL2E – 加载“log e”常数
[edit | edit source]FLDL2E 命令将 log e = 1.44269... 常数(即 e = 2.71828... 的以 2 为底的对数)加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FLDL2E 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后加载的常数被存储在顶部堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 EA | FLDL2E |
7.04-38 FLDL2T – 加载“log10”常数
[edit | edit source]FLDL2T 命令将 log10 = 3.32192... 常数(即 10 的以 2 为底的对数)加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FLDL2T 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后加载的常数被存储在顶部堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 E9 | FLDL2T |
7.04-39 FLDLG2 – 加载“lg2”常数
[edit | edit source]FLDLG2 命令将 lg2 = 0.301029... 常数(即 2 的以 10 为底的对数)加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FLDLG2 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后加载的常数被存储在顶部堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 EC | FLDLG2 |
7.04-40 FLDLN2 – 加载“ln2”常数
[edit | edit source]FLDLN2 命令将 ln2 = 0.693147... 常数(即 2 的以 e = 2.71828... 为底的对数)加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FLDLN2 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后加载的常数被存储在顶部堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 ED | FLDLN2 |
7.04-41 FLDPI – 加载“PI”常数
[edit | edit source]FLDPI 命令将 PI = 3.14159... 常数加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FLDPI 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后加载的常数被存储在顶部堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 EB | FLDPI |
7.04-42 FLDZ – 加载零常数
[edit | edit source]FLDZ 命令将 0(即零)常数加载到协处理器的堆栈寄存器 ST(7) 中,该寄存器此时必须为空。然后 FLDZ 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后加载的常数被存储在顶部堆栈寄存器 ST(0) 中。
代码 | 示例 |
---|---|
D9 EE | FLDZ |
7.04-43 FMUL – 实数相乘
[edit | edit source]FMUL 命令将协处理器顶部堆栈寄存器 ST(0) 中的实数或任何非顶部寄存器 ST(1-7) 中的实数(如果指定为第一个操作数),乘以另一个实数 - 乘数,该乘数从内存读取或从其他堆栈寄存器读取,如果指定为第二个操作数。乘积替换顶部堆栈寄存器 ST(0) 或其他堆栈寄存器 ST(1-7) 中的先前内容,如果指定为第一个操作数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (0,4,8)(8-F) | 0-2 | FMUL dword ptr [bp+si+ffff]
|
D8 | C(8-F) | FMUL ST,ST(0-7)
| |
DC | (0,4,8)(8-F) | 0-2 FMUL qword ptr [bp+si+ffff] | |
DC | C(8-F) | FMUL ST(1-7),ST
|
7.04-44 FMULP – 乘法并向上移位堆栈
[edit | edit source]FMULP 命令将指定协处理器非顶部堆栈寄存器 ST(1-7) 中的实数乘以顶部堆栈寄存器 ST(0) 中的实数乘数。乘积覆盖指定非顶部堆栈寄存器 ST(1-7) 中的先前值。然后 FMULP 命令将协处理器的堆栈指针加 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6),包括写入乘积的那个寄存器。ST(0) 寄存器被重命名为 ST(7) 并被宣布为可用。对以前内容(乘数)的访问已丢失。
代码 | 示例 |
---|---|
DE С(8-F) | FMULP ST(1-7),ST
|
7.04-45 FNOP – 空操作
[edit | edit source]虽然 FNOP 命令已知什么也不做,但实际上它将 IP(指令指针)加 2,因为 FNOP 命令的机器码本身占用 2 个字节,因此 IP 指向下一个命令。
代码 | 示例 |
---|---|
D9 D0 | FNOP |
7.04-46 FPATAN – 部分反正切
[edit | edit source]FPATAN 命令将协处理器顶部堆栈寄存器 ST(0) 中的正实数被除数除以堆栈寄存器 ST(1) 中的正实数除数。除数必须等于或大于被除数。商用于计算以弧度为单位的 Arctg(ST(0)/ST(1)) 函数的近似值。结果替换堆栈寄存器 ST(1) 中的除数,然后 FPATAN 命令将协处理器的堆栈指针加 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6)。以前 ST(1) 寄存器(写入结果的寄存器)变为顶部堆栈寄存器 ST(0)。对 ST(0) 寄存器(除数)先前内容的访问已丢失。
代码 | 示例 |
---|---|
D9 F3 | FPATAN |
7.04-47 FPREM – 部分余数
[edit | edit source]FPREM 命令的主要功能是将周期性三角函数的参数简化为其主区间的限制。FPREM 命令将协处理器顶层堆栈寄存器 ST(0) 中的实数被除数除以堆栈寄存器 ST(1) 中的实数除数。余数替换顶层堆栈寄存器 ST(0) 中的被除数。如果此余数大于 ST(1) 寄存器中的除数,则它被视为部分余数,并通过设置 SWR 寄存器中的标志 C2 = 1 来标记。在这种情况下,FPREM 命令应重复执行,直到 SWR 寄存器中标志 C2 的清除状态指示获得最终余数。
获得最终余数后,SWR 寄存器中标志 C3、C1 和 C0 的状态指向与三角函数参数的最终值相对应的圆的扇区。在协处理器 SWR 寄存器中检查标志 C3、C2、C1 和 C0 的方式在第 7.04-64 条中进行了描述。
代码 | 示例 |
---|---|
D9 F8 | FPREM |
FPTAN 命令在协处理器顶层堆栈寄存器 ST(0) 中接受以弧度表示的角参数,用于计算 Tg(ST(0)) 值的近似值。FPTAN 命令将协处理器的堆栈指针减 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),角参数出现在寄存器 ST(1) 中。计算出的正切函数值替换寄存器 ST(1) 中的参数,并将一个单位常数写入寄存器 ST(0)。如果 SWR 寄存器中的成功位 C2 被清除为 0,否则它被设置为 1。
代码 | 示例 |
---|---|
D9 F2 | FPTAN |
- 注释
- 如果协处理器的堆栈寄存器 ST(7) 不为空,则堆栈指针不能递减,然后 FPTAN 命令将不会执行。
- 过时的算术协处理器(最高到 80287 型号)要求 FPTAN 命令的参数在 0 到 PI/4 的范围内。
FRNDINT 命令通过舍入操作将协处理器顶层堆栈寄存器 ST(0) 中的实数值转换为整数。舍入方式取决于 CWR 寄存器中 RC(舍入控制)字段的状态:如果 RC=00 – 四舍五入到最接近的整数,如果 RC=01 – 四舍五入到最接近的较小整数,如果 RC=10 – 四舍五入到最接近的较大整数,如果 RC=11 – 四舍五入通过省略原始实数值的小数部分。
代码 | 示例 |
---|---|
D9 FC | FRNDINT |
- 注释
- 协处理器 CWR 寄存器中的位 11 和 10 构成 RC(舍入控制)字段。CWR 寄存器中所有位的状态可以通过 FSTCW 命令(7.04-55)写入内存,然后可以有意地更改所需位的状态。更改后的状态在通过 FLDCW 命令(7.04-35)加载回 CWR 寄存器后生效。
FRSTOR 命令(FRSTOR = ReSTORe)从长度为 96 或 108 字节的记录中加载所有协处理器寄存器的状态,包括控制寄存器和堆栈,从指定的内存地址开始。此记录必须事先由 FSAVE 命令(7.04-51)存储在内存中。此记录的实际长度取决于 CPU 的模式:实模式或保护模式。因此,重要的是在完全与存储此记录的 CPU 模式相同的模式下执行协处理器状态的恢复。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DD | (2,6,A)(0-7) | 0-2 | FRSTOR [bp+si+ffff]
|
FSAVE 命令在计算机内存中存储所有堆栈寄存器和算术协处理器中的控制寄存器的状态,从指定的地址开始,然后重置协处理器的寄存器 CWR、SWR、TWR、IPR、DPR,就像 FINIT 命令所做的那样(7.04-28)。由 FSAVE 命令形成的整个记录长度为 96 或 108 字节 - 这取决于 CPU 的模式:实模式或保护模式。稍后,此记录可以通过 FRSTOR 命令(7.04-50)读取,这使能够恢复协处理器的先前状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
DD | (3,7,B)(0-7) | 0-2 | FSAVE [bp+si+ffff]
|
FSCALE 命令将协处理器顶层堆栈寄存器 ST(0) 中的实数值乘以 2 的幂,其中幂指数为整数,可以是正数或负数。幂指数必须在 ST(1) 寄存器中作为实数值准备。如果它不是整数,它将四舍五入到最接近的较小整数。最终的乘积替换顶层堆栈寄存器 ST(0) 中的第一个乘数。
代码 | 示例 |
---|---|
D9 FD | FSCALE |
FSQRT 计算协处理器顶层堆栈寄存器 ST(0) 中的正实数值的平方根。平方根值替换顶层堆栈寄存器 ST(0) 中的操作数。
代码 | 示例 |
---|---|
D9 FA | FSQRT |
FST 命令将协处理器顶层堆栈寄存器 ST(0) 中的实数值复制到任何其他堆栈寄存器 ST(1-7) 中,或者根据指定的地址和格式复制到内存中。如果指定的格式比协处理器堆栈寄存器中的原始 10 字节格式短,则存储的值将根据指定的格式进行舍入。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (1,5,9)(0-7) | 0-2 | FST dword ptr [bp+si+ffff]
|
DD | (1,5,9)(0-7) | 0-2 | FST qword ptr [bp+si+ffff]
|
DD | D(0-7) | FST ST(1-7)
|
- 注释
- 代码“DF D(0-7)”也被 DEBUG.EXE 解码为 FST 命令。
- FST 命令使能够将新内容复制到非空堆栈寄存器中。该寄存器的旧内容将被覆盖。
FSTCW 命令将协处理器控制寄存器 CWR 的当前状态复制到一个数据字中,该数据字存储在内存中,根据指定的地址。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (3,7,B)(8-F) | 0-2 | FSTCW [bp+si+ffff]
|
- 注释
- 稍后,可以通过 FLDCW 命令(7.04-35)恢复存储的 CWR 寄存器状态。
FSTENV(= 存储环境)命令将所有协处理器服务寄存器的状态写入内存,从指定的地址开始:CWR(控制字寄存器)、SWR(状态字寄存器)、TWR(标签字寄存器)、IPR(指令指针寄存器)、DPR(数据指针寄存器)。与 FSAVE 命令(7.04-51)相反,FSTENV 命令不会重置协处理器的服务寄存器,也不会保存堆栈寄存器的内容。由 FSTENV 命令形成的记录中的数据可以稍后用于通过 FLDENV 命令(7.04-36)恢复服务寄存器的先前状态。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (3,7,B)(0-7) | 0-2 | FSTENV [bp+si+ffff]
|
FSTP 命令将协处理器顶层堆栈寄存器 ST(0) 中的实数值复制到任何其他堆栈寄存器 ST(1-7) 中,该寄存器不一定是空闲的,或者根据指定的地址和格式复制到内存中。如果指定的格式比协处理器堆栈寄存器中的原始 10 字节格式短,则存储的值将根据指定的格式进行舍入。然后 FSTP 命令将协处理器的堆栈指针加 1,使得寄存器 ST(1-7) 被重命名为 ST(0-6)。ST(0) 寄存器被重命名为 ST(7) 并被宣布为空闲。无法访问 ST(0) 寄存器的旧内容。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D9 | (1,5,9)(8-F) | 0-2 | FSTP dword ptr [bp+si+ffff]
|
DB | (3,7,B)(8-F) | 0-2 | FSTP tbyte ptr [bp+si+ffff]
|
DD | (1,5,9)(8-F) | 0-2 | FSTP qword ptr [bp+si+ffff]
|
DD | D(8-F) | FSTP ST(1-7)
|
- 注释
- 代码“D9 D(8-F)”和“DF D(8-F)”也被 DEBUG.EXE 解码为 FSTP 命令。
FSTSW 指令将协处理器状态字寄存器 SWR 的状态复制到指定的地址,主要用于分析比较后的结果。SWR 寄存器中一些位的角色在文章 7.04-08 和 7.04-64 中有描述。
第一 byte |
第二个字节 | 数据 字节 |
示例 | |
---|---|---|---|---|
DD | (3,7,B)(8-F) | 0-2 | FSTSW [bp+si+ffff]
| |
DF | E0 | ESC 3C,AL |
[注意 1] |
- 注释
- ^ 80486 及更高版本的 CPU 执行 FSTSW AX 操作(代码 "DF E0"),将协处理器状态字复制到 CPU 的 AX 寄存器中。此操作对于 DEBUG.EXE 来说是“未知”的,但 DEBUG.EXE 在其旧名称
ESC 3C,AL
下接受它。
FSUB 指令(FSUB = SUBtract)从协处理器顶部的堆栈寄存器 ST(0) 中的实数被减数(如果它被指定为第二个操作数)中减去一个实数减数,该实数减数存储在内存单元或协处理器堆栈寄存器 ST(0-7) 中。如果减数被指定为第一个操作数,则从协处理器顶部的堆栈寄存器 ST(0) 或其他堆栈寄存器 ST(1-7) 中的实数被减数中减去。余数替换协处理器顶部的堆栈寄存器 ST(0) 中的被减数或其他堆栈寄存器 ST(1-7) 中的被减数,如果它被指定为第一个操作数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (2,6,A)(0-7) | 0-2 | FSUB dword ptr [bp+si+ffff]
|
D8 | E(8-F) | FSUB ST,ST(0-7)
| |
DC | (2,6,A)(0-7) | 0-2 | FSUB qword ptr [bp+si+ffff]
|
DC | E(8-F) | FSUB ST(1-7),ST
|
FSUBP 指令从协处理器顶部的堆栈寄存器 ST(0) 中的另一个实数值(被减数)中减去一个实数值(减数),该实数值存储在指定的协处理器非顶部的堆栈寄存器 ST(1-7) 中。余数将覆盖指定的非顶部的堆栈寄存器 ST(1-7) 中的被减数。然后 FSUBP 指令将协处理器的堆栈指针增加 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6),包括写入余数的那个寄存器。ST(0) 寄存器被重命名为 ST(7) 并被宣布为可用。对它的以前内容(减数)的访问将丢失。
代码 | 示例 |
---|---|
DE E(8-F) | FSUBP ST(1-7),ST
|
FSUBR 指令从指定的内存单元或另一个协处理器堆栈寄存器中存储的实数被减数(指定为第二个操作数)中减去一个实数值(减数),该实数值存储在协处理器顶部的堆栈寄存器 ST(0) 中,或者存储在任何非顶部的堆栈寄存器 ST(1-7) 中(如果它被指定为第一个操作数)。余数替换协处理器顶部的堆栈寄存器 ST(0) 中的减数,或者替换其他堆栈寄存器 ST(1-7) 中的减数,如果它被指定为第一个操作数。
第一 byte |
第二个字节 | 数据 字节 |
示例 |
---|---|---|---|
D8 | (2,6,A)(8-F) | 0-2 | FSUBR dword ptr [bp+si+ffff]
|
D8 | E(0-7) | FSUBR ST,ST(0-7)
| |
DC | (2,6,A)(8-F) | 0-2 | FSUBR qword ptr [bp+si+ffff]
|
DC | E(0-7) | FSUBR ST(1-7),ST
|
FSUBRP 指令从任何协处理器非顶部的堆栈寄存器 ST(1-7) 中的实数减数中减去一个实数值(减数),该实数值从顶部的堆栈寄存器 ST(0) 中的实数被减数中减去。余数将替换协处理器非顶部的堆栈寄存器 ST(1-7) 中的减数。然后 FSUBRP 指令将协处理器的堆栈指针增加 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6),包括写入余数的那个寄存器。ST(0) 寄存器被重命名为 ST(7) 并被宣布为可用。对它的以前内容(被减数)的访问将丢失。
代码 | 示例 |
---|---|
DE E(0-7) | FSUBRP ST(1-7),ST
|
FTST (= TeST) 指令将协处理器顶部的堆栈寄存器 ST(0) 中的实数值与零常数进行比较。协处理器状态字寄存器 SWR 中的标志 C3、C2 和 C0 的状态根据结果进行更改。首先应检查标志 C2 的状态:如果 C2 = 1,则操作数不可比较,因此没有进一步检查的意义。标志 C3 = 1 表示相等,即顶部的堆栈寄存器 ST(0) 中的值为零。标志 C0 = 1 表示顶部的堆栈寄存器 ST(0) 中的值为负数。如果顶部的堆栈寄存器 ST(0) 包含一个正实数,则所有三个标志 C3、C2、C0 都将被清除为零。在协处理器 SWR 寄存器中检查标志 C0、C2、C3 的方法在文章 7.04-64 中有描述。
代码 | 示例 |
---|---|
D9 E4 | FTST |
FXAM (= eXAMine) 指令确定协处理器顶部的堆栈寄存器 ST(0) 中操作数的类型。协处理器状态字寄存器 SWR 中的标志 C3、C2、C1 和 C0 的状态指示结果。标志 C1 反映操作数的符号。标志 C3、C2 和 C0 的状态解释在下表中给出
C3 | C2 | C0 | 操作数类型 |
---|---|---|---|
0 | 0 | 0 | - 未知格式 |
0 | 0 | 1 | - 任何非数字格式 |
0 | 1 | 0 | - 正确的实数 |
0 | 1 | 1 | - 无穷大 (tag = 10) |
1 | 0 | 0 | - 零 (tag = 01) |
1 | 0 | 1 | - 空的 ST(0) 寄存器 (tag = 11)。 |
1 | 1 | 0 | - 任何非规格化数 |
为了分析结果,状态字应该通过 FSTSW 指令 (7.04-58) 从协处理器的 SWR 寄存器中复制到 CPU 的 AX 寄存器中(最好)。然后,状态可以被 TEST 指令 (7.03-90) 测试,也可以通过 SAHF 指令 (7.03-77) 加载到 CPU 的标志中。由于标志 C0、C1、C2、C3 在 SWR 寄存器(以及 AX 寄存器)中分别由位 08、09、10 和 14 表示,所以在 AH 寄存器中,相同的标志分别由位 0、1、2、6 表示。通过 SAHF 指令加载到 CPU 的标志后,C3 标志的状态由 CPU 的零标志 ZF 表示,C2 标志的状态由 CPU 的奇偶标志 PF 表示,C0 标志的状态由 CPU 的进位标志 CF 表示。
代码 | 示例 |
---|---|
D9 E5 | FXAM |
FXCH (= eXCHange) 指令交换协处理器顶部的堆栈寄存器 ST(0) 与任何其他指定的堆栈寄存器 ST(1-7) 之间的內容。
代码 | 示例 |
---|---|
D9 C(8-F) | FXCH ST(1-7)
|
- 注释
- 代码 "DD C(8-F)" 和 "DF C(8-F)" 也被 DEBUG.EXE 解码为 FXCH 指令。
FXTRACT (= eXTRACT) 指令将协处理器顶部的堆栈寄存器 ST(0) 中的实数值分解为它的尾数(即有效数字)和二进制指数(即二进制幂指数)。尾数被写入堆栈寄存器 ST(7) 中,该寄存器在此时必须为空。指数替换顶部的堆栈寄存器 ST(0) 中的原始值。然后 FXTRACT 指令将协处理器的堆栈指针减少 1;因此寄存器 ST(0-6) 被重命名为 ST(1-7),寄存器 ST(7) 被重命名为 ST(0),因此最后尾数出现在协处理器顶部的堆栈寄存器 ST(0) 中,而指数出现在寄存器 ST(1) 中。
代码 | 示例 |
---|---|
D9 F4 | FXTRACT |
FYL2X 指令计算协处理器顶部的堆栈寄存器 ST(0) 中正实数值的以 2 为底的对数,然后将对数乘以寄存器 ST(1) 中的实数乘数。乘法可以将以 2 为底的对数转换为任何任意底。积覆盖寄存器 ST(1) 中的乘数。然后 FYL2X 指令将协处理器的堆栈指针增加 1,因此寄存器 ST(1-7) 被重命名为 ST(0-6)。写入积的那个寄存器成为顶部的堆栈寄存器 ST(0)。以前的 ST(0) 寄存器被重命名为 ST(7) 并被宣布为可用,对其内容的访问将丢失。FYL2X 指令的最终结果由公式 ST(0)=ST(1)•log(ST(0)) 表示。
代码 | 示例 |
---|---|
D9 F1 | FYL2X |
FYL2XP1 命令计算任意底的对数,与 FYL2X 命令 (7.04-67) 一样,但 FYL2XP1 命令暗示其协处理器顶端堆栈寄存器 ST(0) 中的参数是由 F2XM1 命令 (7.04-01) 计算的级数和。为了获得高精度的计算,该级数和必须在 (−1 + 1/SQRT2) 到 (−1 + SQRT2) 的范围内,对应于以 2 为底的对数的值从 −1/2 到 +1/2。协处理器堆栈指针的进一步乘以堆栈寄存器 ST(1) 中的乘数和增量操作与 FYL2X 命令 (7.04-67) 中的操作相同。计算的对数保留在顶端堆栈寄存器 ST(0) 中。FYL2XP1 命令的最终结果由公式 ST(0)=ST(1)•log(1+ST(0)) 表示。
代码 | 示例 |
---|---|
D9 F9 | FYL2XP1 |