binutilsにおけるx86マルチバイトnop

nop

nopはNo OPerationの略で何もしない命令です。 Intelプロセッサではフラグを書き換えない xchg 命令を利用した xchgl %eax, %eax がnopに対応し、 ハードウェアの特別なサポートも存在するとしています。

nopを使えば任意のバイト数をnopで埋める際には消費するクロックサイクルを抑えるために マルチバイトnopを使う方が良いとされています。

プロセッサによって利用するマルチバイトnopは異なり、 binutils では f32 , f16 , alt , alt_long , alt_short にグループ分けされたnopが定義されています。

マルチバイトnop

以下にnopテーブルを示します。 この命令を直接書いてもうまく処理されないので、かならず .byte ディレクティブで埋め込むか、 .p2align を利用したコンパイラの最適化を利用してください。

f32

f16

alt

alt_long

alt_short

プロセッサによるnopの選択

どのプロセッサも1-2バイトnopは f32 のものを使用する。

i386, i486, Pentium, Generic32

f32 のテーブルをそのまま使用する。

PentiumPro, Pentium4, Nocona, Core, Core2, Core i7, Generic64

3-10では alt を使用し、11-15では alt_long を使用する。 64bit環境では data16 によっていくらでもnopを長くできる。

Athlon, K6, K8, AMDFAM10, BD

3-10では alt を使用し、11-15では alt_short を使用する。

実際マルチバイトnopは速いのか?

簡単な実験をしました。 環境はFedora16, gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2), Intel Core i7 930 @ 2.80GHz です。

#include <stdio.h>

asm(
    "N_1:\n"
    "    .byte 0x90, 0x90, 0x90, 0x90, 0x90\n"
    "    .byte 0x90, 0x90, 0x90, 0x90, 0x90\n"
    "    .byte 0x90, 0x90, 0x90, 0x90, 0x90\n"
    "    ret\n"
    "N_15:\n"
    "    .byte 0x66, 0x66, 0x66, 0x66, 0x66\n"
    "    .byte 0x66, 0x2e, 0x0f, 0x1f, 0x84\n"
    "    .byte 0x00, 0x00, 0x00, 0x00, 0x00\n"
    "    ret\n"
);

int main(void){
    int i;
    for(i = 0; i != -1; i++)
        putchar(N_1(i));    /* or N_15(i) */
    return 0;
}
$ ./n_1.out > /dev/null
real    0m59.134s
user    0m58.787s
sys     0m0.159s

$ ./n_15.out > /dev/null
real    0m50.698s
user    0m50.375s
sys     0m0.156s