我正在查看我的程序的分解(因为它崩溃了),并注意到很多
xchg ax, ax
我用谷歌搜索了它,发现它本质上是一个 nop,但是为什么 Visual Studio 做一个 xchg 而不是一个 noop?
该应用程序是一个 C# .NET3.5 64 位应用程序,由 Visual Studio 编译
我正在查看我的程序的分解(因为它崩溃了),并注意到很多
xchg ax, ax
我用谷歌搜索了它,发现它本质上是一个 nop,但是为什么 Visual Studio 做一个 xchg 而不是一个 noop?
该应用程序是一个 C# .NET3.5 64 位应用程序,由 Visual Studio 编译
在 x86 上,NOP
指令是 XCHG AX, AX
2 条助记指令汇编成相同的二进制操作码。(实际上,我想汇编程序可以使用任何xchg
一个寄存器,但是据我所知,AX
或者EAX
是通常用于的寄存器)。nop
xchg ax, ax
具有不更改寄存器值和不更改标志的属性(嘿 - 这是一个无操作!)。
编辑(回应 Anon 的评论。):
xchg
哦,对了 - 现在我记得指令有几种编码。有些采用一组 mod/r/m 位(如许多 Intel x86 架构指令)来指定源和目标。这些编码占用超过一个字节。还有一种特殊编码,它使用单个字节并与通用寄存器交换(E)AX
. 如果指定的寄存器也是(E)AX
,那么你有一个单字节 NOP 指令。您还可以使用指令的较大变体指定(E)AX
与自身交换xchg
。
我猜想 MSVC 使用xchg
with的多字节版本(E)AX
作为源和目标,当它想要咀嚼超过一个字节而不进行任何操作时 - 它需要与单字节相同的周期数xchg
,但使用更多空间。在反汇编中,您不会看到xchg
解码为 a的多字节NOP
,即使结果相同。
具体来说xchg eax, eax
,或者nop
可以编码为操作码0x90
,或者0x87 0xc0
取决于您是否希望它用完 1 个或 2 个字节。Visual Studio 反汇编程序(可能还有其他反汇编程序)会将操作码解码0x90
为NOP
指令,并将操作码解码0x87 0xc0
为xchg eax, eax
.
自从我完成详细的汇编语言工作以来已经有一段时间了,所以我很可能在这里至少有一个错误......
xchg ax,ax
并且nop
实际上是相同的指令,它们映射到相同的操作码(0x90 iirc)。没关系,xchg ax,ax
是No-Op。为什么要使用不执行任何操作的指令浪费额外的操作码编码?
有问题的是为什么你看到两个助记符都被打印出来了。我想这只是你反汇编的一个缺陷,没有二进制差异。
其实,xchg ax,ax
这就是MS拆机“66 90”的方式。66 是操作数大小覆盖,因此它应该在ax
而不是eax
. 但是,CPU 仍将其作为 nop 执行。此处使用 66 前缀使指令大小为两个字节,通常用于对齐目的。
MSVC 通常将 NOP 放入已编译的代码中以进行调试构建。这允许“编辑并继续”工作。
我不知道这是否与问题有关,但许多 Windows 功能都以 MOV EDI、EDI 开头。这也是一个 2 字节的 NOP。两个字节的 NOP 对热补丁代码很有用,因为您可以安全地用短 JMP 替换它。
参考:http: //blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx
这里真正的问题是;为什么反汇编程序选择将其显示为xchg ax,ax
而不是nop
?
我怀疑这是来自 32 位或 64 位代码,并且(鉴于反汇编程序显示xchg ax,ax
而不是xchg eax,eax
),有一个操作数大小覆盖前缀旨在使nop
稍大(以使用更少的指令实现一定数量的填充);并且前缀的存在混淆了反汇编程序,导致xchg ax,ax
(而不是nop
or o16 nop
)。