16位模式下的操作数大小前缀

9

我将尝试解释GAS在使用.code16时的行为。

根据手册,当处于16位模式下使用32位操作数或指令时,指令编码会产生一个66H操作数前缀。这是否意味着

.code16
movw %eax, %ebx

这种模式是否合法?那么这段代码不能在16位处理器上运行吗?

2
这是不合法的,因为 w 后缀表示 2 字节移动。movl %eax,%eax 是合法的。 - fuz
1个回答

16

从80386开始,我们可以使用operandsize-addresssize-覆盖前缀。这些前缀可以与16位地址模式和32位地址模式配合使用。

此外,它还可以用于实地址模式、保护模式和虚拟86模式。这些前缀会反转代码段中一个指令的默认操作数大小和/或地址大小。默认的操作数大小和地址大小由代码段描述符中的D标志指定(如果没有GDT / LDT,则在BIOS的POST过程完成后我们变成16位地址模式)。

在16位地址模式下,如果我们想要使用32位操作数和/或32位地址,则必须添加这些前缀。如果没有这些前缀,我们只能在16位地址模式下使用16位地址/操作数。

在32位地址模式下,如果我们想要使用32位操作数和/或32位地址,则必须从代码中省略这些前缀。如果我们向我们的代码中添加这些前缀,则可以在32位地址模式下使用16位地址/操作数。

Intel:

指令前缀可用于覆盖代码段的默认操作数大小和地址大小。这些前缀可在实地址模式、保护模式和虚拟8086模式下使用。操作数大小或地址大小前缀仅在指令执行期间更改大小。

以下两个指令前缀允许在一个段内混合使用32位和16位操作:

  • 操作数大小前缀(66H)
  • 地址大小前缀(67H)

这些前缀会反转代码段描述符中D标志选择的默认大小。例如,处理器可以按照以下四种方式之一解释(MOV mem,reg)指令:

  • 在32位代码段中:

    • 使用32位有效地址将32位从32位寄存器移动到内存。
    • 如果前面有操作数大小前缀,则使用32位有效地址将16位从16位寄存器移动到内存。
    • 如果前面有地址大小前缀,则使用16位有效地址将32位从32位寄存器移动到内存。
    • 如果前面同时有地址大小前缀和操作数大小前缀,则使用16位有效地址将16位从16位寄存器移动到内存。
  • 在16位代码段中:

    • 使用16位有效地址,将16位寄存器中的内容移动到内存中。
    • 如果有操作数大小前缀,则使用16位有效地址将32位寄存器中的内容移动到内存中。
    • 如果有地址大小前缀,则使用32位有效地址将16位寄存器中的内容移动到内存中。
    • 如果有地址大小前缀和操作数大小前缀,则使用32位有效地址将32位寄存器中的内容移动到内存中。

    上述示例表明,任何指令都可以在16位或32位段中生成任何操作数大小和地址大小的组合。选择代码段的16位或32位默认值通常基于以下标准:

    • 性能 - 尽可能使用32位代码段。它们在P6系列处理器上比16位代码段运行得快得多,在早期的IA-32处理器上略快。
    • 代码段将要运行的操作系统 - 如果操作系统是16位操作系统,则可能不支持32位程序模块。
    • 操作模式 - 如果代码段被设计为在实地址模式、虚拟8086模式或SMM中运行,则必须是16位代码段。
    • 向后兼容早期的IA-32处理器 - 如果代码段必须能够在Intel 8086或Intel 286处理器上运行,则必须是16位代码段。

代码段描述符中的D标志确定了代码段指令的默认操作数大小和地址大小。(在实地址模式和虚拟8086模式中,不使用段描述符,则默认值为16位。)D标志被设置的代码段是32位代码段;清除D标志的代码段是16位代码段。

可执行代码段。该标志称为D标志,它指示该段中指令引用的有效地址和操作数的默认长度。如果标志设置,则假定32位地址和32位或8位操作数;如果清除,则假定16位地址和16位或8位操作数。指令前缀66H可用于选择除默认值之外的操作数大小,前缀67H可用于选择除默认值之外的地址大小。

