28

Intel のマニュアルから、命令をメモリに書き込むことは可能であると言われ、読みましたが、命令プリフェッチ キューは古い命令を既にフェッチしており、それらの古い命令を実行します。私はこの行動を観察できませんでした。私の方法論は以下の通りです。

Intel ソフトウェア開発マニュアルには、セクション 11.6 から次のように記載されています。

プロセッサに現在キャッシュされているコード セグメントのメモリ位置への書き込みにより、関連するキャッシュ ラインが無効になります。このチェックは、命令の物理アドレスに基づいています。さらに、P6 ファミリおよび Pentium プロセッサは、コード セグメントへの書き込みによって、実行のためにプリフェッチされた命令が変更される可能性があるかどうかをチェックします。書き込みがプリフェッチされた命令に影響を与える場合、プリフェッチ キューは無効になります。この後者のチェックは、命令の線形アドレスに基づいています。

したがって、古い命令を実行したい場合は、2 つの異なる線形アドレスが同じ物理ページを参照する必要があるようです。そのため、ファイルを 2 つの異なるアドレスにメモリ マップします。

int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
        MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
        MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);

変更したい命令へのポインタである単一の引数を取るアセンブリ関数があります。

fun:
    push %rbp
    mov %rsp, %rbp

    xorq %rax, %rax # Return value 0

# A far jump simulated with a far return
# Push the current code segment %cs, then the address we want to far jump to

    xorq %rsi, %rsi
    mov %cs, %rsi
    pushq %rsi
    leaq copy(%rip), %r15
    pushq %r15
    lretq

copy:
# Overwrite the two nops below with `inc %eax'. We will notice the change if the
# return value is 1, not zero. The passed in pointer at %rdi points to the same physical
# memory location of fun_ins, but the linear addresses will be different.
    movw $0xc0ff, (%rdi)

fun_ins:
    nop   # Two NOPs gives enough space for the inc %eax (opcode FF C0)
    nop
    pop %rbp
    ret
fun_end:
    nop

C では、コードをメモリ マップド ファイルにコピーします。関数は linear address から呼び出しますが、コード変更のターゲットとしてa1ポインターを渡します。a2

#define DIFF(a, b) ((long)(b) - (long)(a))
long sz = DIFF(fun, fun_end);
memcpy(a1, fun, sz);
void *tochange = DIFF(fun, fun_ins);
int val = ((int (*)(void*))a1)(tochange);

CPU が変更されたコードを取得した場合、val==1 です。それ以外の場合、古い命令が実行された場合 (2 つの nop)、val==0 です。

これを 1.7GHz Intel Core i5 (2011 macbook air) と Intel(R) Xeon(R) CPU X3460 @ 2.80GHz で実行しました。ただし、毎回、CPU が常に新しい命令に気付いていることを示す val==1 が表示されます。

私が観察したい行動を経験した人はいますか?私の推論は正しいですか?P6 および Pentium プロセッサについて言及しているマニュアルと、私の Core i5 プロセッサについて言及していないことについて、私は少し混乱しています。おそらく、CPU が命令プリフェッチ キューをフラッシュする原因となる何か他のことが起こっているのでしょうか? どんな洞察も非常に役に立ちます!

4

3 に答える 3

29

MACHINE_CLEARS.SMCCPUのパフォーマンスカウンター(イベントの一部)を確認する必要があると思います(Airパワーブックで使用されているMACHINE_CLEARSSandy Bridge 1で利用できます。また、Nehalem 2であるXeonでも利用できます-「smc」を検索してください) )。oprofileperfまたは Intelを使用Vtuneしてその値を見つけることができます。

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

マシンクリア

指標の説明

特定のイベントでは、パイプライン全体をクリアして、最後にリタイアした命令の直後から再開する必要があります。このメトリックは、メモリ順序違反、自己変更コード、および不正なアドレス範囲への特定のロードという 3 つのイベントを測定します。

考えられる問題

実行時間のかなりの部分が、マシン クリアの処理に費やされます。MACHINE_CLEARS イベントを調べて、特定の原因を特定します。

SMC: http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html

