【发布时间】:2012-09-15 14:02:10
【问题描述】:
NOPL 在 x86 机器中的作用是什么?感觉好像什么都没做,但是为什么总是在汇编代码里呢?
【问题讨论】:
-
你问“为什么总是在汇编代码中?” - 通常你不会在汇编代码中找到很多 NOP。您正在查看的某些特定代码是否有很多 NOP?
-
伙计,我希望我能“感受”到代码的作用!
NOPL 在 x86 机器中的作用是什么?感觉好像什么都没做,但是为什么总是在汇编代码里呢?
【问题讨论】:
NOP 是一个单字节的“无操作”操作,字面意思是“无操作”。 NOPW、NOPL 等。是等效的无所事事,但占用 word 和 long-sized 字节。
例如
NOP // 1byte opcode
NOP // 1byte opcode
相当于做
NOPW // 2byte opcode.
它们对于填充内容非常方便,因此代码序列从特定的内存边界开始,占用几个字节的指令空间,但实际上并没有做任何事情。
NOP 对 CPU 的唯一影响是将 IP/EIP 增加 1。NOPx 等效项将增加 2、4 等...
【讨论】:
XCHG AX,AX 和 XCHG BX,BX 不相同。第一个是官方的NOP,不会导致数据依赖。
根据John Fremlin's blog: Operands to NOP on AMD64、nopw、nopl 等是gas 语法,而不是AT&T 语法。
下面是由gas 为不同的nop 和gas source 生成的指令编码,指令长度为3 到15 个字节。请注意,有些与英特尔推荐的nop 表单相同(见下文),但不是全部。特别是,在较长的nop 的gas 中,在不同的nop 形式中使用多个(最多5 个)连续的0x66 操作数前缀,而英特尔推荐的nop 形式从不使用多个0x66 操作数前缀在任何一条推荐的nop 指令中。
source code 中 gas 2.30 的nop 编码(为了可读性而重新格式化):
/* 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 可用于从 1 到 9 字节的所有指令长度。有几个不同的nop,因为所有长于两个字节的nop 都接受1 个操作数。一个字节的nop (0x90) 是xchg (e)ax,(e)ax 的同义词。
Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z, CHAPTER 4: INSTRUCTION SET REFERENCE, 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 在自我修改代码、调试和逆向工程方面也非常有用。
【讨论】:
nop 不再是 xchg eax,eax 的同义词。 nop 不会将 eax 的前 32 位清零,但 xchg eax,eax 会。
xchg eax,eax,它必须使用2字节操作码+modrm编码(87 C0),因为0x90没有边-对 RAX 的影响。但是xchg eax, ecx 仍然可以组装成0x91 - 只有0x90 专门被nop felixcloutier.com/x86/nop 接管。但是xchg rax,rax 和xchg ax,ax 仍然可以使用REX.W 或66 90,因为它们没有架构效果。 godbolt.org/z/xn5nKnWT6
其实在代码需要打补丁的时候,会在汇编代码中使用NOP。
由于新指令的大小可能与旧指令的大小不同,因此需要填充。
填充指令应该和 NOP 一样,虽然它可能会占用几个字节。
我们插入更复杂的指令(如 66 90)而不是几个 NOP 的原因是,一条指令通常比几个 NOP 执行得更快。
【讨论】: