The Design and Implementation of
the Gracious Days

このあたりで質問した内容。修正がコミットされて解決した。

Userland DTrace のコンパイル時の処理

Userland DTrace では、USDT provider(ユーザレベル静的定義トレースプロバイダ)を定義する際に、プローブをC言語のヘッダファイルとして生成する機能がある。プローブ定義はD言語で次のような書式を使って記述する。

provider sample {
    probe first__read(int, char *);
    probe debuglog(char *);
};

これをfoo.dというファイルに置いてdtrace -h -s foo.dとすると、次のようなマクロ定義が含まれるfoo.hというファイルができる。

#define SAMPLE_FIRST_READ(arg0, arg1) \
    __dtrace_sample___first__read(arg0, arg1)
#define SAMPLE_DEBUGLOG(arg0) \
    __dtrace_sample___debuglog(arg0)

プログラムではこのfoo.hをインクルードして、プローブを置きたいところにマクロを記述する。関数呼出だが、DTraceを有効にしなければnop命令に書き換えられるので、性能への影響は小さい。

これらの関数は当然ながら対象プログラムに含まれていない。そのため、対象プログラムのオブジェクトファイル(ここではsample.oとする)ができた後に、次のように-Gオプションをつけてdtraceコマンドを実行することで生成する。

% dtrace -G -o foo.o -s foo.d sample.o

foo.oには、foo.dで定義されたシンボルの実体が入っている。したがって、最後にすべてをリンクすればUserland DTraceに対応したバイナリができあがる。

シンボル置換

foo.dで定義したfirst_readというプローブは名前に二重下線が含まれているが、実際のプローブ名はfirst-readのようにハイフンに置き換えられる。この処理は、dtrace -Gコマンドがオブジェクトファイルの中のシンボルテーブルを直接書き換えて実現している。

% nm sample.o
            U __dtrace_sample___first__read
            U __stack_chk_fail
            U __stack_chk_guard
0000000000000000    T main
            U read

% dtrace -C -x nolibs -G -o foo.o -s foo.d sample.o

% nm sample.o
            U __dtrace_sample___first-read
            U __stack_chk_fail
            U __stack_chk_guard
0000000000000000    T main
            U ead

プログラムのオブジェクトファイルにある最初の__dtrace_sample__first__readというシンボルが、dtrace -Gコマンドの後に置き換わっているのが分かる。

DTraceのプローブを定義しながら開発を進めていたところ、時々プログラムのリンク時にエラーが出るようになった。先ほどの実行例を良くみると、置き換え後の read のシンボルの頭が 1 文字欠けているのが分かると思う。プローブの名前にも read があるので、これは置き換えの処理がおかしいのでは、というところまであたりをつけたところで、冒頭の質問メールを出してみた。

するとIllumos-gate のバグ報告6653で報告されているものと同一の症状のようだ。原因は次のとおりである。

  • シンボルテーブルはテーブルのオフセットが記録されているが、個々のシンボルがテーブル内でユニークな領域を占める保証はない。つまり"first-read" というシンボルと "read" というシンボルは、同じ部分文字列を含む可能性がある。

ELFのシンボルテーブルを生成するツールによっては文字列の重複部分を最適化するようだ。__ を長さの違う - に置換するため、オフセットがずれてreadの頭一文字が欠けてしまう。レポートには binutils の gas 2.26 以降で発生するとある。dtrace -Gの前にobjcopyを実行してsample.oをコピーすると、症状が出なかった。

そもそもシンボルテーブルを書き換えるのではなくて、シンボルのルックアップ時に__-に置き換えるべきなのでは、というもっともな指摘もあり、FreeBSDにはこういう修正がcommitされた。FreeBSD 10 系はそもそも問題が発生するシンボルテーブルを生成しないので影響はない。

Userland DTraceは使っているひとが少ないのか、不具合がまだまだあるようだ。


Next post: ZFSの性能測定とチューニング

Previous post: uniq(1)の空白