Warning
この記事は2019年に書かれたもので、情報として残しておくためにObsidianに移したものです。現在は64bitのOSが主流なので、ここに書かれている内容は時代遅れかもしれません。
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
1: 90 nop
2: 66 90 xchg %ax, %ax
3: 8d 76 00 leal 0(%esi), %esi
4: 8d 74 26 00 leal 0(%esi, 1), %esi
5: 90 nop
8d 74 26 00 leal 0(%esi,1), %esi
6: 8d b6 00 00 00 00 leal 0L(%esi), %esi
7: 8d b4 26 00 00 00 00 leal 0L(%esi,1), %esi
8: 90 nop
8d b4 26 00 00 00 00 leal 0L(%esi, 1), %esi
9: 89 f6 movl %esi, %esi
8d bc 27 00 00 00 00 leal 0L(%edi, 1), %edi
10: 8d 76 00 leal 0(%esi), %esi
8d bc 27 00 00 00 00 leal 0L(%edi, 1), %edi
11: 8d 74 26 00 leal 0(%esi, 1), %esi
8d bc 27 00 00 00 00 leal 0L(%edi, 1), %edi
12: 8d b6 00 00 00 00 leal 0L(%esi), %esi
8d bf 00 00 00 00 leal 0L(%edi), %edi
13: 8d b6 00 00 00 00 leal 0L(%esi), %esi
8d bc 27 00 00 00 00 leal 0L(%edi, 1), %edi
14: 8d b4 26 00 00 00 00 leal 0L(%esi, 1), %esi
8d bc 27 00 00 00 00 leal 0L(%edi, 1), %edi
f16
3: 8d 74 00 lea 0(%esi), %esi
4: 8d b4 00 00 lea 0w(%si), %si
5: 90 nop
8d b4 00 00 lea 0w(%si), %si
6: 89 f6 mov %si, %si
8d bd 00 00 lea 0w(%di), %di
7: 8d 74 00 lea 0(%si), %si
8d bd 00 00 lea 0w(%di), %di
8: 8d b4 00 00 lea 0w(%si), %si
8d bd 00 00 lea 0w(%di), %di
alt
3: 0f 1f 00 nopl 0(%[re]ax)
4: 0f 1f 40 00 nopl 0(%[re]ax, %[re]ax, 1)
5: 0f 1f 44 00 00 nopw 0(%[re]ax, %[re]ax, 1)
6: 66 0f 1f 44 00 00 nopl 0L(%[re]ax)
7: 0f 1f 80 00 00 00 00 nopl 0L(%[re]ax, %[re]ax, 1)
8: 0f 1f
84 00 00 00 00 00 nopw 0L(%[re]ax, %[re]ax, 1)
9: 66 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
10: 66 2e 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
alt_long
11: 66 data16
66 2e 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
12: 66 data16
66 data16
66 2e 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
13: 66 data16
66 data16
66 data16
66 2e 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
14: 66 data16
66 data16
66 data16
66 data16
66 2e 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
15: 66 data16
66 data16
66 data16
66 data16
66 data16
66 2e 0f 1f
84 00 00 00 00 00 nopw %cs:0L(%[re]ax, %[re]ax, 1)
alt_short
11: 0f 1f 44 00 00 nopl 0(%[re]ax, %[re]ax, 1)
66 0f 1f 44 00 00 nopw 0(%[re]ax, %[re]ax, 1)
12: 66 0f 1f 44 00 00 nopw 0(%[re]ax, %[re]ax, 1)
66 0f 1f 44 00 00 nopw 0(%[re]ax, %[re]ax, 1)
13: 66 0f 1f 44 00 00 nopw 0(%[re]ax, %[re]ax, 1)
0f 1f 80 00 00 00 00 nopl 0L(%[re]ax)
14: 0f 1f 80 00 00 00 00 nopl 0L(%[re]ax)
0f 1f 80 00 00 00 00 nopl 0L(%[re]ax)
15: 0f 1f 80 00 00 00 00 nopl 0L(%[re]ax)
0f 1f 84 00 00 00 00 00 nopl 0L(%[re]ax, %[re]ax, 1)
プロセッサによる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