在x86机器中,NOPL的作用是什么?它似乎没有做任何事情,但为什么它总是出现在汇编代码中?
NOP // 1byte opcode
NOP // 1byte opcode
等同于执行
NOPW // 2byte opcode.
NOP指令非常方便,可以利用它填充一些字节的指令空间,使代码序列在特定的内存边界上开始,但实际上不执行任何操作。
NOP对CPU唯一的影响是将IP
/EIP
增加1。NOPx等效指令会将其增加2、4等等。
XCHG AX,AX
和 XCHG BX,BX
不是相同的。第一个是官方的“NOP”操作,并且不会导致数据依赖关系。 - MSaltersnopw
,nopl
等是gas
语法,而不是AT&T语法。gas
为长度从3到15个字节的不同nop
生成的指令编码,此处来源于gas
源码。请注意,其中有些与英特尔推荐的nop
形式相同(见下文),但并非全部如此。特别地,在较长的nop
中,gas
使用多个(最多5个)连续的0x66
操作数前缀以不同的nop
形式,而英特尔推荐的nop
形式从未在任何单个推荐的nop
指令中使用超过一个0x66
操作数前缀。
nop
编码来自于经过重新格式化以增加可读性的gas 2.30 源代码:
/* nopl (%[re]ax) */
static const unsigned char alt_3[] = {0x0f,0x1f,0x00};
/* nopl 0(%[re]ax) */
static const unsigned char alt_4[] = {0x0f,0x1f,0x40,0x00};
/* nopl 0(%[re]ax,%[re]ax,1) */
static const unsigned char alt_5[] = {0x0f,0x1f,0x44,0x00,0x00};
/* nopw 0(%[re]ax,%[re]ax,1) */
static const unsigned char alt_6[] = {0x66,0x0f,0x1f,0x44,0x00,0x00};
/* nopl 0L(%[re]ax) */
static const unsigned char alt_7[] = {0x0f,0x1f,0x80,0x00,0x00,0x00,0x00};
/* nopl 0L(%[re]ax,%[re]ax,1) */
static const unsigned char alt_8[] = {0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00};
/* nopw 0L(%[re]ax,%[re]ax,1) */
static const unsigned char alt_9[] =
{0x66,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00};
/* nopw %cs:0L(%[re]ax,%[re]ax,1) */
static const unsigned char alt_10[] =
{0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00};
static const unsigned char *const alt_patt[] = {
f32_1, f32_2, alt_3, alt_4, alt_5, alt_6, alt_7, alt_8,
alt_9, alt_10
};
nop
。有几种不同的nop
,因为所有长度大于两个字节的nop
都接受1个操作数。一个字节的nop
(0x90
)与xchg (e)ax,(e)ax
同义。
Intel® 64和IA-32架构软件开发人员手册,第2卷(2A、2B和2C):指令集参考,A-Z,第4章:指令集参考,M-Z列出了不同指令长度的推荐nop
形式。Table 4-12. Recommended Multi-Byte Sequence of NOP Instruction
Length Assembly Byte Sequence
2 bytes 66 NOP 66 90H
3 bytes NOP DWORD ptr [EAX] 0F 1F 00H
4 bytes NOP DWORD ptr [EAX + 00H] 0F 1F 40 00H
5 bytes NOP DWORD ptr [EAX + EAX*1 + 00H] 0F 1F 44 00 00H
6 bytes 66 NOP DWORD ptr [EAX + EAX*1 + 00H] 66 0F 1F 44 00 00H
7 bytes NOP DWORD ptr [EAX + 00000000H] 0F 1F 80 00 00 00 00H
8 bytes NOP DWORD ptr [EAX + EAX*1 + 00000000H] 0F 1F 84 00 00 00 00 00H
9 bytes 66 NOP DWORD ptr [EAX + EAX*1 + 00000000H] 66 0F 1F 84 00 00 00 00 00H
除了英特尔推荐的这些nop
外,还有许多其他的nop
。除了像Marc B在他的回答中提到的将指令对齐到特定内存边界之外,nop
在自修改代码、调试和逆向工程中也非常有用。
xchg eax,eax
,则必须使用2字节的opcode+modrm编码([87 C0](https://www.felixcloutier.com/x86/xchg)),因为`0x90`对RAX没有副作用。但是,`xchg eax,ecx仍然可以组装为
0x91 - 只有0x90特别被
nop占用 https://www.felixcloutier.com/x86/nop。但是,
xchg rax,rax和
xchg ax,ax`仍然可以使用REX.W或66 90,因为它们没有架构效果。https://godbolt.org/z/xn5nKnWT6 - Peter Cordes实际上,在代码需要修补时,汇编代码中将使用NOP。
因为新指令的大小可能与旧指令不同,所以需要填充。
填充指令应该与NOP具有相同的作用,尽管它可能占用多个字节。
我们插入更复杂的指令(如66 90),而不是几个NOP的原因是:一条指令通常比几条NOP执行得更快。