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 さんによる数多くの助言に基づいています。ありがとうございます。