2007年05月24日

オープンソースを楽しむエンジニアの一日 〜 コードを楽しく読む工夫

はてなブックマークに登録

C言語で書かれたソースコードを読んでいるとき、関数ポインタから呼び出されている機能の実体がどこに書かれているのかを探すのに苦しんだ経験はありませんか?
私はあります、いっぱいあります!! そんなときはどうするかというと・・・

手順1: まずは気分転換をする!
手順2: そして気合いを入れ直す!
手順3: さらに気力で読み砕く!
手順4: 最後に根性で発見する!

これが、ごく一般的な作業手順(?)かと思います・・・(ごめんなさい嘘です)
でもまあ、実際にここまで出来れば、そのプログラムの大まかな構成とか癖みたいなものはだいたい把握できているはずなので、他の関数ポインタについてもある程度当たりをつけて見つけだすことが出来るようにはなるかと思います。

・・・・・が、、できれば気合いと根性を使わずに追えるなら追いたいのが人情ですよね。
straceやltraceを使えばシステムコールやライブラリコールをトレースすることはできますが、残念ながら目的のプログラム内の関数呼び出しをトレースする事はできません。「今どの関数の中にいるのか」、「この操作をしたらどの関数に飛ぶのか」をトレースする事ができれば、ソースを読むのが楽になる(=もっと楽しくなる)に違いありません。

というわけで今日は、特定のプログラムの関数呼び出しをトレースする方法を考えてみたいと思います。

■ gdbでできそうな気がする
実は「コールトレースなんてgdbですぐじゃん」ってなんとなく思っていたのですが、実際にやろうとするとなかなか簡単にできる方法が見つかりませんでした。gdbを使うときは、ブレークポインタを設定してバックトレースする事が多いのですが、今回やりたいことは「今現在どの関数を実行中なのかリアルタイムに教えてほしい!」なので、b&bt ではだめなんです。
苦肉の策として、

$ (echo -e "break main\nrun\n";yes s) | gdb test

とか頭をよぎったんですが、step と next を使い分けないと目的の出力は得られないのでだめでした。(それよりなにより、この方法はなぜかちょっと恥ずかしいす)

もし gdb でうまくやる方法をご存じの方がいらっしゃれば、教えていただけると嬉しいです。


■ valgrindでできるんじゃない?
valgrindとは、超強力なデバッガ&プロファイラです。メモリ関連のバグを発見するのに非常に重宝しています。大きな特徴は、valgrindが仮想的なCPUとして動作することで、対象プログラムの挙動を詳細にトレースできるところでしょうか。

これなら、仕組み的にコールトレースくらいできてもよさそうな気がしたので調べてみたところ、Tool Suite に Callgrind というものがある事がわかりました。

$ valgrind --tool=callgrind test

とすることで簡単にトレースできるようです。
結果はプロセス毎に別ファイル(callgrind.out.PID)に出力されます。

一見よさげなのですが、これにも問題がありました。

  • アプリケーション内の関数コールだけがわかればいいのに、ライブラリコールやシステムコールもトレースされる
  • そのため、出力結果が膨大になる
  • 関数呼び出し毎に callgrind.out.PID をフラッシュしてくれない
  • そのため、リアルタイムに状況を把握できない

惜しいですねえ・・・もう少しって感じですね。
でも、目的にはだいぶ近づいてきました。callgrindに手を加えるか、callgrindっぽいものを作ればばっちりいけそうです。

■ gcc -finstrument-functions ってなに??
valgrindのソース読もうとしていると、社内のIRCでこんな情報が流れました。

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
2. Posted by Yasui   2007年05月25日 13:19
コメントありがとうございます。
プロファイリング関数でコールトレースするのは常套手段なんですね。
とても参考になりました。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント