2016-10-19 1 views
4

いくつかのソフトウェアをデバッグ中に、多くの場合、INT3命令がサブルーチンの間に挿入されていることに気付きました。なぜコンパイラはINT3命令をサブルーチン間に挿入するのですか?

This is an example.

Iは、これらの技術的サブルーチンはどちらの理由末尾にretnを実行しない場合は、実行を一時停止するために機能し、その代わりにそれらの後に、「間」に挿入されていないと仮定する。

私の前提は正しいですか?これらの命令の目的は何ですか?

+0

関数間には空白があります。非常に基本的なx86の最適化は、関数が16の倍数のアドレスから始まるようにすることです。ギャップを埋めるために*いくつかの*バイト値を求めなければならない場合は、0xccが最適です。忘却にジャンプするプログラムのコーナーケースをキャッチします。 –

+1

数十億の小さなトランジスタが、dec/jg'の代わりに 'dec/jnz'(' do {} while(-i) ')のようなループ条件を書くことが安全であるように、 {} while(-i> 0) ')。私は、ビットがカウンタで反転してもまだ動作するかもしれないコードを書くことは "安全"だろうと思うが、明らかにそれは必要ではない。 (もちろん、アウト・オブ・オーダーの実行CPU内のフリップ・ビットは、単にアーキテクチャ状態のビットを反転する可能性は低いので、より奇妙なものが得られる可能性が高いです。) –

答えて

4

Linuxの場合、gccとclangパッドは0x90(NOP)で機能を整列します。 (リンカでさえ、.oを不均一なサイズのセクションにリンクするとき)。

通常、CPUが関数の最後にRET命令の分岐予測を持たない場合を除いて、特別な利点はありません。その場合、NOPは、正しい分岐ターゲットが発見されてから回復するまでに時間がかかるものでCPUを起動させません。


関数の最後の命令がRETでない可能性があります。それは間接的なJMP(例えば、関数ポインタによるテールコール)であってもよい。その場合、分岐予測が失敗する可能性が高くなります。 (CALL/RETペアはリターンスタックによって特別に予測されますが、RETは間接JMPであり、基本的にはjmp [rsp]add rsp, 8です)。

間接JMPまたはCALL(ブランチターゲットバッファ予測が使用できない場合)のデフォルト予測では、次の命令にジャンプします。 (正しいターゲットがわかるまでは予測もストールもないか、オプションではないか、またはデフォルトの予測がジャンプテーブルに十分使える) FP sqrtやマイクロコード化されたもののように簡単に中断すると、分岐予測ミスが増します。投機的に実行された命令がTLBミスを引き起こし、ハードウェアのページウォークをトリガしたり、そうでなければキャッシュを汚染したりする場合はさらに悪化する。

例外を生成する命令INT 3には、これらの問題がありません。 CPUはINTを実行する前に実行しようとはしません。何も悪いことはありません。 IIRCでは、次の命令のデフォルト予測が有用でない場合、間接JMPの後にそのようなものを配置することをお勧めします。機能間のランダムなゴミで


、RETを含むマシンコードの偶数プリデコード16Bブロックが遅く可能性があります。現代のCPUは、4つの命令のグループで並列にデコードするため、命令がすでにデコードされてから、RETを検出することはできません。 (これは投機的実行とは異なります)。無条件ブランチ(RETのような)の後のバイトの長さの変更が遅延するのを避けることは、ブランチのデコードを遅らせるので便利です。

LCPストールはインテルCPUにのみ影響します。AMDはL1キャッシュの命令境界をマークし、大きなグループでデコードします。 (インテルはデコードされたuopキャッシュを使用してループ内で毎回実際にデコードするための電力コストなしで高いスループットを得る)。インテルCPUでは命令長の発見が実際のデコードよりも早い段階で行われることに注意してください。例えば、Sandybridgeフロントエンドは、次のようになります。

David Kanter's SnB writeup

(デイビット・カンターのハスウェルの過去記事からコピーされたダイアグラムでも、私は彼のSandybridgeの過去記事にリンクされている彼らは両方の優れている。。)

この回答(とはるかに)で説明した内容については、Agner Fog's microarch pdfタグwikiのその他のリンクも参照してください。

+0

投機的実行が 'INT 3'で止まって、次の関数の投機的実行をブロックすることを望むでしょう。次の関数のプロローグを実行することはおそらくほとんど無害ですが、無駄です。 – MSalters

+0

@Peter Cordesは、同様のトピックで、無条件分岐命令に遭遇した後(つまりRET、JMP)に分岐予測が引き続き命令をデコードするのはなぜですか?多くの場合、これらのバイトはガベージとパディングになると予想され、したがってサイクルが浪費されます。 – byteptr

+0

@byteptr:これは分岐予測ではなく、それは単なる並列デコードです。命令長マーキングは、実際のデコーダのいずれかがブロックを見て無条件分岐を検出する前に、インテルCPUの16Bのブロックで発生します。 –

5

誤った前提。

これらは関数間でパディングされています。命令をスキップすることをランダムに決定したCPUは壊れてしまい、投げ捨てるべきです。

INT 3の理由は2倍です。これは1バイトの命令です。つまり、1バイトのスペースがあっても使用できます。命令の大部分は、長すぎるため不適切です。さらに、それは "デバッグブレーク"命令です。これは、デバッガが関数間でコードを実行しようとする試みをキャッチできることを意味します。これは、retnを無視することによって引き起こされるのではなく、初期化されていない関数ポインタを使用するなどの単純な理由によるものです。

+0

ありがとうございます。 – Invalidation

+1

これは決して実行されるべきではないので、理論的には '0x00'がうまくいくでしょう。しかし実際には、CPUは、関数の境界がわからずにx86命令としてバイトをデコードするだけなので、デコード(または投機的実行)中にCPUを遅らせることのない有効な命令となる。しかし、INT3が間接ジャンプや折り返しアドレスの紛失があなたをパディングに導くまれなケースで、早期/騒がしい障害を引き起こすという良い点があります。おそらく普通はNOP埋め込み機能を備えた次の関数に黙っているよりも良いでしょう(Linuxのような典型的です)。 –

+1

マイナーコメント: 'int3'は1バイト、' int 3'は2バイトです。どちらの命令も動作が少し異なります。 – kay

関連する問題