MACHINE_CLEARS イベント コード: 0xC3 SMC マスク: 0x04

自己変更コード (SMC) が検出されました。

検出された自己変更コード マシン クリアの数。

Intel は smc についても述べていますhttp://software.intel.com/en-us/forums/topic/345561 ( Intel Performance Bottleneck Analyzer の分類法からリンク)

このイベントは、自己変更コードが検出されたときに発生します。これは通常、バイナリ編集を行って特定のパスを強制する人々 (ハッカーなど) によって使用されます。このイベントは、プログラムがコード セクションに書き込む回数をカウントします。自己変更コードは、すべての Intel 64 および IA-32 プロセッサで重大なペナルティを引き起こします。変更されたキャッシュ ラインは、L2 および LLC キャッシュに書き戻されます。また、命令を再ロードする必要があるため、パフォーマンスが低下します。

そのようなイベントがいくつか見られると思います。そうである場合、CPU はコードを自己変更する行為を検出でき、「マシン クリア」を発生させました - パイプラインの完全な再起動。最初のステージは Fetch で、L2 キャッシュに新しいオペコードを要求します。コードの実行ごとの SMC イベントの正確な数に非常に興味があります。これにより、レイテンシに関する推定値が得られます.. (SMC は、1 ユニットが 1.5 CPU サイクルと想定されるいくつかのユニットでカウントされます - B.6.2.インテル最適化マニュアルの6)

インテルは「最後のリタイア命令の直後から再開する」と言っていることがわかるので、最後のリタイア命令はmov;になると思います。そしてあなたのNOPはすでにパイプラインにあります。しかし、SMC は mov の退職時に引き上げられ、nops を含むパイプラインのすべてを殺します。

この SMC によるパイプラインの再起動は安くはありません。Agner はOptimizing_assembly.pdfにいくつかの測定値を持っています- 「17.10 自己変更コード (すべてのプロセッサ)」(Core2/CoreiX はここでは PM のようなものだと思います):

コードを変更した直後にコードを実行した場合のペナルティは、P1 で約 19 クロック、PMMX で 31 クロック、PPro、P2、P3、PM で 150 ~ 300 クロックです。P4 は、コードを自己変更した後、トレース キャッシュ全体を消去します。80486 以前のプロセッサでは、コード キャッシュをフラッシュするために、変更中のコードと変更済みのコードの間でジャンプが必要です。...

自己変更コードは、優れたプログラミング手法とは見なされません。速度が大幅に向上し、変更されたコードが何度も実行されるため、自己変更コードを使用することによるデメリットを上回る場合にのみ使用してください。

ここでは、SMC 検出器を失敗させるために異なる線形アドレスを使用することをお勧めしました: https://stackoverflow.com/a/10994728/196561 - 実際のインテルのドキュメントを探してみます...実際の質問には今は答えられません。

ここにいくつかのヒントがあるかもしれません: Optimization manual, 248966-026, April 2012 "3.6.9 Mixing Code and Data":

コード セグメントに書き込み可能なデータを配置することは、自己変更コードと区別することが不可能な場合があります。コード セグメント内の書き込み可能なデータは、自己変更コードと同じパフォーマンス ペナルティを受ける可能性があります。

そして次のセクション

ソフトウェアは、実行中の同じ 1 KB サブページ内のコード ページへの書き込みや、書き込み中の同じ 2 KB サブページ内のコードのフェッチを避ける必要があります。さらに、直接または投機的に実行されたコードを含むページをデータ ページとして別のプロセッサと共有すると、マシンのパイプライン全体とトレース キャッシュがクリアされる SMC 条件がトリガーされる可能性があります。これは、自己変更コードの状態によるものです。

そのため、書き込み可能なサブページと実行可能なサブページの交差を制御する回路図がいくつかある可能性があります。

他のスレッドから変更を試みることもできます (相互変更コード) -- ただし、非常に慎重なスレッド同期とパイプライン フラッシュが必要です (ライター スレッドに総当たりの遅延を含めることをお勧めします; 同期の直後の CPUIDが望ましい)。しかし、彼らはすでに「核兵器」を使用してこれを修正していることを知っておく必要があります-US6857064特許を確認してください.

