## 2.5 操作数选择
一条指令可以有零或多个操作数-指令操作的数据。零操作数的一个例子是NOP(no operation)。操作数可以在下面的位置:
+ 位于指令本身(立即数)
+ 位于寄存器(EAX, EBX, ECX, EDX, ESI, EDI, ESP, 或者EBP,如果是32位操作数;AX, BX, CX, DX, SI, DI, SP, 或者BP,如果是16位操作数;AH, AL, BH, BL, CH, CL, DH, 或者DL,如果是8位操作数;段寄存器;位操作的EFLAGS寄存器)
+ 位于存储器
+ 位于I/O端口
立即数和寄存器操作可以比存储器操作数有更快的访问速度,因为存储器操作数必须从存储器中取出来。寄存器操作数可以从CPU中获得。立即数同样从CPU获得,因为它们被作为指令的一部分而被预取。
对于那些带有操作数的指令来说,有些隐式的声明操作数;有些显式的声明操作数;其他的使用隐式和显式两种方式的组合来声明操作数;例如:
隐式操作数:AAM
根据定义,AAM(ASCII adjust for multiplication)操作AX寄存器。
显示操作数:XCHG EAX, EBX
要交换的操作数被编码在指令中操作码后面的位置。
隐式和显式操作数:PUSH COUNTER
存储器变量COUNTER(显式操作数)被拷贝到堆栈(隐式操作数)的顶部。
注意:大部分指令都有隐式操作数。例如,所有的算术指令更新EFLAGS寄存器。
80386指令可以显式的引用1到2个操作数。2个操作数的指令,如MOV, ADD, XOR等,通常用结果覆盖其中一个参与操作数。因此可以区分处源操作数(不受操作影响的一个)和目的操作数(被结果覆盖的一个)。
对于多数指令,两个显式指定的操作数的一个-或者是源,或者是目的操作数-可以位于寄存器或者存储器。另一个操作数必须是寄存器或者是立即数作为源操作数。因此,显式2个操作数指令允许操作数有如下几种:
+ 寄存器到寄存器
+ 寄存器到存储器
+ 存储器到寄存器
+ 立即数到寄存器
+ 立即数到存储器
然而,一些字符串指令和堆栈操作指令从存储器到存储器传输数据。有些字符串指令隐式的声明操作数,并且都位于存储器。入栈和出栈操作允许在存储器和基于存储器的堆栈之间传输数据。
### 2.5.1 立即数
一些指令使用指令自身的一部分数据作为一个(有时是两个)操作数。这样的操作数被称作是立即数。操作数可以是32位,16位,或者8位长。例如:
```
SHR PATTERN, 2
```
指令的一个字节含有数值2,变量PATTERN的移位数。
```
TEST PATTERN, 0FFFF00FFH
```
指令中的双字包含要测试变量PATTERN的掩码。
### 2.5.2 寄存器操作数
操作数可以被放置在下面32位通用寄存器之一(EAX, EBX, ECX, EDX, ESI, EDI, ESP, 或者EBP),16位通用寄存器之一(AX, BX, CX, DX, SI, DI, SP, 或者BP),8位通用寄存器之一(AH, BH, CH, DH, AL, BL, CL, 或者DL)。
80386有引用段寄存器的指令(CS, DS, ES, SS, FS, GS)。只有在系统设计人员选择了段模式的时候,应用程序才能使用这些指令。
80386有些指令用来引用标志寄存器。标志位可以存储在堆栈中,并从堆栈中恢复。一些指令会直接改变在EFLAGS中通常会被修改的标志位。其他标志位很少被修改,它们可以通过堆栈中的标志位镜像而被间接的改变。
### 2.5.3 存储器操作数
寻址存储器操作数的数据操作指令必须声明(直接或间接的)包含操作数的段以及操作数在段内的偏移量。然而,为了指令编码的速度和紧凑,段选择符被存储在高速段寄存器内。因此,如果要寻址一个存储器操作数,数据操作指令只需要声明要求的段寄存器和偏移量。
寻址存储器操作数的80386数据操作指令使用下面方法之一来声明操作数在段内的偏移量:
1.大多数访问存储器的数据操作指令包含一个字节,显式的指明操作数的寻址方式。一字节,被认为是modR/M字节,跟在操作码后面,指明操作数位于寄存器还是存储器中。如果操作数在存储器中,地址由一个段寄存器和下面的任意值计算出来:基址寄存器,索引寄存器,缩放因子,移位。当使用索引寄存器时,modR/M字节跟在标识索引寄存器和缩放因子的字节之后,这种寻址方式具有最高的自由度。
2.一些数据操作指令隐式的使用特殊寻址方式:
+ 对于一些隐式使用EAX寄存器的短型MOV,操作数的偏移量被编码为双字放入指令中。没有使用基址寄存器,索引寄存器以及缩放因子。
+ 字符串操作隐式的使用DS:ESI, (MOVS, CMPS, OUTS, LODS, SCAS) 或者ES:EDI (MOVS, CMPS, INS, STOS)来寻址。
+ 堆栈操作隐式的通过SS:ESP寄存器来寻址;例如,PUSH, POP, PUSHA, POPA, POPAD, PUSHF, PUSHFD, POPF, POPFD, CALL, RET, IRET, IRETD, 异常以及中断。
2.5.3.1 段选择
数据操作指令不需要显式声明使用哪个段寄存器。对于所有这些指令,段寄存器的声明是可选的。对于所有的存储器访问,如果没有显式的声明一个段,处理器根据表2-1的规则来自动选择一个段寄存器。(如果系统设计人员已经选择了平坦模式,那么段寄存器和处理器选择它们的规则对于应用程序来说不是很明显)。
存储器引用的类型和操作数驻留的段之间有着紧密的联系。通常,引用存储器暗示着当前段为数据段(即,暗示着段选择符在DS中)。然而,ESP和EBP用来访问堆栈中的数据项;因此,当ESP和EBP作为基址寄存器使用时,当前段为堆栈段(即,SS含有选择符)。
特殊的指令前缀要素可以覆盖默认的段选择。段重载前缀允许显式的段选取。80386对于每个段寄存器都有一个段重载前缀。只有在下面的特殊情况下,隐式的段选择才不会被重载:
+ 在字符串指令中用于目的字符串的ES。
+ 堆栈指令中SS的使用。
+ 取指令时CS的使用。
表2-1. 缺省段寄存器选择规则
| 需要存储器引用 | 使用的段寄存器 | 隐式段选择规则 |
| --- | --- | --- |
| 指令 | Code(CS) | 自动指令预取 |
| 堆栈 | Stack(SS) | 所有堆栈的入栈和出栈。任何使用ESP或者EBP作为基址寄存器的存储器引用。 |
| 本地数据 | Data(DS) | 除了堆栈和目的字符串的所有数据引用。 |
| 目的字符串 | Extra(ES) | 字符串指令的目的操作数。 |
2.5.3.2 有效地址计算
modR/M字节为寻址方法提供了最大的自由度,需要modR/M作为第二个字节的指令在80386指令集中非常常见。对于由modR/M定义的存储器操作来说,段内偏移通过对下面三部分求和计算出来:
+ 指令中的移位。
+ 基址寄存器。
+ 索引寄存器。索引寄存器可能会自动与缩放因子2,4,或者8相乘。
上面生成的结果称为有效地址。构成有效地址的每个成员可以时正值,也可以是负值。如果所有成员的和超过了232,有效地址会被截短为32位值。图2-10展示了modR/M寻址的所有可能。
移位,由于是编码在指令中,所以作固定运算很有用;例如:
+ 简单缩放操作数的位置。
+ 静态分配数组的开始。
+ 记录中一项的偏移量。
基址和索引具有类似的功能。都使用相同的通用寄存器集合。都可以用来计算地址中需要动态确定的部分;例如:
+ 进程参数的位置以及堆栈中的局部变量。
+ 在几个相同记录类型的记录或记录数组中,其中一个的开始。
+ 一位数组或多维数组的开始。
+ 动态分配数组的开始。
当通用寄存器用于基址或索引时,它们有下面的不同:
+ ESP不能用作索引寄存器。
+ 当ESP或者EBP用于基址寄存器时,缺省段由SS指定。所有其他情况,使用DS作为段选择器。
缩放因子允许数组项是2,4,或8字节宽时用索引高效的访问数组。索引寄存器的移位在寻址时由处理器完成,不会损失性能。也避免了单独的移位或乘法指令。
基址,索引,和移位这三个部件可以任意组合;任何一个上面的部件可以是空的。缩放因子只有在使用了索引因子时才能用。每种可能的组合对于由高级语言程序员和汇编程序员使用的数据结构非常有用。下面时一些寻址部件不同组合后的可能使用:
移位
单独的移位只是操作数的偏移量。该部件用于直接寻址静态分配的缩放操作数。可以使用8位,16位或32位移位。
基址
操作数的偏移量由通用寄存器的一个间接声明,作为“基”变量。
基址 + 移位
寄存器和移位的组合可以用于两种不同的目的:
1.对中元素大小不是2,4,或8字节的静态数组进行索引。移位编码为相对于数组开始的偏移量。寄存器内保存计算结果,决定一项指定的元素在数组内的偏移量。
2.访问记录中的一项。移位定位记录中的数据项。寄存器选择出现的记录中的一个,因此为这种常见的功能提供了一种紧凑的编码。
这种组合一个重要的特殊情形就是访问在堆栈中的进程活动记录的参数。这种情况下,EBP是作为基址寄存器的最好选择,因为当EBP被用作基址寄存器时,存储器自动使用堆栈段寄存器(SS)来定位操作数,因此为这种常见的功能提供了一种紧凑的编码。
(索引 + 缩放) + 移位
当静态数组中元素大小是2,4,或者8时,这种组合提供了有效的索引。移位定位在数组的开始,索引寄存器保存要求的数组元素的下标,处理器自动应用缩放因子将下标转换为索引。
基址 + 索引 + 移位
两个寄存器加在一起支持二维数组(移位决定数组的开始)或者记录数组的一个实例(移位指示记录中的一项)。
基址 + (索引 * 缩放) + 移位
当数组中元素是2,4,或者8字节宽时,这种组合提供了一种二维数组的高效索引。
![](https://box.kancloud.cn/2016-03-06_56dbfdadac4e8.png)
- 第一章 80386介绍
- 1.1 该手册的组织结构
- 1.2 其他文献
- 第二章 编程基本模型
- 2.1 存储器组织和段
- 2.2 数据类型
- 2.3 寄存器
- 2.4 指令格式
- 2.5 操作数选择
- 2.6 中断和异常
- 第4章 系统寄存器
- 4.1 系统寄存器 (System Registers)
- 4.2 系统指令 (System Instructions)
- 第五章 内存管理
- 5.1 分段地址转换(Segment Translation)
- 5.2 分页地址转换(Page Translation)
- 5.3 混合分段和分页地址转换(Combining Segment and Page Translation)
- 第六章 内存管理
- 6.1 为什么要保护(Why Protection?)
- 6.2 80386保护机制概述(Overview of 80386 Protection Mechnaisms)
- 6.3 段级保护(Segment-Level Protection)
- 6.4 页级保护(Page-Level Protection)
- 6.5 混合分页和分段保护(Combining Page and Segment Protection)
- 第7章 多任务(Multitasking)
- 8.1 I/O 寻址(I/O Addressing)
- 7.1 任务状态段(Task State Segment)
- 7.3 任务寄存器(Task Register)
- 7.4 任务门描述符(Task Gate Descriptor)
- 7.5 任务切换(Task Switching)
- 7.6 任务链(Task Linking)
- 7.7 任务寻址空间(Task Address Space)
- 第8章 输入 输出
- 8.2 I/O 指令(I/O Instructions)
- 8.3 保护和I/O(Protection and I/O)
- 第9章 异常和中断(Exceptions and Interrupts)
- 9.1 识别中断(Identifying Interrupts)
- 9.2 允许和禁止中断(Enabling and Disabling Interrupts)
- 9.3 同时发生的中断和异常的优先级(Priority Among Simultaneous Interrupts and Exceptions)
- 9.4 中断描述符表(Interrupt Descriptor Table)
- 9.5 IDT 描述符(IDT Descriptors)
- 9.6 中断任务和中断子程序(Interrupt Tasks and Interrupt Procedures)
- 9.7 出错码(Error Code)
- 9.8 异常条件(Exception Conditions)
- 9.9 异常总结(Exception Summary)
- 9.10 出错码总结(Error Code Summary)
- 第10章 初始化(Initialization)
- 10.1 复位后处理器状态(Processor State After Reset)
- 10.2 实模式初始化(Software Initialization for Real-Address Mode)
- 10.3 切换到保护模式(Switching to Protected Mode)
- 10.4 保护模式初始化(Software Initialization for Protected Mode)
- 10.5 初始化示例
- 10.6 TLB测试
- 第十四章 80386实地址模式
- 14.1 物理地址构成
- 14.2 寄存器和指令
- 14.3 中断和异常处理
- 14.4 进入和离开实地址模式
- 14.6 实地址模式异常
- 14.7 与8086的不同
- 14.8 与80286实地址模式的不同