2007年05月24日
オープンソースを楽しむエンジニアの一日 〜 コードを楽しく読む工夫
C言語で書かれたソースコードを読んでいるとき、関数ポインタから呼び出されている機能の実体がどこに書かれているのかを探すのに苦しんだ経験はありませんか?
私はあります、いっぱいあります!! そんなときはどうするかというと・・・
手順1: まずは気分転換をする!
手順2: そして気合いを入れ直す!
手順3: さらに気力で読み砕く!
手順4: 最後に根性で発見する!
これが、ごく一般的な作業手順(?)かと思います・・・(ごめんなさい嘘です)
でもまあ、実際にここまで出来れば、そのプログラムの大まかな構成とか癖みたいなものはだいたい把握できているはずなので、他の関数ポインタについてもある程度当たりをつけて見つけだすことが出来るようにはなるかと思います。
・・・・・が、、できれば気合いと根性を使わずに追えるなら追いたいのが人情ですよね。
straceやltraceを使えばシステムコールやライブラリコールをトレースすることはできますが、残念ながら目的のプログラム内の関数呼び出しをトレースする事はできません。「今どの関数の中にいるのか」、「この操作をしたらどの関数に飛ぶのか」をトレースする事ができれば、ソースを読むのが楽になる(=もっと楽しくなる)に違いありません。
というわけで今日は、特定のプログラムの関数呼び出しをトレースする方法を考えてみたいと思います。
私はあります、いっぱいあります!! そんなときはどうするかというと・・・
手順1: まずは気分転換をする!
手順2: そして気合いを入れ直す!
手順3: さらに気力で読み砕く!
手順4: 最後に根性で発見する!
これが、ごく一般的な作業手順(?)かと思います・・・(ごめんなさい嘘です)
でもまあ、実際にここまで出来れば、そのプログラムの大まかな構成とか癖みたいなものはだいたい把握できているはずなので、他の関数ポインタについてもある程度当たりをつけて見つけだすことが出来るようにはなるかと思います。
・・・・・が、、できれば気合いと根性を使わずに追えるなら追いたいのが人情ですよね。
straceやltraceを使えばシステムコールやライブラリコールをトレースすることはできますが、残念ながら目的のプログラム内の関数呼び出しをトレースする事はできません。「今どの関数の中にいるのか」、「この操作をしたらどの関数に飛ぶのか」をトレースする事ができれば、ソースを読むのが楽になる(=もっと楽しくなる)に違いありません。
というわけで今日は、特定のプログラムの関数呼び出しをトレースする方法を考えてみたいと思います。
- ■ gdbでできそうな気がする 実は「コールトレースなんてgdbですぐじゃん」ってなんとなく思っていたのですが、実際にやろうとするとなかなか簡単にできる方法が見つかりませんでした。gdbを使うときは、ブレークポインタを設定してバックトレースする事が多いのですが、今回やりたいことは「今現在どの関数を実行中なのかリアルタイムに教えてほしい!」なので、b&bt ではだめなんです。
- ■ valgrindでできるんじゃない? valgrindとは、超強力なデバッガ&プロファイラです。メモリ関連のバグを発見するのに非常に重宝しています。大きな特徴は、valgrindが仮想的なCPUとして動作することで、対象プログラムの挙動を詳細にトレースできるところでしょうか。
- アプリケーション内の関数コールだけがわかればいいのに、ライブラリコールやシステムコールもトレースされる
- そのため、出力結果が膨大になる
- 関数呼び出し毎に callgrind.out.PID をフラッシュしてくれない
- そのため、リアルタイムに状況を把握できない
- ■ gcc -finstrument-functions ってなに?? valgrindのソース読もうとしていると、社内のIRCでこんな情報が流れました。
苦肉の策として、
$ (echo -e "break main\nrun\n";yes s) | gdb test
とか頭をよぎったんですが、step と next を使い分けないと目的の出力は得られないのでだめでした。(それよりなにより、この方法はなぜかちょっと恥ずかしいす)
もし gdb でうまくやる方法をご存じの方がいらっしゃれば、教えていただけると嬉しいです。
これなら、仕組み的にコールトレースくらいできてもよさそうな気がしたので調べてみたところ、Tool Suite に Callgrind というものがある事がわかりました。
$ valgrind --tool=callgrind test
とすることで簡単にトレースできるようです。
結果はプロセス毎に別ファイル(callgrind.out.PID)に出力されます。
一見よさげなのですが、これにも問題がありました。
惜しいですねえ・・・もう少しって感じですね。
でも、目的にはだいぶ近づいてきました。callgrindに手を加えるか、callgrindっぽいものを作ればばっちりいけそうです。
15:24:44 [hirose] http://www.cqpub.co.jp/INTERFACE/column/freesoft/2004/200402/0.htm 15:24:45 (shirleybt) > フリーソフトウェア徹底活用講座(14) 15:24:52 [hirose] gccの-finstrument-functionsオプション。 15:29:29 [hamano] __cyg_profile_func_enter , exit hook 出来るみたいですね。 15:30:39 [hamano] LD_PRELOAD で 上の関数をロードさせたら便利そう 15:40:26 [hamano] できました<関数トレーサ 15:40:32 [hamano] LD_PRELOAD=/home/hamano/ftrace.so ./stone 15:40:42 [hirose] ハヤス! 15:40:42 [YasuiML] ぐほ 15:41:08 [hamano] 対象のプログラムのビルド時に-finstrument-functions を付ける必要があります 15:41:13 [hirose] OKOK 15:41:32 [YasuiML] あいかわらずすげーな(笑 15:41:34 [hamano] でも今アドレスしか出ないんですよね 15:41:41 [hirose] addr2line? 15:41:44 [hamano] アドレスから名前を引くには 15:41:51 [hamano] はい、libbfd とか 15:41:58 [hamano] しないとダメでしょうね
どうやら、トレースしたいプログラムを gcc で -finstrument-functions というオプションを指定してコンパイルすれば、各関数の入り口と出口をフックできるようになるそうです。フック関数はシェアードライブラリとして作っておき、環境変数のLD_PRELOADに *.so を指定してプログラムを実行すれば良いそうな。
これを使うことで、いとも簡単に(実際約10分くらい?)で関数呼び出しをトレースできるようになってしまいました。(アドレスしかでませんが・・・)
でも、あとはこれにアドレスからシンボルを解決するような処理を追加すれば、間違いなく目的のものになるでしょうね。
最後に、今回でてきた ftrace.so のソースを紹介したいと思います。
おそらく近日中には、実用に耐えられるくらいのものを公開できると思います。
/* * ftrace.c */ #include<stdio.h> void __cyg_profile_func_enter( void *func_address, void *call_site ) __attribute__ ((no_instrument_function)); void __cyg_profile_func_exit ( void *func_address, void *call_site ) __attribute__ ((no_instrument_function)); void __cyg_profile_func_enter( void *this, void *callsite ){ printf("enter: %p\n", (int *)this); } void __cyg_profile_func_exit( void *this, void *callsite ){ printf("exit: %p\n", (int *)this); }
# コンパイル/テスト手順 $ gcc -shared -o ftrace.so ftrace.c $ gcc -O0 -finstrument-functions -o test test.c $ LD_PRELOAD=./ftrace.so ./test
klab_gijutsu2 at 21:25│Comments(2)│TrackBack(1)
トラックバックURL
この記事へのトラックバック
1. オープンソースを楽しむエンジニアの二日目 - ftraceコマンドを作る [ DSAS開発者の部屋 ] 2007年05月25日 20:55
昨日のエントリ オープンソースを楽しむエンジニアの一日 では、特定のプログラムの関数呼び出しをトレースする方法を考えてみました。どうやら、gcc のプロファイリング関数と LD_PRELOAD を使えば比較的簡単に実装できそうだという事がわかりました。
今日はこれらの仕....
この記事へのコメント
1. Posted by 2007年05月25日 01:58
このあたりを参考にすれば良いのではないでしょうか。ほぼ同じものができつつあるようですが。
http://www-06.ibm.com/jp/developerworks/linux/050722/j_l-graphvis.shtml
http://www-06.ibm.com/jp/developerworks/linux/050722/j_l-graphvis.shtml
2. Posted by Yasui 2007年05月25日 13:19
コメントありがとうございます。
プロファイリング関数でコールトレースするのは常套手段なんですね。
とても参考になりました。
プロファイリング関数でコールトレースするのは常套手段なんですね。
とても参考になりました。