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 i386gcc (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はその気になればこんなことさえできてしまうという,とてもおもしろい例だと思います.