なんだろう、何百番煎じな気がする。もうお茶も出なくなってお湯ですよ、お湯。
フォロワーの @pasberth さんが、「JIT ってどういう仕掛けになってるの!」といっていたので、
メモリ上にバイトコードを置いて実行する方法ということでちょろっと書いたものです。
とりあえず、ピタゴラスの定理をアセンブラで書いて、
C でぺけぺけ。
ひとまず、私の作業環境である Windows での動作確認。
そいでもって、x86 なら、どのプラットフォームでもバイナリ部分は変えなくても走るよ! ってのを示すために、
Mac を立ち上げて実行。
ソースコードは下記のような感じです。@pasberth さん用に書いたので、Mac 向けソースになってます
Windows で動かすには、先頭の vm_protect を VirtualProtect の形式に書き換えれば動きます。
#include <mach/mach.h> #include <stdio.h> /************************************************ とりあえず、中のデータは16進で記録してあって、 リトルエンディアンですから、 0x01020304 は、メモリ上に 0x04,0x03,0x02,0x01 の順で格納されます。 要は逆順です。 また、命令のオーダーについては、必ずしも何バイトということはありませんが、 最長命令は 32bit アーキテクチャですんで 4 バイト ( 32 bit / 8 = 4 byte. ) 見やすくするために、2バイトや1バイト命令に関しては、 0x90 (NOP) [NOP = なにもしない] を挿入して、アライメント(区切り)を合わせてあります。 まー、適当に見てもらえれば。 ************************************************/ int main(){ unsigned long d; int a = 0,b = 0,c = 0; unsigned long code[32] = {0}; printf("vm_protect : %s\n\n",vm_protect( mach_task_self(), (vm_address_t)code,32 * sizeof(long), FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE ) == KERN_SUCCESS ? "[OK]" : "[FALSE]"); printf("INPUT [a,b,c] : "); scanf("%d,%d,%d",&a,&b,&c); code[0] = 0x0424448B; // MOV EAX,DWORD PTR SS:[ESP+4] code[1] = 0x08244C8B; // MOV ECX,DWORD PTR SS:[ESP+8] code[2] = 0x9090C13B; // CMP EAX,ECX code[3] = 0x9090047E; // JLE SHORT ; EIP+4 code[4] = 0x9090C88B; // MOV ECX,EAX code[5] = 0x0C24548B; // MOV EDX,DWORD PTR SS:[ESP+C] code[6] = 0x9090CA3B; // CMP ECX,EDX code[7] = 0x9090147E; // JLE SHORT ; EIP+C code[8] = 0x90909056; // PUSH ESI code[9] = 0x9090F28B; // MOV ESI,EDX code[10] = 0x9090D18B; // MOV EDX,ECX code[11] = 0x9090CE8B; // MOV ECX,ESI code[12] = 0x9090905E; // POP ESI code[13] = 0x90C0AF0F; // IMUL EAX,EAX code[14] = 0x90C9AF0F; // IMUL ECX,ECX code[15] = 0x90D2AF0F; // IMUL EDX,EDX code[16] = 0x9090C103; // ADD EAX,ECX code[17] = 0x9090C22B; // SUB EAX,EDX code[18] = 0x9090D8F7; // NEG EAX code[19] = 0x9090C01B; // SBB EAX,EAX code[20] = 0x90909040; // INC EAX code[21] = 0x909090C3; // RETN printf("\nRETURN : %s\n", ((int(*)(int,int,int))code)(a,b,c) == 0 ? "[FALSE]" : "[OK]"); return 0; }
中のバイトコード部分は、ソースコード中のコメントを見てもらえれば。
本来、こういうページ領域上にバイトコードを置いて、それを呼び出そうとしても、
実行できないように設定されています。
(実行できるようになっていると、プロセスに脆弱性があったとき、悪意のあるコードを攻撃者から送信されて、実行されてしまう危険性が格段に上がる)
ですが、さすがに完全に実行できないと、Java VM をはじめとしたソフトウェアが稼働できなくなってしまうため、
OSから実行可能にフラグを書き換えるAPIが提供されています。
OS |
関数 |
Windows | VirtualProtect MSDN : http://msdn.microsoft.com/ja-jp/library/cc430214.aspx |
Mac OS X | vm_protect The GNU Mach Reference Manual : http://www.gnu.org/software/hurd/gnumach-doc/Memory-Attributes.html Darwin : http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/vm_protect.html |
Linux | mprotect Man page of MPROTECT : http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/mprotect.2.html |
実行可能なバイトコードは、x86 プロセッサ共通なので、同じプロセッサならどれでも動きますが、
各オペレーティングシステムに合わせて、上記に挙げた関数を利用して、実行可能に設定する必要があります。
実際の JIT コンパイラなどでは、各プラットフォームごとのライブラリや、
APIの差異の吸収が必要になって、結構面倒なことになりますが、
今回の例はきわめて単純な例を使って、 x86 プロセッサ上なら同じコードが動くということを示すのと同時に、
Java などでは、こういう感じでたぶんコードを実行しているということで。
(mono はこれ使ってる感濃厚。 ソースコードを眺めていた感じでは。