【问题标题】:What does NOPL do in x86 system?NOPL 在 x86 系统中做了什么?
【发布时间】:2012-09-15 14:02:10
【问题描述】:

NOPL 在 x86 机器中的作用是什么?感觉好像什么都没做,但是为什么总是在汇编代码里呢?

【问题讨论】:

标签: assembly x86 nop


【解决方案1】:

NOP 是一个单字节的“无操作”操作,字面意思是“无操作”。 NOPW、NOPL 等。是等效的无所事事,但占用 word 和 long-sized 字节。

例如

NOP // 1byte opcode
NOP // 1byte opcode

相当于做

NOPW // 2byte opcode.

它们对于填充内容非常方便,因此代码序列从特定的内存边界开始,占用几个字节的指令空间,但实际上并没有做任何事情。

NOP 对 CPU 的唯一影响是将 IP/EIP 增加 1。NOPx 等效项将增加 2、4 等...

【讨论】:

  • 我从来没有听说过 x86 指令集中的 NOPW 和 NOPL 操作.. 也没有出现在英特尔指令集参考中 :) 也许你在混合不同的架构
  • @Jack 在我看来就像 AT&T 语法
  • @harold 不知道.. 如果我使用 nopw/nopl 而不是 nop,gcc 会抱怨
  • @Jack:在 X86 上,NOP 只是“XCHG AX,AX”的别名,因为这是一个单周期指令,除了增加 IP 之外没有任何影响。有许多两字节指令可以达到这个目的,包括那些将寄存器移动到自身的指令(在某些情况下表示 MOV AX,AX 的操作码在其他情况下表示 MOV EAX,EAX,但在任何一种情况下唯一的效果是 IP 增加两个)。
  • 请注意,您不能“仅仅”选择一个明显的幂等操作。 XCHG AX,AXXCHG BX,BX 相同。第一个是官方的NOP不会导致数据依赖。
【解决方案2】:

根据John Fremlin's blog: Operands to NOP on AMD64nopwnopl 等是gas 语法,而不是AT&T 语法。

下面是由gas 为不同的nopgas source 生成的指令编码,指令长度为3 到15 个字节。请注意,有些与英特尔推荐的nop 表单相同(见下文),但不是全部。特别是,在较长的nopgas 中,在不同的nop 形式中使用多个(最多5 个)连续的0x66 操作数前缀,而英特尔推荐的nop 形式从不使用多个0x66 操作数前缀在任何一条推荐的nop 指令中。

source codegas 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 在自我修改代码、调试和逆向工程方面也非常有用。

【讨论】:

  • 请注意,在 amd64 上,nop 不再是 xchg eax,eax 的同义词。 nop 不会将 eax 的前 32 位清零,但 xchg eax,eax 会。
  • 确实,如果你为x86-64组装xchg eax,eax,它必须使用2字节操作码+modrm编码(87 C0),因为0x90没有边-对 RAX 的影响。但是xchg eax, ecx 仍然可以组装成0x91 - 只有0x90 专门被nop felixcloutier.com/x86/nop 接管。但是xchg rax,raxxchg ax,ax 仍然可以使用REX.W 或66 90,因为它们没有架构效果。 godbolt.org/z/xn5nKnWT6
【解决方案3】:

其实在代码需要打补丁的时候,会在汇编代码中使用NOP。

由于新指令的大小可能与旧指令的大小不同,因此需要填充。

填充指令应该和 NOP 一样,虽然它可能会占用几个字节。

我们插入更复杂的指令(如 66 90)而不是几个 NOP 的原因是,一条指令通常比几个 NOP 执行得更快。

【讨论】:

    猜你喜欢
    • 2016-07-15
    • 2011-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-06
    • 2011-05-09
    相关资源
    最近更新 更多