操作数寻址

IA-32 机器指令中的源操作数可以位于:

  • 指令自身(立即数,立即数会被编码到机器指令中)
  • 寄存器
  • 内存位置
  • I/O 端口

指令可以从以下位置返回数据:

  • 寄存器
  • 内存位置
  • I/O 端口

立即操作数

一些指令会将数据作为源操作数编码到指令自身中。这些操作数被称为立即操作数(或者简称为立即数)。比如下列指令中将 14 加到 EAX 寄存器的内容里:

ADD EAX, 14

所有的算术指令(除了 DIVIDIV 指令)都允许使用立即数作为源操作数。不同指令中所允许的最大的立即数值会有所不同,但均不会超过 23212^{32} - 1


寄存器操作数

源操作数和目的操作数可以是以下任意寄存器,这取决于执行的指令:

  • 通用目的寄存器
  • 段寄存器
  • EFLAGS 寄存器
  • x87 FPU 寄存器
  • MMX 寄存器
  • 控制寄存器(CR0 CR2 CR3 CR4 和系统表指针寄存器GDTR LDTR IDTR 和任务寄存器
  • 调试寄存器 DRn
  • MSR 寄存器

有些指令会使用 64 bit 的操作数,这需要放入一对寄存器中。如 EDX:EAX,其中 EDX 包含高序 bit,EAX 包含低序 bit。


内存操作数

通过段选择器和偏移地址来表示内存中的源操作数和目的操作数。段选择器表示包含该操作数的段,偏移地址则表示操作数的线性或有效地址。偏移地址既可以是 32 bit 也可以是 16 bit,分别用记号表示为 (m16:32m16:16)。

指定一个段选择器

段选择器可以显式表示也可以隐式使用。最常用的是将段选择器加载到段寄存器中,然后允许处理器隐式的选择寄存器,这取决于所要执行的操作。处理器会根据下表中的规则自动选择一个段:

操作类型 使用的寄存器 使用的段 默认选择规则
指令 CS 代码段 所有指令获取
SS 栈段 所有栈入栈出,和所有使用 ESPEBP 作为基寄存器的内存引用
本地数据 DS 数据段 所有数据引用,除了栈和字符串
目的字符串 ES 数据段和 ES 寄存器指向的数据段 字符串指令的目的操作数

在汇编器中,段可以通过 : 运算符覆盖。比如下列指令中,将 ES 寄存器所指的段中的数据移动到寄存器 EAX 中,其中偏移地址包含在 EBX 寄存器中。

MOV ES:[EBX], EAX

在机器级别,这种段覆盖体现在指令起始位置的第一个字节,即段覆盖前缀。下列默认的段选择器无法被覆盖:

  • 必须从代码段中获取指令
  • 字符串指令中的目的字符串必须存储在 ES 寄存器所指向的数据段
  • 栈入栈出操作必须使用 SS 寄存器所指向的段

部分指令需要显式的指定段选择器。这种情况下,16-bit 段选择器可以位于内存位置,也可以位于 16-bit 寄存器中。比如下列指令,通过 MOV 将位于 BX 中的段选择器移动到 DS 段寄存器中。

MOV DS, BX

段选择器也可以显式的表示为内存中 48-bit 远指针的一部分。这时,前 32-bit 包含偏移地址,后16-bit 包含段选择器。

指定一个偏移地址

内存地址的偏移量部分可以通过静态值(displacement)或通过以下组件的多个组合来计算:

  • Displacement — 8-,16-,32-bit 值。
  • Base — 通用目的寄存器中的值
  • Index — 通用目的寄存器中的值
  • Scale factor — 2, 4 或 8,用来和 Index 值相乘

通过这些组件计算出来的偏移地址被称为有效地址。除了缩放比例因子,每一个组件既可以是正数也可以是复数(2的补码)。

这些组件的组合方式如下图所示:

Intel SDM - 操作数寻址

作为基或索引组件的通用目的寄存器有如下限制:

  • ESP 寄存器无法用作索引寄存器
  • ESPEBP 用作基时,默认使用 SS 作为段寄存器。在其他情况中,使用 DS 作为默认段

基,索引和偏移组件可以进行任意组合,并且任意组件都可以为空。缩放比例因子只能在索引使用的时候使用。每一个可能的组合对高级语言或汇编中的数据结构操作有很大的帮助。

下面将展示一些常用组合的使用建议:

  • Displacement — 单独的位移表示相对于操作数的直接偏移,会被编码到指令中。一般用来访问静态申请的标量操作数。

  • Base — 单独的 base 和 displacement 一样,都表示直接偏移。但由于 base 可以更改,所以可以用来访问的动态存储的变量和数据结构

  • Base + Displacement — 可以用作两种目的:

    • 作为元素大小不是 2, 4, 8 字节的数组索引 — 位移组件编码数组起始位值的静态偏移。基寄存器保存数组中特定元素的偏移
    • 访问记录中的一个字段(结构体):基寄存器保存记录的起始地址,位移则为该字段的静态偏移

    如果是访问过程**记录(过程调用时创建的栈帧),则推荐使用 EBP 作为基寄存器,此时将自动选择栈段。

  • (Index * Scale) + Displaccement — 这种组合可以高效的索引元素大小为 2, 4, 8 字节的静态数组。Displacement 用来定位数组的起始位置,索引寄存器保存想要的数组元素下标,处理会自动应用缩放比例因子将下表转换为索引

  • Base + Index + Displacement — 同时使用两个寄存器可以用来访问二维数组(此时 displacement 保存数组的起始位置)或者记录数组中几个实例的一个(此时 displacement 为记录中的字段的偏移量)

  • Base + (Index ∗ Scale) + Displacement — 使用全部寻址组件可以高效的索引数组元素为 2, 4, 8 字节的二维数组。


I/O 端口寻址

处理器做多可以包含 216=655362^{16} = 65536 个 8-bit I/O 端口,如果是 16-bit 或 32-bit,数量则要做相应的递减。I/O 端口可以通过立即数或者 DX 寄存器中的值来寻址。

相关文章: