【问题标题】:Is x86 32-bit assembly code valid x86 64-bit assembly code?x86 32 位汇编代码是有效的 x86 64 位汇编代码吗?
【发布时间】:2017-10-20 17:05:39
【问题描述】:

所有 x86 32 位汇编代码都是有效的 x86 64 位汇编代码吗?

我想知道 32 位汇编代码是否是 64 位汇编代码的子集,即每个 32 位汇编代码都可以在 64 位环境中运行?

我猜答案是肯定的,因为 64 位 Windows 可以执行 32 位程序,但后来我看到 64 位处理器支持 32 位兼容模式?

如果不是,请提供一个不是有效的 64 位汇编代码的 32 位汇编代码的小示例,并说明 64 位处理器如何执行 32 位汇编代码。

【问题讨论】:

  • 你需要使用兼容模式,是的。
  • @Jester - 你能举例说明 32 位指令不是有效的 64 位指令吗?可以混合使用 32 位和 64 位汇编代码吗?
  • push eax 例如在 64 位模式下无效。
  • @Jester 是的,你可以混合使用它们:远跳转到 32 位选择器,然后运行 ​​32 位代码,然后远跳转到 64 位选择器。
  • 这不是我对混合的定义……这意味着它们是分开的而不是混合的;)

标签: windows assembly binary x86


【解决方案1】:

现代 x86 CPU 具有三种主要操作模式(此描述已简化):

  • 实模式下,CPU 执行 16 位代码,禁用分页和分段。代码中的内存地址指的是物理地址,段寄存器的内容被移位并添加到地址中以形成有效地址。
  • 保护模式中,CPU 根据 CS(代码段)寄存器中的段选择器执行 16 位或 32 位代码。启用分段,可以(并且通常)启用分页。通过远跳转到适当的段,程序可以在 16 位和 32 位代码之间切换。 CPU 可以进入子模式虚拟 8086 模式,以在受保护模式的操作系统内部为各个进程模拟 实模式
  • long 模式下, CPU 执行 64 位代码。分段主要是禁用的,分页是启用的。 CPU 可以进入子模式兼容模式,在为长模式编写的操作系统中执行 16 位和 32 位保护模式代码。 兼容模式 通过远跳转到设置了适当位的 CS 选择器来输入。 虚拟 8086 模式不可用。

*有一个很好的x86-64 operating modes 表格,包括传统模式和真实模式,以及长模式的所有 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。每个寄存器都可以用作字节、字、双字或 qword 寄存器。 REX 前缀系列(0x40 到 0x4f)对操作数是指旧寄存器还是新寄存器进行编码。另外还有 8 个 SSE/AVX 寄存器 xmm8、xmm9、...、xmm15 可用。
  • 您只能推送/弹出 64 位和 16 位数量(尽管您不应该这样做),不能推送/弹出 32 位数量。
  • 单字节inc regdec reg 指令不可用,它们的指令空间已重新用于REX 前缀。两字节的inc r/mdec r/m 仍然可用,所以inc regdec reg 仍然可以被编码。
  • 存在一种新的指令指针相对寻址模式,使用 32 位模式必须对 [disp32] 绝对地址进行编码的 2 种冗余方式中较短的一种。
  • 默认地址宽度为 64 位,可以通过 0x67 前缀选择 32 位地址宽度。 16 位寻址不可用。
  • 默认操作数宽度为 32 位。可以通过 0x66 前缀选择 16 位宽度,可以通过适当的 REX 前缀选择 64 位宽度,与您使用的寄存器无关。
  • 不能在需要 REX 前缀的指令中使用 ahbhchdh。 REX 前缀会使这些寄存器编号改为表示寄存器 sidispbp 的低 8 位。
  • 写入 64 位寄存器的低 32 位会清除高 32 位,从而避免乱序执行的错误依赖。 (写入 8 位或 16 位部分寄存器仍会与 64 位旧值合并。)
  • 由于分段不起作用,除了用于支持线程本地存储 (TLS) 的 fsgs 覆盖 (0x64, 0x65) 之外,段覆盖是毫无意义的空操作。
  • 此外,许多专门处理分段的指令不可用。它们是:push/pop segpush/pop fs/gs 除外)、arplcall far(仅 0xff 编码有效)、lesldsjmp far(仅 0xff 编码有效)、
  • 处理十进制算术的指令不可用,它们是:daadasaaaaasaamaad
  • 此外,以下指令不可用:bound(很少使用)、pusha/popa(不适用于附加寄存器)、salc(未记录)、
  • 0x80 的 0x82 指令别名无效。
  • 在早期的 amd64 CPU 上,lahfsahf 不可用。

基本上就是这样!

【讨论】:

  • 比我的答案更好!
  • 还有 16 位或 32 位模式中不可用的 RIP 寻址模式
  • @LưuVĩnhPhúc 已解决!
  • 常规 VEX 编码在 16 位模式下也不可用(because those invalid opcodes were used intentionally as traps,另请参阅 MichaelPetch 正在评论的我的答案底部的链接)。所以这意味着没有 AVX1/2 / FMA,而且一些 BMI 指令也不可用。
  • @PeterCordes 是的,好吧。再次查看手册,VEX 前缀在实模式和虚拟 8086 模式下确实是禁止使用的,但它们确实在 16 位保护模式下工作。
【解决方案2】:

不,虽然有大量重叠,但 64 位汇编代码不是 32 位汇编代码的超集,因此 32 位汇编通常在 64 位模式下无效。

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

This question 详细介绍了一些被删除的指令,但也有许多编码形式的含义发生了变化。

例如,cmets 中的 Jester 给出了 push eax 在 64 位代码中无效的示例。根据this reference,您可以看到 32 位推送标记为 N.E.,表示 不可编码。在 64 位模式下,编码用于表示 push rax(8 字节推送)。因此,相同的字节序列在 32 位模式和 64 位模式下具有不同的含义。

一般而言,您可以浏览该站点上的指令列表,并找到许多被列为无效或不可以 64 位编码的指令。

如果不是,请提供一个 32 位汇编代码的小示例 不是有效的 64 位汇编代码并解释 64 位处理器如何 执行 32 位汇编代码。

如上所述,push eax 就是这样一个例子。我认为缺少的是 64 位 CPU 支持直接运行 32 位二进制文​​件。他们不是通过机器语言级别的 32 位和 64 位指令之间的兼容性来做到这一点的,而是简单地通过 32 位模式 来让解码器(特别是)解释指令流作为 32 位 x86 而不是 x86-64,以及用于运行 64 位指令的所谓 long 模式。当此类 64 位芯片首次发布时,通常运行 32 位操作系统,这几乎意味着芯片永久处于此模式(永远不会进入 64 位模式)。

最近,通常运行 64 位操作系统,它知道模式,当用户启动 32 位进程时,它会将 CPU 置于 32 位模式(这仍然非常常见:直到最近我的浏览器仍然是 32 位的)。

模式的所有细节和适当的术语都可以在 fuz 的答案中找到,这确实是您应该阅读的答案。

【讨论】: