我试图了解 GAS 的行为.code16
。
从手册来看,似乎在 16 位部分,对于 32 位操作数或指令,将为指令编码生成 66H 操作数覆盖前缀。这是否意味着
.code16
movw %eax, %ebx
这种模式合法吗?那么代码不能在16位处理器上运行?
我试图了解 GAS 的行为.code16
。
从手册来看,似乎在 16 位部分,对于 32 位操作数或指令,将为指令编码生成 66H 操作数覆盖前缀。这是否意味着
.code16
movw %eax, %ebx
这种模式合法吗?那么代码不能在16位处理器上运行?
这些是 80386+ 的法律说明。
从 80386 开始,我们可以使用
operandsize-
和addresssize-
覆盖前缀。这些前缀可以与 16 位地址模式和 32 位地址模式结合使用。此外,它可以与实地址模式、保护模式和虚拟 86 模式一起使用。这些前缀反转了代码段中一条指令的默认操作数大小和/或地址大小。默认操作数大小和地址大小由代码段描述符中的 D 标志指定(或者如果没有 GDT/LDT,那么我们在 bios 的 POST-process 完成后成为 16 位地址模式。)
对于 16 位地址模式,如果我们想使用 32 位操作数和/或 32 位地址,我们必须添加这些前缀。如果没有这些前缀,我们只能在 16 位地址模式下使用 16 位地址/操作数。
在 32 位地址模式下,如果我们想使用 32 位操作数和/或 32 位地址,我们必须从代码中去掉这些前缀。如果我们将这些前缀添加到我们的代码中,那么我们可以在 32 位地址模式下使用 16 位地址/操作数。
英特尔:
指令前缀可用于覆盖代码段的默认操作数大小和地址大小。这些前缀可用于实地址模式以及保护模式和虚拟 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 位寄存器移动到内存。
- 如果前面有操作数大小前缀,则使用 16 位有效地址将 32 位从 32 位寄存器移动到内存。
- 如果前面有地址大小前缀,则使用 32 位有效地址将 16 位从 16 位寄存器移动到内存。
- 如果前面有地址大小前缀和操作数大小前缀,则使用 32 位有效地址将 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 位寄存器时,处理器用零填充寄存器的两个高位字节。
3.3.2. 32 位与 16 位地址和操作数大小
处理器可以配置为 32 位或 16 位地址和操作数大小。对于 32 位地址和操作数大小,最大线性地址或段偏移量为 FFFFFFFFH (2^32-1),操作数大小通常为 8 位或 32 位。对于 16 位地址和操作数大小,最大线性地址或段偏移量为 FFFFH (2^16-1),操作数大小通常为 8 位或 16 位。
使用 32 位寻址时,逻辑地址(或远指针)由 16 位段选择器和 32 位偏移量组成;当使用 16 位寻址时,它由一个 16 位段选择器和一个 16 位偏移量组成。指令前缀允许在程序中临时覆盖默认地址和/或操作数大小。
在保护模式下操作时,当前执行代码段的段描述符定义了默认地址和操作数大小。段描述符是应用程序代码通常不可见的系统数据结构。汇编器指令允许为程序选择默认寻址和操作数大小。然后,汇编器和其他工具适当地为代码段设置段描述符。
在实地址模式下操作时,默认寻址和操作数大小为 16 位。可以在实地址模式下使用地址大小覆盖来启用 32 位寻址;但是,最大允许的 32 位线性地址仍然是 000FFFFFH (2^20-1)。
3.6. 操作数大小和地址大小属性
当处理器在保护模式下执行时,每个代码段都有一个默认的操作数大小属性和地址大小属性。这些属性是通过代码段的段描述符中的 D(默认大小)标志来选择的(参见英特尔架构软件开发人员手册第 3 卷中的第 3 章,保护模式内存管理)。当设置 D 标志时,选择 32 位操作数大小和地址大小属性;当标志被清除时,选择 16 位大小的属性。当处理器在实地址模式、虚拟 8086 模式或 SMM(系统管理模式)下执行时,默认操作数大小和地址大小属性始终为 16 位。
操作数大小属性选择指令操作的操作数的大小。当 16 位操作数大小属性生效时,操作数一般可以是 8 位或 16 位,当 32 位操作数大小属性生效时,操作数一般可以是 8 位或 32 位。
address-size 属性选择用于寻址内存的地址大小:16 位或 32 位。当 16 位地址大小属性生效时,段偏移和位移为 16 位。此限制将可寻址的段大小限制为 64 KB。当 32 位地址大小属性生效时,段偏移和位移为 32 位,允许寻址高达 4 GB 的段。
可以通过向指令添加操作数大小和/或地址大小前缀来覆盖特定指令的默认操作数大小属性和/或地址大小属性(参见英特尔架构软件第 2 章中的“指令前缀”开发人员手册,第 3 卷)。此前缀的效果仅适用于它所附加的指令。
表 3-1 显示了有效的操作数大小和地址大小(在保护模式下执行时)取决于 D 标志的设置以及操作数大小和地址大小前缀。