【问题标题】:Why does Visual Studio use xchg ax,ax为什么 Visual Studio 使用 xchg ax,ax
【发布时间】:2011-01-09 07:30:27
【问题描述】:

我正在查看我的程序的分解(因为它崩溃了),并注意到很多

xchg    ax, ax

我用谷歌搜索了它,发现它本质上是一个 nop,但是为什么 Visual Studio 做一个 xchg 而不是一个 noop?

应用程序是一个 C# .NET3.5 64 位应用程序,由 Visual Studio 编译

【问题讨论】:

  • 与这些指令相关的字节是否相同? nop 应该是 16 进制 90。 xchg ax,ax 是 2 字节指令吗?
  • 这个问题比我最初想象的(或无论如何记得)要微妙一些。
  • 嘿,你能告诉我如何看待你的汇编代码吗?我想看看如何将简单的 prgoram 转换为程序集。谢谢

标签: assembly code-generation x86-64


【解决方案1】:

这里真正的问题是;为什么反汇编程序选择显示为xchg ax,ax而不是nop

我怀疑这是来自 32 位或 64 位代码,并且(鉴于反汇编程序显示 xchg ax,ax 而不是 xchg eax,eax)有一个操作数大小覆盖前缀旨在使 nop 稍大(以更少的指令实现一定数量的填充);并且前缀的存在混淆了反汇编程序,导致xchg ax,ax(而不是nopo16 nop)。

【讨论】:

    【解决方案2】:

    在 x86 上,NOP 指令 XCHG AX, AX

    这 2 条助记指令汇编成相同的二进制操作码。 (实际上,我认为汇编程序可以使用任何xchg 的寄存器,但据我所知AXEAX 通常用于nop)。

    xchg ax, ax 具有不更改寄存器值和不更改标志的特性(嘿 - 这是一个无操作!)。


    编辑(回应 Anon 的评论):

    哦,对了 - 现在我记得 xchg 指令有几种编码。有些采用一组 mod/r/m 位(如许多 Intel x86 架构指令)来指定源和目标。这些编码占用超过一个字节。还有一种特殊编码,它使用单个字节并与(E)AX 交换通用寄存器。如果指定的寄存器也是(E)AX,那么你有一个单字节 NOP 指令。您还可以使用 xchg 指令的较大变体指定 (E)AX 与自身交换。

    我猜当 MSVC 想要咀嚼超过一个字节而不进行任何操作时,它使用 xchg 的多字节版本和 (E)AX 作为源和目标 - 它需要与单字节xchg,但使用更多空间。在反汇编中,您不会看到多字节 xchg 被解码为 NOP,即使结果相同。

    具体而言,xchg eax, eaxnop 可以编码为操作码 0x900x87 0xc0,具体取决于您希望它用完 1 个字节还是 2 个字节。 Visual Studio 反汇编程序(可能还有其他反汇编程序)会将操作码 0x90 解码为 NOP 指令,并将操作码 0x87 0xc0 解码为 xchg eax, eax

    自从我完成详细的汇编语言工作以来已经有一段时间了,所以我可能至少在一个方面是错的......

    【讨论】:

    • 那将是反汇编器很聪明(或愚蠢,取决于你想要它做什么)。您是否看到两个解码器都使用相同的反汇编程序?这可能会很有趣。
    • XCHG 仅在 (E)AX 上运行。
    • 我的猜测是反汇编程序可以访问告诉它原始指令是什么的元数据,但这是一个猜测。
    • 让我想起了 360 和 370 机器上的 NOP:“BCR 0,0”。 BCR 是到寄存器中地址的条件分支。第一个 0 是条件代码掩码,所有条件都被屏蔽了,因此 BCR 0,0 是“无条件,不分支”。第二个零是寄存器零;当用于寻址时,寄存器零总是用值“零”代替。所以 NOP 是“无条件地,不要分支到地址 0”。
    • x86 有很多特定寄存器的“快捷方式”——0x90 是“XCHG 注册到(E)AX”。
    【解决方案3】:

    我不知道这是否与问题有关,但许多 Windows 功能以 MOV EDI、EDI 开头。这也是一个 2 字节的 NOP。两个字节的 NOP 对 hotpatch 代码很有用,因为您可以安全地用短 JMP 替换它。

    参考:http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

    【讨论】:

      【解决方案4】:

      其实xchg ax,ax就是MS反汇编“66 90”的方式。 66 是操作数大小覆盖,因此它应该在ax 而不是eax 上运行。但是,CPU 仍将其作为 nop 执行。此处使用 66 前缀使指令大小为两个字节,通常用于对齐目的。

      【讨论】:

      【解决方案5】:

      MSVC 通常将 NOP 放入已编译的代码中以进行调试构建。这允许“编辑并继续”工作。

      【讨论】:

        【解决方案6】:

        xchg ax,axnop 实际上是相同的指令,它们映射到相同的操作码(0x90 iirc)。没关系,xchg ax,ax 无操作。为什么要在不执行任何操作的指令上浪费额外的操作码编码?

        值得怀疑的是为什么你看到两个助记符都被打印出来了。我猜这只是你反汇编的一个缺陷,没有二进制差异。

        【讨论】:

        • 是否在无操作指令上浪费操作码编码的问题引发了一些有趣的问题,尤其是在指定捕获无效指令的架构中。一方面,如果操作码空间的很大一部分被功能相同的指令使用,禁止使用除一种编码之外的所有编码将使其他编码可用于未来的操作。另一方面,这样做可能会使一些动态代码生成场景变得更加困难,并且如果应该捕获无效操作码,则需要额外的硬件来捕获它们。
        • @supercat:这与拒绝定义另一个什么都不做的操作码有点不同,不是吗?
        • @SamB:假设使用 16 位指令的处理器定义了 256 个操作码来表示“将 8 位立即操作数添加到 r0”,另外 256 个操作码表示“从 8 位立即操作数中减去r0”。加零和减零的指令在功能上可能是等效的,但取缔其中之一可能会使期望动态修改在紧密循环中使用的“将 N 加到 r0”指令的代码复杂化,以避免需要专用寄存器加数(或必须在循环中加载它)。如果只有 255 条“将 N 添加到 r0”指令...
        • ...可能想要将 N 的值更改为零或从零更改的代码可能必须特殊情况下该行为,而不是简单地能够将所需的 N 存储在操作码的 8 位内并忽略其余部分。
        • @supercat:不过,这与决定不为 NOP 发明全新的编码几乎不一样
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-03-12
        • 1970-01-01
        • 2021-10-10
        • 2017-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多