2

C と MIPS で自己修正コードを書こうとしています。

後でコードを変更したいので、(インライン アセンブリではなく) 実際のマシン命令を記述して、それらの命令を実行しようとしています。誰かが私に、メモリを malloc し、そこに命令を書き込んで、C 関数ポインタをそこに向けてからジャンプすることができるだろうと言いました。(以下に例を含めます)

私はこれを私のクロス コンパイラ (sourcery codebench ツールチェーン) で試しましたが、うまくいきません (はい、後から考えるとかなりナイーブだと思います)。どうすればこれを適切に行うことができますか?

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>


void inc(){
    int i = 41;
    uint32_t *addone = malloc(sizeof(*addone) * 2); //we malloc space for our asm function
    *(addone) = 0x20820001; // this is addi $v0 $a0 1, which adds one to our arg (gcc calling con)
    *(addone + 1) = 0x23e00000; //this is jr $ra

    int (*f)(int x) = addone; //our function pointer
    i = (*f)(i);
    printf("%d",i);    
}

int main(){
    inc();
exit(0);}

ここでは、引数が $a0 に渡され、関数の結果が $v0 にあると想定される gcc 呼び出し規約に従います。戻りアドレスが $ra に入れられるかどうかは実際にはわかりません (ただし、コンパイルできないため、まだテストできません。MIPS32 をコンパイルしているため、命令には int を使用します (したがって、32 ビットの int十分なはずです)

4

4 に答える 4

2

記述された OP のコードは、Codesourcery mips-linux-gnu-gcc でエラーなしでコンパイルされます。

他の人が上で述べたように、MIPS の自己修正コードでは、コードが書き込まれた後に命令キャッシュをデータ キャッシュと同期させる必要があります。MIPS アーキテクチャの MIPS32R2 バージョンでは、ここで必要なことを実行するユーザー モード命令であるSYNCI 命令が追加されました。最新の MIPS CPU はすべて、MIPS32R2 を実装していSYNCIます。

メモリ保護は MIPS のオプションですが、ほとんどの MIPS CPU はこの機能が選択された状態で構築されていないため、ほとんどの実際の MIPS ハードウェアでは mprotect システム コールを使用する必要はありません。

コンパイラ以外-O0の最適化を使用すると、ストア*addoneと関数呼び出しが最適化され、コードが壊れることに注意してください。キーワードを使用volatileすると、コンパイラはこれを実行できなくなります。

次のコードは正しい MIPS アセンブリを生成しますが、テストするのに便利な MIPS ハードウェアがありません。

int inc() {
    volatile int i = 41;
    // malloc 8 x sizeof(int) to allocate 32 bytes ie one cache line,
    // also ensuring that the address of function addone is aligned to
    // a cache line.
    volatile int *addone = malloc(sizeof(*addone) * 8);
    *(addone)     = 0x20820001; // this is addi $v0 $a0 1
    *(addone + 1) = 0x23e00000; //this is jr $ra
    // use a SYNCI instruction to flush the data written above from
    // the D cache and to flush any stale data from the I cache
    asm volatile("synci 0(%0)": : "r" (addone));
    volatile int (*f)(int x) = addone; //our function pointer
    int j = (*f)(i);
    return j;
}

int main(){
    int k = 0;
    k = inc();
    printf("%d",k);    
    exit(0);
}
于 2012-11-01T02:26:36.213 に答える
2

ポインターを不適切に使用しています。または、より正確に言えば、あるべき場所でポインターを使用していません。

これを試着してサイズを確認してください:

uint32_t *addone = malloc(sizeof(*addone) * 2);
addone[0] = 0x20820001; // addi $v0, $a0, 1
addone[1] = 0x23e00000; // jr $ra

int (*f)(int x) = addone; //our function pointer
i = (*f)(i);
printf("%d\n",i);

メモリに書き込んだ後、呼び出す前に、メモリを実行可能として設定する必要がある場合もあります。

mprotect(addone, sizeof(int) * 2, PROT_READ | PROT_EXEC);

これを機能させるには、さらにかなり大きなメモリ ブロック (4k 程度) を割り当てて、アドレスがページ アラインされるようにする必要がある場合があります。

于 2012-10-31T16:57:16.083 に答える
2

また、問題のメモリが実行可能であることを確認し、書き込み後に dcache から適切にフラッシュされ、実行前に icache にロードされることを確認する必要があります。これを行う方法は、mips マシンで実行されている OS によって異なります。

Linux では、mprotect システム コールを使用してメモリを実行可能にし、cacheflush システム コールを使用してキャッシュをフラッシュします。

編集

例:

#include <unistd.h>
#include <sys/mman.h>
#include <asm/cachecontrol.h>

#define PALIGN(P)  ((char *)((uintptr_t)(P) & (pagesize-1)))
uintptr_t  pagesize;

void inc(){
    int i = 41;
    uint32_t *addone = malloc(sizeof(*addone) * 2); //we malloc space for our asm function
    *(addone) = 0x20820001; // this is addi $v0 $a0 1, which adds one to our arg (gcc calling con)
    *(addone + 1) = 0x23e00000; //this is jr $ra

    pagesize = sysconf(_SC_PAGESIZE);  // only needs to be done once
    mprotect(PALIGN(addone), PALIGN(addone+1)-PALIGN(addone)+pagesize,
             PROT_READ | PROT_WRITE | PROT_EXEC);
    cacheflush(addone, 2*sizeof(*addone), ICACHE|DCACHE);

    int (*f)(int x) = addone; //our function pointer
    i = (*f)(i);
    printf("%d",i);    
}

コードを含むページ全体を書き込み可能かつ実行可能にすることに注意してください。これは、メモリ保護がページごとに機能し、malloc が残りのページを他の目的で引き続き使用できるようにするためです。代わりにvallocormemalignを使用してページ全体を割り当てることもできます。その場合、コードを読み取り専用で安全に実行できます。

于 2012-10-31T17:00:45.383 に答える
1

関数の呼び出しは、単に命令にジャンプするよりもはるかに複雑です。

  • 引数はどのように渡されますか? それらはレジスタに格納されていますか、それともコールスタックにプッシュされていますか?

  • 値はどのように返されますか?

  • リターン ジャンプのリターン アドレスはどこにありますか? 再帰関数がある場合は、$raそれをカットしません。

  • 呼び出された関数が完了したときにスタック フレームをポップするのは、呼び出し元または呼び出し先のどちらですか?

呼び出し規約が異なれば、これらの質問に対する答えも異なります。私はあなたがやっていることのようなことを試したことはありませんが、規則に一致するようにマシンコードを記述し、関数ポインターがその規則を使用することをコンパイラーに伝える必要があると思います (異なるコンパイラーには異なる方法があります)これ - gcc は関数属性でそれを行います)。

于 2012-10-31T16:30:11.960 に答える