でかいソフトウェアの、大量のソースコードを短時間で読む必要が生じたので、その補助ツールとしてptrace(2)ベースのLinux用関数トレーサを自作しました。こういうツール上でまずソフトウェアを実行してみて、どのファイルのどの関数がどういう順で呼ばれるか把握おけば、いきなりソースコードの山と格闘を始めるより楽かなーと思いまして。せっかく作ったので公開します。

http://binary.nahi.to/hogetrace/

straceはシステムコールだけ、ltraceは共有ライブラリ(DSO)の関数呼び出しだけ*1をトレースしますが、このツールは、実行バイナリ中の自作関数の呼び出しもトレースします。例えば再帰で1から10まで足し算するソースコードを用意して

% cat recursion.c #include <stdio.h> int sum( int n) { return n == 0 ? 0 : n + sum(n - 1 ); } int main() { int s = sum( 10 ); printf( "sum(10) = %d

" , s); return s; }

最適化なしで普通にコンパイルしてトレーサにかけてみるとこんな感じの出力になります。-g をつけると、ファイル名/行番号を出せるので、そうしてあります。

% gcc -g -o recursion recursion.c % hogetrace --plt -lATv ./recursion [pid 17700] +++ process 17700 attached (ppid 17699) +++ [pid 17700] === symbols loaded: './recursion' === [pid 17700] ==> _start() at 0x08048300 [pid 17700] ==> __libc_start_main@plt() at 0x080482cc [pid 17700] ==> __libc_csu_init() at 0x08048460 [pid 17700] ==> _init() at 0x08048294 [pid 17700] ==> call_gmon_start() at 0x08048324 [pid 17700] <== call_gmon_start() [eax = 0x0] [pid 17700] ==> frame_dummy() at 0x080483b0 [pid 17700] <== frame_dummy() [eax = 0x0] [pid 17700] ==> __do_global_ctors_aux() at 0x080484d0 [pid 17700] <== __do_global_ctors_aux() [eax = 0xffffffff] [pid 17700] <== _init() [eax = 0xffffffff] [pid 17700] <== __libc_csu_init() [eax = 0x8049534] [pid 17700] ==> main() at 0x08048404 [/home/sato/ht-trunk/sample/recursion.c:9] [pid 17700] ==> sum(int n <10>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <9>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <8>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <7>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <6>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <5>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <4>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <3>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <2>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <1>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] ==> sum(int n <0>) at 0x080483d4 [/home/sato/ht-trunk/sample/recursion.c:4] [pid 17700] <== sum() [eax = 0x0] [pid 17700] <== sum() [eax = 0x1] [pid 17700] <== sum() [eax = 0x3] [pid 17700] <== sum() [eax = 0x6] [pid 17700] <== sum() [eax = 0xa] [pid 17700] <== sum() [eax = 0xf] [pid 17700] <== sum() [eax = 0x15] [pid 17700] <== sum() [eax = 0x1c] [pid 17700] <== sum() [eax = 0x24] [pid 17700] <== sum() [eax = 0x2d] [pid 17700] <== sum() [eax = 0x37] [pid 17700] ==> printf@plt() at 0x080482ec sum(10) = 55 [pid 17700] <== printf@plt() [eax = 0xd] [pid 17700] <== main() [eax = 0x37] [pid 17700] ==> _fini() at 0x080484f8 [pid 17700] ==> __do_global_dtors_aux() at 0x08048350 [pid 17700] <== __do_global_dtors_aux() [eax = 0x0] [pid 17700] <== _fini() [eax = 0x0] [pid 17700] +++ process 17700 detached (ppid 17699) +++ hogetrace: done

結果の55が計算され、表示されるまでの関数の呼び出し状況がわかると思います。

当初は、KLabのftraceなど、gcc -finstrument-functions系のトレーサを使わせてもらおうと思っていたのですが、（私の解析したいソフトウェアの場合）ソフトウェアの再コンパイルやMakefileの書き換えに大変手間がかかることがわかったので、「stripされていなければ再コンパイル不要」なツールを自作してみたという感じです*2。連休もあることだし、勉強がてら。仕組みはstrace/ltrace/gdb と同じくptrace(2)ベースで、fork/exec/clone対応、DSOの関数呼び出しのトレースも対応、です。なお関数の引数表示のところは、ftraceのソースコードをそのまま流用させていただきました。ありがとうございます。

以下仕組みのメモ + 遊んでいておもしろかったところ (あとでちゃんと書く):

デバッガもどきを作るのは初めてだったので、とてーも楽しかったです。ソースコードやrpmはリンク先にあります。