P6 および Pentium プロセッサについて言及しているマニュアルについて少し混乱しています

これは、インテルの取扱説明書の古いバージョンをフェッチ、デコード、および実行した場合に可能です。パイプラインをリセットして、このバージョンを確認できます:注文番号: 325462-047US、2013 年 6 月 「11.6 SELF-MODIFYING CODE」。このバージョンは、新しい CPU についてはまだ何も述べていませんが、異なる仮想アドレスを使用して変更している場合、動作がマイクロアーキテクチャ間で互換性がない可能性があることに言及しています (Nehalem/Sandy Bridge では機能し、Skymont では機能しない可能性があります)。

11.6 自己修正コード プロセッサに現在キャッシュされているコード セグメントのメモリ位置への書き込みにより、関連するキャッシュ ラインが無効になります。このチェックは、命令の物理アドレスに基づいています。さらに、P6 ファミリおよび Pentium プロセッサは、コード セグメントへの書き込みによって、実行のためにプリフェッチされた命令が変更される可能性があるかどうかをチェックします。書き込みがプリフェッチされた命令に影響を与える場合、プリフェッチ キューは無効になります。この後者のチェックは、命令の線形アドレスに基づいています。Pentium 4 および Intel Xeon プロセッサの場合、ターゲット命令が既にデコードされてトレース キャッシュに常駐しているコード セグメント内の命令の書き込みまたはスヌープは、トレース キャッシュ全体を無効にします。

実際には、リニア アドレスのチェックによって IA-32 プロセッサ間の互換性の問題が発生することはありません。自己変更コードを含むアプリケーションは、命令の変更とフェッチに同じ線形アドレスを使用します。

デバッガーなどのシステム ソフトウェアは、命令のフェッチに使用されたものとは異なる線形アドレスを使用して命令を変更する可能性があり、変更された命令が実行される前に、CPUID 命令などのシリアル化操作を実行します。これにより、自動的に再同期されます。命令キャッシュとプリフェッチ キュー。(自己変更コードの使用の詳細については、セクション8.1.3「自己変更コードと相互変更コードの処理」を参照してください。)

Intel486 プロセッサの場合、キャッシュ内の命令に書き込むと、キャッシュとメモリの両方で命令が変更されますが、命令が書き込み前にプリフェッチされた場合、古いバージョンの命令が実行される可能性があります。古い命令が実行されないようにするには、命令を変更する書き込みの直後にジャンプ命令をコーディングして、命令プリフェッチ ユニットをフラッシュします。

REAL Update「SMC 検出」 (引用符付き) でググると、最新の Core2/Core iX が SMC を検出する方法の詳細と、SMC 検出器に Xeon と Pentium がぶら下がっている多くのエラータ リストがあります。

  1. http://www.google.com/patents/US6237088パイプラインで実行中の命令を追跡するためのシステムおよび方法 @ 2001

  2. DOI 10.1535/itj.1203.03 (Google で調べてください。citeseerx.ist.psu.edu に無料バージョンがあります) - Penryn に「INCLUSION FILTER」が追加され、誤った SMC 検出の数が減りました。「既存の介在物検出メカニズム」は図9に描かれています

  3. http://www.google.com/patents/US6405307 - SMC 検出ロジックに関する古い特許

米国特許第 6237088 号 (FIG5、概要) によると、「ライン アドレス バッファー」があります (フェッチされた命令ごとに 1 つのアドレスを持つ多くの線形アドレス、つまりキャッシュ ライン精度でフェッチされた IP でいっぱいのバッファー)。すべてのストア、またはすべてのストアのより正確な「ストア アドレス」フェーズは、現在実行中の命令のいずれかと交差するかどうかを確認するために並列コンパレータにフィードされます。

両方の特許は、SMC ロジックで物理アドレスまたは論理アドレスを使用するかどうかを明確に述べていません... Sandy ブリッジの L1i は VIPT (仮想インデックス、物理タグ、インデックスの仮想アドレス、およびタグ内の物理アドレス) です。httpによると://nick-black.com/dankwiki/index.php/Sandy_Bridgeしたがって、L1 キャッシュがデータを返すときの物理アドレスを取得できます。インテルは、SMC 検出ロジックで物理アドレスを使用する可能性があると思います。

