質問
MSILを調べて、多くの nop の手順。 MSDNの記事によると、彼らは何のアクションも取らず、オペコードにパッチが適用された場合にスペースを埋めるために使用されます。リリースビルドよりもデバッグビルドで多く使用されています。これらの種類のステートメントがアセンブリ言語で使用され、オペコードが単語の境界に収まるようにすることを知っていますが、MSILで必要なのはなぜですか?
解決
NOPにはいくつかの目的があります:
- これらは、生成されたコード内で他の行と組み合わされている場合でも、デバッガーが行にブレークポイントを配置できるようにします。
- ローダーは、異なるサイズのターゲットオフセットでジャンプにパッチを適用できます。
- コードのブロックを特定の境界に揃えることができます。これはキャッシュに適しています。
- 関数全体のサイズの変更を心配することなく、インクリメンタルリンクを使用して、コードのチャンクを新しいセクションの呼び出しで上書きできます。
他のヒント
デバッグでnopsが使用される方法は次のとおりです。
Nopsは、言語コンパイラ(C#、VBなど)によって暗黙的なシーケンスポイントを定義するために使用されます。これらは、JITコンパイラーに、マシン命令をIL命令にマップできる場所を指示します。
DebuggingModes.IgnoreSymbolStoreSequencePoints 、詳細のいくつかを説明します。
C#では、コール指示の後にNopsも配置されるため、ソース内のリターンサイトの場所は、コール後の行ではなくコールアウトになります。
リリースビルドが何も出力しないコードで、行ベースのマーカー(ブレークポイントなど)の機会を提供します。
また、特定のプロセッサまたはアーキテクチャ向けに最適化する場合、コードの実行を高速化する可能性があります。
長い間、プロセッサはほぼ並行して動作する複数のパイプラインを採用しているため、2つの独立した命令を同時に実行できます。 2つのパイプラインを持つ単純なプロセッサでは、最初の命令がすべての命令をサポートし、2番目の命令がサブセットのみをサポートします。また、まだ完了していない前の命令の結果を待つ必要がある場合、パイプライン間にいくつかのストールがあります。
これらの状況では、専用の nop が次の命令を特定のパイプライン(最初の、または最初ではない)に強制し、後続の命令のペアを改善して< em> nop は償却以上のものです。
おい!ノーオペレーションは素晴らしいです!時間を消費するだけの命令です。薄暗い暗黒時代では、これを使用して、重要なループでタイミングを微調整したり、さらに重要なことに自己修正コードのフィラーとして使用したりします。
最近(4年間)働いていた1つのプロセッサで、次の操作が開始される前に前の操作が完了したことを確認するためにNOPが使用されました。例えば:
レジスタに値をロード(8サイクルかかります) nop 8 登録するために1を追加します
これにより、追加操作の前にレジスタの値が正しいことが確認されました。
もう1つの使用法は、vector0のアドレスがたとえば0、vector 1の0x20などであるため、特定のサイズ(32バイト)である必要がある割り込みベクトルなどの実行単位を埋めることで、コンパイラーはNOPを配置しました必要に応じてそこに。
それらの古典的な使用法の1つは、デバッガーが常にソースコード行をIL命令に関連付けることができるようにすることです。
これらは、デバッグ中に edit-and-continue をサポートするために使用できます。デバッガーに、オフセットなどを変更せずに古いコードを新しいコードに置き換える作業の余地を提供します。
バッファオーバーフローエクスプロイトのペイロードでは、Nopは不可欠です。
ddaaが言ったように、nopsはスタック内の分散を考慮に入れるため、戻りアドレスを上書きすると、nopスレッド(行の多くのnops)にジャンプしてから、実行可能コードを正しくヒットします。命令の先頭ではないバイトにジャンプします。
50年は遅すぎますが、ちょっと。
Nopは、アセンブリコードを手動で入力する場合に便利です。 コードを削除する必要がある場合は、古いオペコードをnopできます。
同様に、オペコードを上書きして新しいコードを挿入し、別の場所にジャンプすることもできます。そこで、上書きされたオペコードを配置し、新しいコードを挿入します。準備ができたら、元に戻ります。
利用可能なツールを使用しなければならない場合がありました。場合によっては、これは非常に基本的なマシンコードエディタにすぎません。
最近のコンパイラでは、この手法はまったく意味がありません。
ソフトウェアのクラッキングシーンでは、アプリケーションのロックを解除する古典的な方法は、キーまたは登録または期間などをチェックする行にNOPをパッチして、何もせずにアプリケーションを起動し続けることです登録されています。
また、プレースホルダーとしての機能を難読化するように自分自身を変更するコード内にNOPを見ました(非常に古いコピー防止)。
やや非正統的な使用は NOP-Slides で、バッファオーバーフローエクスプロイト。
リンカは、長い命令(通常は長いジャンプ)を短い命令(短いジャンプ)に置き換えることができます。 NOPは余分なスペースを取ります-他のジャンプが機能しなくなるため、コードを移動できませんでした。これはリンク時に発生するため、コンパイラはロングジャンプとショートジャンプのどちらが適切かを判断できません。
少なくとも、これは従来の使用方法の1つです。
これは特定の質問に対する答えではありませんが、昔はNOPを使用してブランチ遅延スロット、他の方法で有用な指示で埋めることができなかった場合。
.NETコンパイラはMSIL出力を調整しますか? ILへのアクセスを高速化するのに役立つかもしれないと思います...また、私の理解では、他のハードウェアプラットフォームでは移植可能で整列アクセスが必要です。
最初に学んだアセンブリはSPARCだったので、別の命令で埋めることができない場合、通常は分岐遅延スロットに精通しています。通常、分岐命令の上に置く命令、またはループでカウンタをインクリメントする命令、NOPを使用します。
クラッキングには慣れていませんが、悪意のある機能がどこから始まるかを正確に計算する必要がないように、NOPを使用してスタックを上書きするのが一般的だと思います。
NOPを使用して、ISRに入った後に蓄積されたレイテンシを自動的に調整しました。ネイルタイミングのデッドオンに非常に便利です。