2005年10月25日

普通のやつらの下を行け: Cで動的コード生成・実行

スクリプト言語には動的にコードを生成して実行する機能を持ったものが多くあります。 普通のやつらの下を行けの第3回として、今回は C による動的なコード生成と実行に取り組んでみたいと思います。

 

今回書いたコードの main() 関数は以下のようなものです。

int
main(int argc, char **argv)
{
    assert(argc == 2);

    define(int, add, (int x, int y), "{ return x + y;  }");
    define(int, mul, (int x, int y), "{ return x * y;  }");
    define(int, add_argv1, (int x),  "{ return x + %d; }", atoi(argv[1]));

    printf("%d\n", add(100, 50));    // 150
    printf("%d\n", mul(100, 50));    // 5000
    printf("%d\n", add_argv1(200));  // 200 + atoi(argv[1])

    return 0;
}

define(...) というあやしい行で動的に関数を定義して、後半の printf のところでそれらを呼び出しています。 add と mul という関数は静的な文字列定数で定義されていますが、3つめの add_argv1 は argv[1] を用いて動的にコードを生成しているため、実行時にならないと関数の動作が定まりません。つまり、 add_argv1 を呼び出すためには動的に生成したコードを実行する仕掛けが必要になります。

このコードに足りない部分を補ってを実行すると、コメントにある通り、 150, 5000, および 200 に 1つめのコマンドライン引数を足した数 (たとえば ./a.out 56 で実行すれば 256) が表示されます。それでは、どのようなコードを補うか見ていきましょう。

実装

動的なコード生成と実行を C で実現するにあたって、ここでは次の方法を取りました。 環境は Debian GNU/Linux sarge です。

  • gccを呼び出してコードを .so ファイルにコンパイル
  • dlopen で .so ファイルを動的にリンク
  • コード生成の部分は C99 の可変長マクロを利用

これらの素材を使って足りない部分を補うと全体のコードは次のようになります。

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <sys/wait.h>

static void *
compile(const char *code)
{
    static int count = 0;
    char *c_file_name, *so_file_name, *gcc_command_line;

    count++;
    asprintf(&c_file_name, "tmp-%d.c", count);
    asprintf(&so_file_name, "./tmp-%d.so", count);
    asprintf(&gcc_command_line, "gcc -fPIC -shared -o %s %s",
             so_file_name, c_file_name);

    FILE *fp = fopen(c_file_name, "w");
    assert(fp != NULL);
    fwrite(code, 1, strlen(code), fp);
    fclose(fp);

    int status = system(gcc_command_line);
    assert(WEXITSTATUS(status) == 0);

    void *handle = dlopen(so_file_name, RTLD_LAZY);
    assert(handle != NULL);

    free(c_file_name);
    free(so_file_name);
    free(gcc_command_line);
    return handle;
}

static char *global_code;
static char *global_body;
static void *global_handle;
#define define(retval_type, func_name, parameters, body, ...)          \
    asprintf(&global_body, body,  ## __VA_ARGS__);                     \
    asprintf(&global_code, "%s func %s { %s }\n",                      \
             #retval_type, #parameters, global_body);                  \
    global_handle = compile(global_code);                              \
    free(global_body);                                                 \
    free(global_code);                                                 \
    retval_type (*func_name)parameters = dlsym(global_handle, "func"); \
    assert(func_name != NULL);

int
main(int argc, char **argv)
{
    assert(argc == 2);

    define(int, add, (int x, int y), "{ return x + y;  }");
    define(int, mul, (int x, int y), "{ return x * y;  }");
    define(int, add_argv1, (int x),  "{ return x + %d; }", atoi(argv[1]));

    printf("%d\n", add(100, 50));    // 150
    printf("%d\n", mul(100, 50));    // 5000
    printf("%d\n", add_argv1(200));  // 200 + atoi(argv[1])

    return 0;
}

相当強引ですが、意外とあっけなくできました。コンパイル方法と実行結果は以下の通りです。

% gcc jit.c  -ldl
% ./a.out 56
150
5000
256

まとめ

「普通のやつらの下を行け」という企画の割には今回は低レベルな技術はあまり使わずに、 力技だけで終わってしまいました。動的に生成したコードをコンパイルして動的リンクする手法については Linkers & Loaders の第10章で以下のように言及されています。

動的ロードによってプログラムが自分自身を拡張できる。 プログラム自体が C や C++ でルーチンを書き、コンパイラとリンカを実行して共有ライブラリを作成して、新しいコードを動的にロードして実行することも考えられる。

今回の手法を発展させれば、ウェブページへの初回アクセス時に .so にコンパイルして次回からネイティブコードを走らせるという、 JSP ならぬ CSP (C Server Pages) を作れるのではないかと思います。…と思ったら 既にありました

謝辞

define() のインタフェースは yaegashi さんによる数多くの助言に基づいています。ありがとうございます。