32位操作数前缀可用于实地址模式程序中执行32位指令形式。此前缀还允许实地址模式程序使用处理器的32位通用寄存器。 32位地址前缀可用于实地址模式程序中,从而允许使用32位偏移量。

从Intel386处理器开始的IA-32处理器可以使用地址覆盖前缀生成32位偏移量;然而,在实地址模式下,32位偏移量的值不能超过FFFFH,否则会导致异常。

汇编语言使用:

如果定义了一个将在实地址模式下运行的代码段,必须将其设置为USE 16属性。如果在此代码段中的指令中使用32位操作数(例如MOV EAX, EBX),汇编器会自动生成一个操作数前缀,强制处理器执行32位操作,即使其默认的代码段属性是16位。

32位操作数前缀允许实地址模式程序使用32位通用寄存器(EAX、EBX、ECX、EDX、ESP、EBP、ESI和EDI)。

在32位模式下,在段寄存器和32位通用寄存器之间移动数据时,Pentium Pro处理器不需要使用16位操作数大小前缀;但是,某些汇编器需要此前缀。处理器假定通用寄存器的16个最低有效位为目标或源操作数。从段选择器到32位寄存器传输值时,处理器将寄存器的两个高位字节填充为零。

AMD:

3.3.2. 32位与16位地址和操作数大小

处理器可配置为32位或16位地址和操作数大小。在32位地址和操作数大小下,最大线性地址或段偏移量为FFFFFFFFH(2^32-1),操作数大小通常为8位或32位。在16位地址和操作数大小下,最大线性地址或段偏移量为FFFFH(2^16-1),操作数大小通常为8位或16位。

在使用32位寻址时,逻辑地址(或far指针)由16位段选择器和32位偏移量组成;在使用16位寻址时,它由16位段选择器和16位偏移量组成。指令前缀允许程序内临时覆盖默认的地址和/或操作数大小。

在保护模式下运行时,当前执行代码段的段描述符定义默认地址和操作数大小。段描述符是一个系统数据结构,通常对应用程序代码不可见。汇编指令允许为程序选择默认寻址和操作数大小。然后,汇编器和其他工具适当地设置代码段的段描述符。

在实地址模式下运行时,默认的寻址和操作数大小为16位。可以在实地址模式中使用地址大小覆盖来启用32位寻址;但是,最大允许的32位线性地址仍为000FFFFFH(2^20-1)。

3.6. 操作数大小和地址大小属性

当处理器在保护模式下执行时,每个代码段都有一个默认的 操作数大小 属性和 地址大小属性。这些属性是通过代码段的段描述符中的 D(默认大小)标志选择的(参见Intel架构软件开发人员手册第3卷第3章“保护模式内存管理”)。当设置了D标志时,将选择32位操作数大小和地址大小属性;当标志被清除时,则选择16位大小属性。当处理器在实地址模式、虚拟8086模式或SMM(系统管理模式)下执行时,默认的操作数大小和地址大小属性始终为16位。
操作数大小属性选择指令操作的操作数大小。当16位操作数大小属性生效时,操作数通常可以是8位或16位;当32位操作数大小属性生效时,操作数通常可以是8位或32位。
地址大小属性选择用于寻址内存的地址大小:16位或32位。当16位地址大小属性生效时,段偏移量和位移量为16位。此限制将可寻址的段大小限制为64KB。当32位地址大小属性生效时,段偏移量和位移量为32位,允许寻址高达4GB的段。
特定指令的 默认 操作数大小 属性和/或 地址大小 属性可以通过向指令添加 操作数大小 前缀和/或 地址大小前缀 来覆盖(参见Intel架构软件开发人员手册第3卷第2章“指令前缀”)。此前缀的效果仅适用于它附加到的指令。
表3-1显示了在保护模式下执行时(根据D标志和操作数大小和地址大小前缀的设置),有效的操作数大小和地址大小。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接