自作の関数に対して、簡易コールグラフを出力する。
結論
GCCにおけるオプションの一つに「-finstrument-functions」というものがあり、これと+αでやりたいことができる。
GCCの-finstrument-functionsオプションとは何か
各関数で、呼び出された直後とreturn前 に自作の関数(ここでは、関数名を出力する関数)が呼べるようになる。
(一つ一つの関数に追加するのは面倒だから便利ということ。)
使い方
まだ、使い始めということもあり、簡単なサンプルしか示せないが、載せる。
- __cyg_profile_func_enter()、__cyg_profile_func_exit() 関数を作成する。
この関数名は固定である。
今回やりたいことは、関数名の出力だが、その出力関数をこの2つの関数に記述する。
(下記にtrace.cというファイルを載せる。詳細はそこで。)
- 共有ライブラリ化する
上で作成したソース(trace.c)を共有ライブラリ化する。
- LD_PRELOAD で実行する。
関数名を出力したい実行ファイルは、もうすでに作成済みであるとして、LD_PRELOADを使って、実行する。
実行例:
$ LD_PRELOAD=./libtrace.so ./main(実行ファイル)
trace.c
__cyg_profile_func_enter()、__cyg_profile_func_exit()の部分を載せる。
andre@andre-VirtualBox:~/work/ftrace$ cat trace.c #define _GNU_SOURCE #include<dlfcn.h> #include<stdio.h> #include<string.h> #define MAX_SIZE 256 const char* addr2name ( void* address, char* funcname ); void __cyg_profile_func_enter( void* func_address, void* call_site ); void __cyg_profile_func_exit ( void* func_address, void* call_site ); void common_func ( void* func_address, const char *fmt ); /* ### 関数の呼び出し直後に実行される ### */ void __cyg_profile_func_enter( void* func_address, void* call_site ) { common_func( func_address, "enter =>" ); } /* ### 関数の return 直前に実行される ### */ void __cyg_profile_func_exit ( void* func_address, void* call_site ) { common_func( func_address, "<= exit" ); } void common_func( void* func_address, const char *fmt ) { char _funcname[MAX_SIZE]; const char *_ret = NULL; memset( _funcname, 0x00, sizeof( _funcname ) ); _ret = addr2name( func_address, _funcname ); if( NULL != _ret ) { printf( "%s _funcname = %s.\n", fmt, _funcname ); } } /* ### 関数ポインタのアドレスを(人が読める)関数名へ変換する ### */ const char* addr2name( void* address, char* funcname ) { Dl_info dli; memset( &dli, 0x00, sizeof( dli ) ); /* ### この dladdr() で関数名へ変換している ### */ if( 0 != dladdr( address, &dli ) ) { strncpy( funcname, dli.dli_sname, MAX_SIZE ); return dli.dli_sname; } return NULL; }
実行ファイルのソース
hello!とだけ出力する実行ファイルである。
andre@andre-VirtualBox:~/work/ftrace$ cat main.c #include<stdio.h> /* Proto Type */ void hello_world( void ); void hello_world(void) { printf( "hello!\n" ); } int main(void) { hello_world(); return 0; }
ビルド&実行
今マイブームであるSConsを使って、ビルドしてみる。
SConstructという名前のファイルに、実行ファイルと共有ライブラリの2つを作成する命令を記述し、[scons]とだけ入力し、コマンド実行する。
http://www25.atwiki.jp/ovlivion-air/ が簡潔で、わかりやすい。
SConstruct
andre@andre-VirtualBox:~/work/ftrace$ cat SConstruct Program('main.c', CFLAGS='-fPIC -finstrument-functions') SharedLibrary('trace', Split('trace.c'), LIBS='dl', CFLAGS='-fPIC -DMACRO=_GNU_SOURCE')
scons
andre@andre-VirtualBox:~/work/ftrace$ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... gcc -o trace.os -c -fPIC -DMACRO=_GNU_SOURCE -fPIC trace.c scons: done building targets.
実行。。。
andre@andre-VirtualBox:~/work/ftrace$ LD_PRELOAD=./libtrace.so ./main enter => _funcname = main. enter => _funcname = hello_world. hello! <= exit _funcname = hello_world. <= exit _funcname = main.
その他
記事を書いている途中に発見した、同じことをやっているページを発見。こちらのほうがちょっと詳しく書いてある。
→http://alohakun.blog7.fc2.com/blog-entry-758.html
参考サイト
http://d.hatena.ne.jp/torutk/20090126/p1
→やりたいことをC++でやっていたページ
http://www.nxmnpg.com/ja/3/dladdr
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/dlsym.3.html
→dladdr()について
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man7/feature_test_macros.7.html
→「#define _GNU_SOURCE」を一番上に書くべきであることをなんとなくわからせてくれたページ。