Cで配列に格納した命令を実行できる話
Cでは配列に命令を格納していくと,そのまま実行できるという話をチラ聞きしたので試してみた.
Cではメモリに記録された内容が何物であるかというのは,当該領域を参照する変数の型によって決定されるので,次のようなことが可能です.
int num = 10; float *rnum = (float *)# fprintf("%f\n", *rnum); /* => ??? */
大半の処理系では,intとfloatのサイズは同じく4バイトなので,このコードはコンパイルでき,実行できます. しかし,int(整数)とfloat(浮動小数点数)ではメモリ上の数の表現方法が異なるので,評価結果は'10'とはなりません.
ここで重要なのは,Cにおいて,プログラマが指定する型は絶対的だということです.あるメモリ上に存在するnバイトのデータは,プログラマがintだと言えばintだし,floatだと言えばfloatだし,charの配列だと言えばcharの配列なわけです.
これを利用することで,次のような事が可能です.(Gentoo 32bit on i386,gcc (Gentoo 4.6.3 p1.9, pie-0.5.2)で動作確認.他の環境ではたぶん動きません)
#include <stdio.h> #include <stdlib.h> int main(void) { char *plus; int (*plusop)(int, int); plus = malloc(13); if (plus == NULL) abort(); plusop = (void *)plus; plus[ 0] = 0x55; /* push %ebp */ plus[ 1] = 0x89; plus[ 2] = 0xe5; /* mov %esp,%ebp */ plus[ 3] = 0x8b; plus[ 4] = 0x45; plus[5] = 0x0c; /* mov 0xc(%ebp),%eax */ plus[ 6] = 0x8b; plus[ 7] = 0x55; plus[8] = 0x08; /* mov 0x8(%ebp),%edx */ plus[ 9] = 0x01; plus[10] = 0xd0; /* add %edx,%eax */ plus[11] = 0x5d; /* pop %ebp */ plus[12] = 0xc3; /* ret */ printf("6 + 9 = %d\n", plusop(6, 9)); free(plus); return 0; }
上のコードでは,mallocで確保した13byteの領域に,8個のx86命令を格納しています.これらの命令は,二つの引数を足して返す関数として機能します.この配列の先頭アドレスを,int (int, int)型の関数ポインタに入れてやり,普通の関数ポインタよろしく'()'を付けてやることで,関数として実行できます.つまりプログラマが,mallocで確保したアドレスに格納されているのは,関数として妥当な命令のリストだと明示したことにより,Cは忠実にそれを信じて実行してくれたわけです.
もちろん出来るというだけで実用性はまったくありませんが,Cはその気になればこんなことさえできてしまうという,とてもおもしろい例だと思います.