GCCパッドがNOPで機能するのはなぜですか?


81

私はしばらくの間Cを使用してきましたが、ごく最近、ASMを使い始めました。プログラムをコンパイルするとき:

objdump逆アセンブリにはコードがありますが、retの後にnopsします。

私が学んだことから、nopsは何もしません、そしてretの後は実行さえされないでしょう。

私の質問は:なぜわざわざ?ELF(linux-x86)は、任意のサイズの.textセクション(+ main)で機能しませんでしたか?

ただ学ぼうとして、助けていただければ幸いです。


それらのNOPは継続しますか?で停止する場合は80483af、次の関数を8バイトまたは16バイトに揃えるためのパディングである可能性があります。
ミスティック2011年

いいえ、4回のnopの後、関数に直行します:__ libc_csu_fini
olly

1
NOPがgccによって挿入された場合、サイズが1〜9バイトガス構文を使用する場合は10 )のNOPが多数あるため、0x90のみを使用するとは思わない
phuclv 2014

回答:


89

まず第一に、gcc常にこれを行うとは限りません。パディングはによって制御され-falign-functions、とによって自動的にオンに-O2なり-O3ます。

-falign-functions
-falign-functions=n

関数の開始をnnバイトまでスキップして、次の2の累乗に揃えます。たとえば、 -falign-functions=32関数を次の32バイト境界に-falign-functions=24整列しますが、23バイト以下をスキップしてこれを実行できる場合にのみ、次の32バイト境界に整列します。

-fno-align-functions-falign-functions=1は同等であり、関数が整列されないことを意味します。

一部のアセンブラは、nが2の累乗である場合にのみこのフラグをサポートします。その場合は切り上げられます。

nが指定されていないかゼロの場合は、マシンに依存するデフォルトを使用します。

レベル-O2、-O3で有効になります。

これを行う理由は複数ある可能性がありますが、x86の主な理由はおそらく次のとおりです。

ほとんどのプロセッサは、整列された16バイトまたは32バイトのブロックで命令をフェッチします。コード内の16バイト境界の数を最小限に抑えるために、クリティカルループエントリとサブルーチンエントリを16だけ整列させると有利な場合があります。または、クリティカルループエントリまたはサブルーチンエントリの後の最初の数命令に16バイトの境界がないことを確認してください。

(Agner Fogによる「アセンブリ言語でのサブルーチンの最適化」から引用。)

編集:これはパディングを示す例です:

デフォルト設定でgcc4.4.5を使用してコンパイルすると、次のようになります。

指定すると、次のようになり-falign-functionsます。


1
-Oフラグは使用せず、単純な「gcc -otesttest.c」を使用しました。
olly 2011年

1
@olly:64ビットUbuntuでgcc 4.4.5を使用してテストしましたが、テストではデフォルトでパディングはなく、パディングはあります-falign-functions
NPE 2011年

@aix:私はcentOS 6.0(32ビット)を使用しており、フラグなしでパディングがあります。誰かが私の完全な「objdump-j.text -d./test」出力をダンプしたいですか?
olly 2011年

1
さらにテストすると、オブジェクトとしてコンパイルすると、「gcc-ctest.c」になります。パディングはありませんが、リンクすると「gcc -otesttest.o」と表示されます。
olly 2011年

2
@olly:そのパディングはmain、実行可能ファイルに続く関数の配置要件を満たすために、リンカーによって挿入されます(私の場合、その関数はです__libc_csu_fini)。
NPE 2011年

15

これは、次の関数を8、16、または32バイトの境界で整列させるために行われます。

A.Fogによる「アセンブリ言語でのサブルーチンの最適化」から:

11.5コードの調整

ほとんどのマイクロプロセッサは、整列された16バイトまたは32バイトのブロックでコードをフェッチします。重要なサブルーチンエントリまたはジャンプラベルが16バイトのブロックの終わり近くにある場合、マイクロプロセッサは、そのコードのブロックをフェッチするときに、数バイトの有用なコードのみを取得します。ラベルの後の最初の命令をデコードする前に、次の16バイトもフェッチする必要がある場合があります。これは、重要なサブルーチンエントリとループエントリを16だけ揃えることで回避できます。

[...]

サブルーチンエントリの整列は、必要に応じてアドレスを8、16、32、または64で割り切れるように、サブルーチンエントリの前に必要な数のNOPを配置するのと同じくらい簡単です。


25〜29バイト(メインの場合)の違いですが、もっと大きなことを話しているのですか?テキストセクションのように、readelfを通して私はそれが364バイトであることがわかりましたか?また、_startで14回のnopsに気づきました。なぜこれらのことを「as」しないのですか?私は新人です、お詫びします。
olly 2011年

@olly:コンパイルされたマシンコードでプログラム全体の最適化を実行する開発システムを見てきました。関数のアドレスfooが0x1234の場合、リテラル0x1234のすぐ近くでそのアドレスを使用するコードはmov ax,0x1234 / push ax / mov ax,0x1234 / push ax、オプティマイザーがmov ax,0x1234 / push ax / push ax。に置き換えることができるようなマシンコードを生成する可能性があります。このような最適化の後に関数を再配置してはならないことに注意してください。そのため、命令を削除すると実行速度は向上しますが、コードサイズは向上しません。
スーパーキャット2015

5

私が覚えている限り、命令はcpuでパイプライン化され、さまざまなcpuブロック(ローダー、デコーダーなど)が後続の命令を処理します。RET命令が実行されているとき、次の命令はすでにCPUパイプラインにロードされています。推測ですが、ここで掘り下げてみることができます(NOP安全な特定の数の場合は、調査結果を共有してください)。


@ninjalj:え?この質問は、パイプライン化されているx86について尋ねています(mcoが言ったように)。最近のx86プロセッサの多くは、おそらくこれらのnopを含め、実行すべきではない命令を投機的に実行します。おそらくあなたは他の場所でコメントするつもりでしたか?
David Cary

3
@DavidCary:x86では、プログラマーには完全に透過的です。誤って推測された投機的に実行された命令は、結果と効果が破棄されるだけです。MIPSには、「投機的」な部分はまったくなく、分岐遅延スロットの命令は常に実行され、プログラマーは遅延スロットを埋める必要があります(または、アセンブラーにそれを行わせると、おそらくnopsになります)。
ninjalj 2013年

@ninjalj:はい、誤って推測された投機的に実行されたopsと整列されていない命令の影響は、出力データ値に影響を与えないという意味で透過的です。ただし、どちらもプログラムのタイミングに影響を及ぼします。これが、gccがx86コードにnopsを追加する理由である可能性があります。これは、元の質問で尋ねられたものです。
David Cary

1
@DavidCary:それが理由である場合、無条件のジャンプの後ではなく、条件付きのジャンプの後にのみ表示されますret
ninjalj 2013年

1
これが理由ではありません。間接ジャンプのフォールバック予測(BTBミスの場合)が次の命令ですが、それが命令以外のガベージである場合、誤投機を停止するための推奨される最適化は、ud2またはint3常に障害となる命令であるため、フロントエンドは代わりにデコードを停止することを認識しますdivたとえば、潜在的に高価または偽のTLBミス負荷をパイプラインに供給することです。これは、関数の最後のretまたは直接jmp末尾呼び出しの後には必要ありません。
PeterCordes20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.