Best abuse of the rules

ルールの最高の悪用

受賞者:Fabrice Bellard

引用元:https://www.ioccc.org/2001/bellard.c

審査員・作者による説明:https://github.com/ioccc-src/winner/blob/main/2001/bellard.hint

動作

セルフホスト可能なC言語のサブセットのコンパイラ。 次は、添付されているbellard.otccex.cから一部だけ抜粋したコードfib.cを動かす例。

$ gcc -m32 -rdynamic bellard.c -o bellard -ldl

$ cat fib.c
fib(n) {
    if (n <= 2)
        return 1;
    else
        return fib(n-1) + fib(n-2);
}
main(argc, argv) {
    int n;
    n = atoi(*(int *)(argv + 4));
    printf("fib(%d) = %d\n", n, fib(n));
    return 0;
}

$ ./bellard fib.c 10
fib(10) = 55

./bellard fib.c 10はfib.cをコンパイルして実行している。 見かけ上は、C言語インタプリタのようになる。 セルフホストは最後に示す。

解説

C言語のソースコードを読んで機械語(x86)に変換し、その機械語を実行する。 ただし、実行可能ファイルを出力するのではなく、メモリ内に命令列を構築し、その中のmain関数をcallして実行する。 たとえば./bellard foo.cと実行すると、foo.cをメモリ内にコンパイルし、その結果を実行するので、見かけの挙動はC言語インタプリタのようになっている。 実行ファイルにするにはELFフォーマットを作る必要があるので、コードサイズ制限に合わなかったか。

コード形状による難読化はないが、サイズ制限に収まっているだけでもすごい。

x86しかサポートしていないので、-m32が必要。 値とポインタがともに4バイトであることを強く仮定したコードになっているので、-m32なしで動かすのはとても大変だと思う。 また、calloc関数で得たメモリに機械語を構築してcallするが、現代ではこのようなメモリは実行フラグが立っていない(実行保護を参照のこと)ので、mmapでメモリ確保するようにした。

作者自身が解説サイトを作っているので興味あれば参照されたい。 最近、難読化されていないバージョンも公開された。 なお作者(Fabrice Bellard)はその後、この作品を元にtccを作っている。

残念ながら、現代の環境ではfibまでは動かせたが、bellard.otccex.cを完全動作させることができなかった。 詳しくは調べていないが、関数ポインタが動いていないような気がする。 腕に自信のあるバイナリアンに解析してもらいたい。


当時のOS環境であるDebian 2.2 (potato)をQEMUでセットアップしたところ、bellard.otccex.cだけでなくセルフホストまで動作確認できた。

セルフホストができている様子
図:セルフホストができている様子

./bellard bellard.otccex.c 10はbellard.otccex.cをコンパイルして動かしているだけ。 ./bellard bellard.c bellard.otccex.c 10はbellard.cをコンパイルしてコンパイラを作り、その新しいコンパイラでbellard.otccex.cをコンパイルして実行する。 ./bellard bellard.c bellard.c bellard.otccex.c 10はコンパイラのコンパイルをもう一段階増やしている。

パッチ

パッチをダウンロード