さらに、http://www.google.com/patents/US6594734 @ 1999 (2003 年に公開されました。CPU の設計サイクルは約 3 ~ 5 年であることを覚えておいてください) の「概要」セクションで、SMC は現在 TLB にあり、物理アドレス (つまり、SMC 検出器をだまそうとしないでください):

自己変更コードは、メモリへのストアの物理メモリ アドレスを使用してスヌープを実行できる物理ページ アドレスが格納されている変換ルックアサイド バッファを使用して検出されます。... アドレスのページよりも細かい粒度を提供するために、キャッシュ内の情報をメモリ内のページの部分に関連付けるキャッシュ内の各エントリに FINE HIT ビットが含まれています。

(特許 US6594734 で象限と呼ばれるページの一部は、1K サブページのように聞こえますね?)

それから彼らは言います

したがって、スヌープは、命令をメモリに格納することによってトリガーされ、命令キャッシュ内に格納されているすべての命令の物理アドレスを、メモリの関連するページまたは複数のページ内に格納されているすべての命令のアドレスと比較することによって、SMC 検出を実行できます。アドレスが一致する場合は、メモリ位置が変更されたことを示します。SMC 状態を示すアドレス一致の場合、命令キャッシュと命令パイプラインはリタイアメント ユニットによってフラッシュされ、新しい命令がメモリからフェッチされて命令キャッシュに格納されます。

SMC 検出のスヌープは物理的なものであり、ITLB は通常、物理アドレスに変換する線形アドレスを入力として受け入れるため、ITLB は物理アドレスの内容アドレス指定可能なメモリとして追加的に形成され、追加の入力比較ポート (参照される) を含みます。スヌープ ポートまたは逆変換ポートとして)

-- そのため、SMC を検出するために、snoop の phys の場合、ストアが物理アドレスをスヌープ経由で命令バッファーに転送するように強制します (同様のスヌープが他のコア/CPU から、または DMA 書き込みからキャッシュに配信されます....)。アドレスがキャッシュ ラインと競合し、命令バッファーに格納されている場合、iTLB からリタイアメント ユニットに配信される SMC 信号を介してパイプラインを再起動します。dTLB から iTLB を介してリタイアメント ユニットまでのスヌープ ループでどれだけの CPU クロックが浪費されるか想像できます (次の「nop」命令はリタイアできませんが、mov よりも早く実行され、副作用はありません)。しかし、何?ITLB には物理アドレス入力と 2 番目の CAM (大きくてホット) があり、クレイジーで不正な自己変更コードをサポートして防御します。

PS: では、ヒュージ ページ (4M または 1G の場合もあります) を使用する場合はどうなりますか? L1TLB には巨大なページ エントリがあり、4 MB ページの 1/4 に対して誤った SMC 検出が多数発生する可能性があります...

PPS: 初期の P6/Ppro/P2...

于 2013-06-30T23:17:26.147 に答える
11

Intel のマニュアルから、命令をメモリに書き込むことは可能であると言われ、読みましたが、命令プリフェッチ キューは古い命令を既にフェッチしている [可能性がある] ため、それらの古い命令を実行 [する可能性があります] です。私はこの行動を観察できませんでした。

はい、そうです。

すべてまたはほとんどすべての最新の Intel プロセッサは、マニュアルよりも厳格です。

線形だけでなく、物理アドレスに基づいてパイプラインをスヌープします。

プロセッサの実装は、マニュアルよりも厳密にすることができます。

彼らは、マニュアルのルールに準拠していないコードに遭遇したことがあり、破りたくないという理由でそうすることを選択する場合があります。

または...アーキテクチャ仕様に準拠する最も簡単な方法(SMCの場合、公式には「次のシリアル化命令まで」でしたが、実際には、レガシーコードの場合は「次の分岐まで」でした。は ??? バイト以上離れています」) はより厳密になる可能性があります。

于 2013-08-22T18:55:39.280 に答える