24

私は例が好きなので、C言語で自己修正コードを少し書きました...

#include <stdio.h>
#include <sys/mman.h> // linux

int main(void) {
    unsigned char *c = mmap(NULL, 7, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|
                            MAP_ANONYMOUS, -1, 0); // get executable memory
    c[0] = 0b11000111; // mov (x86_64), immediate mode, full-sized (32 bits)
    c[1] = 0b11000000; // to register rax (000) which holds the return value
                       // according to linux x86_64 calling convention 
    c[6] = 0b11000011; // return
    for (c[2] = 0; c[2] < 30; c[2]++) { // incr immediate data after every run
        // rest of immediate data (c[3:6]) are already set to 0 by MAP_ANONYMOUS
        printf("%d ", ((int (*)(void)) c)()); // cast c to func ptr, call ptr
    }
    putchar('\n');
    return 0;
}

...明らかに動作します:

>>> gcc -Wall -Wextra -std=c11 -D_GNU_SOURCE -o test test.c; ./test
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

しかし、正直なところ、私はそれがうまくいくとはまったく予想していませんでした。への最初の呼び出しで を含む命令c[2] = 0がキャッシュされることを期待していましたc。その後、への連続したすべての呼び出しは、へcの繰り返しの変更を無視しますc(何らかの方法で明示的にキャッシュを無効にしない限り)。幸いなことに、私の CPU はそれよりも優れているようです。

CPUcは、命令ポインタが(上記のmmapされたメモリへの呼び出しと同様に)大規模なジャンプを行うたびにRAM(RAMに存在すると仮定しても)を命令キャッシュと比較し、一致しない場合はキャッシュを無効にすると思います(すべて?)、しかし、私はそれについてより正確な情報を得ることを望んでいます. 特に、この動作が予測可能 (ハードウェアと OS の違いを除いて) であると見なされ、信頼できるかどうかを知りたいですか?

(おそらく Intel のマニュアルを参照する必要がありますが、それは何千ページにもわたる長さであり、迷子になりがちです...)

4

5 に答える 5

26

あなたが行うことは、通常、自己変更コードと呼ばれます。Intel のプラットフォーム (そしておそらく AMD のプラットフォームも) は、 i/d キャッシュの一貫性を維持する仕事をしてくれます。マニュアルが指摘しているように ( Manual 3A, System Programming )

11.6 自己修正コード

プロセッサに現在キャッシュされているコード セグメントのメモリ位置への書き込みにより、関連するキャッシュ ラインが無効になります。

ただし、このアサーションは、変更とフェッチに同じ線形アドレスが使用されている限り有効です。デバッガーバイナリーローダーは同じアドレス空間で実行されないため、これは当てはまりません。

自己変更コードを含むアプリケーションは、命令の変更とフェッチに同じ線形アドレスを使用します。デバッガーなどのシステム ソフトウェアは、命令のフェッチに使用されたものとは異なる線形アドレスを使用して命令を変更する可能性があり、変更された命令が実行される前に、CPUID 命令などのシリアル化操作を実行します。これにより、自動的に再同期されます。命令キャッシュとプリフェッチ キュー。

たとえば、シリアル化操作は、明示的に実行する必要がある PowerPC などの他の多くのアーキテクチャによって常に要求されます ( E500 Core Manual )。

3.3.1.2.1 自己変更コード

プロセッサが命令を含む可能性のあるメモリ位置を変更する場合、ソフトウェアは、命令キャッシュがデータメモリと一致するようにし、変更が命令フェッチメカニズムに見えるようにする必要があります。これは、キャッシュが無効になっている場合や、ページがキャッシュ禁止とマークされている場合でも実行する必要があります。

キャッシュが無効になっている場合でも、PowerPC ではコンテキスト同期命令を発行する必要があることに注意してください。ロード/ストアバッファなどのより深いデータ処理ユニットのフラッシュを強制すると思われます。

あなたが提案したコードは、スヌーピング機能や高度なキャッシュ コヒーレンシ機能を備えていないアーキテクチャでは信頼できないため、失敗する可能性があります。

この助けを願っています。

于 2012-06-12T10:25:01.057 に答える
6

とても簡単です。命令キャッシュ内のキャッシュ ラインの 1 つにあるアドレスへの書き込みは、命令キャッシュからそのアドレスを無効にします。「同期」は関係ありません。

于 2012-06-12T01:15:53.723 に答える
4

ちなみに、多くの x86 プロセッサ (私が取り組んだもの) は、命令キャッシュだけでなく、パイプライン、命令ウィンドウ (現在実行中の命令) もスヌープします。そのため、自己変更コードは次の命令で有効になります。ただし、新しく作成したコードが確実に実行されるように、CPUID などのシリアル化命令を使用することをお勧めします。

于 2013-08-22T18:49:56.680 に答える
4

CPU はキャッシュの無効化を自動的に処理します。手動で何もする必要はありません。ソフトウェアは、ある時点で何が CPU キャッシュにあるかどうかを合理的に予測することはできないため、これを処理するのはハードウェア次第です。CPU は、ユーザーがデータを変更したことを確認すると、それに応じてさまざまなキャッシュを更新しました。

于 2012-06-12T01:15:57.433 に答える