AJAJA: Systemモジュールがロードされるまでの制御フローの解析

AJAJAを実際に走らせてみて、AJAJAの動作をもう少し詳しく知りたくなりました。特に、AJAJACGIを実現するのに利用されているSystemモジュールが、どのようにしてユーザーランドから利用できるようにしているのか、興味を持ちました。そこで今回は、Systemモジュールが、実行コンテキストにロードされるまでの制御フローを解析してみたいと思います。今回、解析するソースコードはrev #30を用いることにします。

解析方法

ソースコードの流れを理解する方法として、

の2つがあるかと思います。

今回は、両方のアプローチを用いて、ソースコードの解析をしていきました。

まず、動的な方法で解析するために、ssjs単体をビルドする方法を調べました。

ソースコードを読んで試行錯誤した結果、以下のコマンドでssjs単体をビルドできることがわかりました。makeの引数に「BUILD_OPT=1」を指定すると、ビルドされるssjsの実行バイナリが最適化されます。

% cd ajaja-build/bin/mozilla/js/src
% make -f Makefile.ref BUILD_OPT=1

ちなみに、asp_jsとssjsの実行バイナリの内容は全く同じで、単にハードリンクをしてasp_jsが作られています。(実行バイナリがASPモードとして動作するかどうかの判定は、実行バイナリのファイル名で行っているようです。)

以下のようにすれば、ビルドした実行バイナリの動作確認ができます。(最適化を無効にしてビルドした場合は、Linux_ALL_DBG.OBJディレクトリ以下に実行バイナリが作られます。) 以下の例では、「print("hello")」を評価しています。(print関数は、ssjsのみで利用可能な組み込み関数です。)「quit()」を評価することで、ssjsを終了できます。

% ./Linux_All_OPT.OBJ/js
js> print("hello")
hello
js> quit()
%

このようにssjs単体をビルドできるようにした状態で、ソースコードの解析を行っていきました。

動的解析のテクニック

SpiderMonkeyへのパッチ(ajaja-build/bin/ajaja/js.patch)は、パッチを読むと、ajaja-build/bin/mozilla/js/src/js.cに適用されることがわかります。ですので、このjs.cがAJAJAにおいて核になっているソースコードであると予想できます。

ソースコードを読んでいて、プログラムの制御フローを調べたい時は、C言語の標準ライブラリで提供されているprintf関数を使います。プログラムを走らせて動的にソースコードを解析する際に、printf関数は非常に強力な武器になります。

では、js.cの制御フローの中で気になる所にprintf関数でマーキングしていきます。以下は、Process関数とProcessArgs関数の挙動が不明で、いつ制御が帰ってくるのか調べたいという場合のマーキングの例です。

% vim ajaja-build/bin/mozilla/js/src/js.c
[...]
int
main(int argc, char **argv, char **envp)
{
[...]
    printf("#1\n");

#ifdef ASP_JS
    /* init.js */
    sprintf(buff, "%s/init.js", JSPATH);
    Process(cx, glob, buff, JS_FALSE);
#endif

    printf("#2\n");

    result = ProcessArgs(cx, glob, argv, argc);

    printf("#3\n");

[...]

ビルドをして、実行してみます。

% cd ajaja-build/bin/mozilla/js/src
% make -f Makefile.ref BUILD_OPT=1
% ./Linux_All_OPT.OBJ/js
#1
#2
js> print("hello")
hello
js> quit()

#3
%

この実行結果から、Process関数はすぐに制御を戻してくる一方で、ProcessArgs関数は、シェル内でquit()が評価されるまで制御が戻ってこないことがわかります。この結果からわかるように、ソースコードを単に眺めただけではわかりにくい制御フローが、少しの努力で解析できました。

解析結果

このような動的解析のテックニックと、静的なコードリーディングを通して、Systemモジュールが、実行コンテキストにロードされるまでの制御フローを解析した結果が、以下の図です。青とオレンジの線が制御フローを示しています。

青の線(1)は、ユーザーランドにあるJavaScriptのコードが実行されるまでの制御フローを示しています。init.jsのuse関数に矢印が入っていますが、これは、Process関数によってuse関数がSpiderMonkey仮想マシンが解釈可能なAST(抽象木)に変換されると考えられるからです。

オレンジの線(2)は、ユーザーランドにあるJavaScriptのコードで「use('System');」というコードが存在する場合の制御フローです。(1)でSpiderMonkey仮想マシンの実行コンテキストに、先ほどのuse関数がロードされているので、use関数の本体が実行可能です。

まだまだ説明不足の所が多くありますが、今回の試みが、Systemモジュールのような共有ライブラリ型(*.so)のモジュールを、自分で書いてみたいという方の何かの役に立てれば幸いです。

Code Reading―オープンソースから学ぶソフトウェア開発技法

Code Reading―オープンソースから学ぶソフトウェア開発技法