2012-01-26 7 views
8

私は自分のJITインタープリタをVM上のコースの一部として書くつもりです。私は高水準の言語、コンパイラ、インタプリタについて多くの知識を持っていますが、x86アセンブリ(またはそれに関してはC)に関する知識はほとんどまたはまったくありません。私は自分自身のJITインタープリタを書いています。生成された命令をどのように実行すればよいですか?

実際、私はJITの仕組みが分かりませんが、ここでは私の取り組みです:プログラムでいくつかの中間言語を読んでください。 x86命令にコンパイルしてください。最後の命令がVMコードのどこかに戻っていることを確認してください。命令をメモリのどこに格納しますか。最初の命令に無条件にジャンプします。 Voila!

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

int main() { 
    int *m = malloc(sizeof(int)); 
    *m = 0x90; // NOP instruction code 

    asm("jmp *%0" 
       : /* outputs: */ /* none */ 
       : /* inputs: */ "d" (m) 
       : /* clobbers: */ "eax"); 

    return 42; 

}

さて、このプログラムがメモリ内のどこかにNOP命令を格納するためので、私の意図がある、ジャンプ:

だから、このことを念頭に置いて、私は次の小さなCプログラムを持っています(私はプログラムをメインに戻すための方法を設定していないので)おそらくクラッシュします。

質問:私は正しい道にいるのですか?

質問:メインのどこかに戻る方法を見つけ出すために変更されたプログラムを表示できますか?

質問:その他の注意すべき点はありますか?

PS:私の目標は理解を得ることであり、必ずしもすべてを正しい方法で行うというわけではありません。


すべてのフィードバックありがとうございます。次のコードを開始する場所のようですし、私のLinuxマシン上で動作します:

#include <stdlib.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <sys/mman.h> 

unsigned char *m; 

int main() { 
     unsigned int pagesize = getpagesize(); 
     printf("pagesize: %u\n", pagesize); 

     m = malloc(1023+pagesize+1); 
     if(m==NULL) return(1); 

     printf("%p\n", m); 
     m = (unsigned char *)(((long)m + pagesize-1) & ~(pagesize-1)); 
     printf("%p\n", m); 

     if(mprotect(m, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) { 
       printf("mprotect fail...\n"); 
       return 0; 
     } 

     m[0] = 0xc9; //leave 
     m[1] = 0xc3; //ret 
     m[2] = 0x90; //nop 

     printf("%p\n", m); 


asm("jmp *%0" 
        : /* outputs: */ /* none */ 
        : /* inputs: */ "d" (m) 
        : /* clobbers: */ "ebx"); 

     return 21; 
} 
+0

別のオプションは、単に/ wの命令または中間コードを解釈することですo何かを直接実行する。 –

+1

@Alex:それは言語を実装する別のオプションですが、定義上、JITではありません。 –

答えて

8

質問:私は正しい道にいるだろうか?

私ははいと言います。

質問:メインのどこかに戻る方法を見つけ出すために変更されたプログラムを表示できますか?

私はあなたのための任意のコードを持っていないが、生成されたコードとバックを取得する良い方法は、彼らが自動的にリターンアドレスを管理するように、call/ret命令のペアを使用することです。

質問:その他の注意すべき点はありますか?

はい - 多くのオペレーティングシステムでは、特別な手配をせずにヒープ上でコードを実行することができません。これらの特別な手配は、通常、関連するメモリページに実行可能とマークする必要があります。

これは、mprotect()PROT_EXECを使用して行われます。

+1

さらに、命令キャッシュは一般に基本メモリを監視しないため、ジャンプを実行する前に明示的なキャッシュフラッシュが必要な場合があります。 –

+1

@サイモン:合意されています。一般的には「命令をメモリに書き終えた後、実行する前に」です。私の経験では、実行する直前ではなく、書き終わった直後に行うコードを書いています。このコード例では同じ場所ですが、実際には書くよりも多くの時間を実行することがあります。 Simonが指摘するように、データキャッシュではなく命令キャッシュをフラッシュすることが重要です。 –

+0

x86ではIキャッシュをフラッシュする必要はありませんが、SMCはeipに非常に近い場合にのみ動作しません。中程度に近いSMCは、大きな罰則を課す。 – harold

3

あなたの生成されたコードが正しいcalling conventionを以下の場合は、あなたが関数へのポインタ型を宣言し、機能をこのように呼び出すことができます。

typedef void (*generated_function)(void); 

void *func = malloc(1024); 
unsigned char *o = (unsigned char *)func; 
generated_function *func_exec = (generated_function *)func; 

*o++ = 0x90;  // NOP 
*o++ = 0xcb;  // RET 

func_exec(); 
関連する問題