x86 32位汇编代码是否适用于x86 64位汇编代码?

10
所有x86 32位汇编代码是否都是有效的x86 64位汇编代码?
我一直在想32位汇编代码是否是64位汇编代码的子集,即每个32位汇编代码都可以在64位环境中运行?
我猜答案是肯定的,因为64位Windows能够执行32位程序,但是我看到64位处理器支持32位兼容模式?
如果不是,请提供一个小的例子,说明32位汇编代码无法转换为64位汇编代码,并解释64位处理器如何执行32位汇编代码。

你需要使用兼容模式,对的。 - Jester
1
@Jester 是的,你可以混合使用它们:远跳转到一个32位选择器,然后运行32位代码,最后再远跳转到一个64位选择器。 - fuz
1
这并不是我对混合的定义...这意味着它们是分开的,而不是混合的 ;) - Jester
1
x86和x64架构的汇编语言有什么区别? - phuclv
显示剩余9条评论
2个回答

18

现代 x86 处理器有三种主要操作模式(此处描述简化):

  • 实模式 下,CPU 执行 16 位代码,页面和分段被禁用。您的代码中的内存地址指向物理地址,段寄存器的内容被移位并添加到地址以形成有效地址。
  • 保护模式 下,CPU 根据 CS(代码段)寄存器中的段选择器执行 16 位或 32 位代码。分段被启用,页面可以(通常是)启用。程序可以通过远跳转到适当的段来在 16 位和 32 位代码之间切换。CPU 可以进入子模式 虚拟 8086 模式,以模拟从受保护模式操作系统内部的个别进程进入 实模式
  • 长模式 下,CPU 执行 64 位代码。分段被大多数禁用,页面被启用。CPU 可以进入子模式 兼容性模式,从而在为 长模式 编写的操作系统中执行 16 位和 32 位受保护模式代码。通过远跳转到具有相应位设置的 CS 选择器可进入 兼容性模式。无法使用 虚拟 8086 模式

维基百科有一个很好的表格,列出了 x86-64 操作模式,包括遗留和实模式以及长模式的所有 3 个子模式。在主流 x86-64 操作系统下,引导 CPU 核心后,它们始终处于长模式,根据 32 位或 64 位用户空间切换不同的子模式。(不考虑系统管理模式中断...)


那么 16 位、32 位和 64 位模式之间有什么区别呢?

16 位和 32 位模式基本相同,除了以下差异:

  • 在16位模式下,默认的地址和操作数宽度为16位。分别使用0x67和0x66前缀可以将其更改为32位,适用于单个指令。在32位模式下,则相反。
  • 在16位模式下,指令指针被截断为16位,跳转到高于65536的地址可能会导致奇怪的结果。
  • VEX / EVEX编码的指令(包括AVX、AVX2、BMI、BMI2和AVX512指令集)不会在实模式或虚拟8086模式中解码(尽管它们在16位保护模式中可用)。
  • 16位模式比32位模式具有较少的寻址模式,但是如果需要,可以按每个指令覆盖到32位寻址模式。

现在,64位模式有一些不同。大多数指令的行为与32位模式类似,但有以下区别:

  • 有八个名为r8、r9、...、r15的附加寄存器。每个寄存器都可以用作字节、字、双字或四字寄存器。 REX前缀系列(0x40至0x4f)编码操作数是新寄存器还是旧寄存器。 还提供了额外的八个SSE / AVX寄存器xmm8、xmm9、...、xmm15。
  • 只能推送/弹出64位和16位量(虽然不应该使用后者),不能推送/弹出32位量。
  • 单字节的 inc reg dec reg 指令不可用,它们的指令空间已被重新分配给REX前缀。 两个字节的 inc r/m dec r/m 仍然可用,因此可以编码 inc reg dec reg
  • 存在新的指令指针相对寻址模式,使用了32位模式具有的2种冗余方式中长度较短的 [disp32] 绝对地址编码方式之一。
  • 默认地址宽度为64位,可以通过0x67前缀选择32位地址宽度。16位寻址不可用。
  • 默认操作数宽度为32位。可以通过0x66前缀选择16位宽度,可以通过适当的REX前缀独立选择64位宽度,无论使用哪个寄存器。
  • 在需要REX前缀的指令中不能使用ahbhchdh。REX前缀会导致这些寄存器编号改为表示sidispbp寄存器的低8位。
  • 将数据写入64位寄存器的低32位会清除高32位,从而避免乱序执行时出现错误依赖关系。(写入8或16位部分寄存器仍会与64位旧值合并。)
  • 由于分段功能失效,分段覆盖语句无意义,只是空操作,除了fsgs覆盖语句(0x64,0x65),用于支持线程本地存储(TLS)。
  • 此外,许多专门处理分段的指令不可用,包括:push/pop seg(除push/pop fs/gs外)、arplcall far(只有0xff编码有效)、lesldsjmp far(只有0xff编码有效)。
  • 不支持处理十进制算术的指令,包括:daa, das, aaa, aas, aam, aad,
  • 另外,以下指令不可用:bound(很少使用),pusha/popa(对于额外的寄存器没有用处),salc(未经记录)。
  • 0x82指令作为0x80的别名是无效的。
  • 在早期的amd64 CPU上,lahfsahf不可用。

基本上就是这些了!


比我的回答更好! - BeeOnRope
还有RIP寻址模式,这在16位或32位模式下不可用。 - phuclv
@LưuVĩnhPhúc 地址已解决! - fuz
1
@PeterCordes 好的。再次查阅手册,VEX前缀在实模式和虚拟8086模式下确实是禁止使用的,但在16位保护模式下可以使用。 - fuz
啊,那很有道理,我没有考虑到它们在实模式和vm86模式下可能不同。谢谢你的检查! - Peter Cordes
显示剩余5条评论

12

不,不是这样的。

尽管有很多重叠,但64位汇编代码并不是32位汇编代码的超集,因此32位汇编代码在64位模式下通常无效。

这适用于助记符汇编源代码(由汇编器汇编成二进制格式)以及二进制机器码格式本身。

这个问题详细介绍了被移除的指令,但也有许多编码形式的含义发生了改变。

例如,评论中的Jester给出了push eax在64位代码中无效的示例。根据这个参考,你可以看到32位push被标记为N.E.,意思是不可编码。在64位模式下,该编码用于表示push rax(一个8字节的push)。因此,相同的字节序列在32位模式和64位模式下具有不同的含义。

总体而言,您可以浏览该网站上的指令列表,并找到许多在64位模式下被列为无效或不可编码的指令。

如果不是这样,请提供一个32位汇编代码的小例子,它不能成为64位汇编代码,并解释64位处理器如何执行32位汇编代码。

如上所述,push eax就是一个例子。我认为缺失的是64位CPU支持直接运行32位二进制文件。它们并不通过机器语言级别上的32位和64位指令的兼容性来实现,而是通过具有32位模式的方式,其中解码器(尤其是)将指令流解释为32位x86而不是x86-64,以及用于运行64位指令的所谓长模式。当这样的64位芯片首次发布时,通常运行32位操作系统,这几乎意味着芯片永久处于此模式(永远不进入64位模式)。

最近,典型的做法是运行64位操作系统,该操作系统了解这些模式,并且在用户启动32位进程时将CPU置于32位模式下(这仍然非常常见:直到最近我的浏览器仍然是32位的)。

有关模式的所有详细信息和适当术语可以在fuz的答案中找到,这实际上是您应该阅读的答案。


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