C/C++プログラム実行時の関数をトレースする方法

C/C++プログラム実行時の関数をトレースする方法

Posted at January 18,2017 12:03 AM
Tag:[C/C++]

C/C++プログラム実行時の関数をトレースする方法を紹介します。

1.はじめに

C/C++プログラムで実行時の関数をトレースする場合、printf()などを埋め込んでログに出力することが少なくないと思います。

が、その方法ではログ出力するためにプログラムに手を入れなくてはならなず、非効率です。

また規模の大きなプログラムでは現実的な解決方法ではありません。

printf()を埋め込まずにトレースできないか、方法を探していたところ、標準でそのような機能があることをみつけました。

仕組みは、gccでトレースしたいプログラムファイルのコンパイル時に、コンパイルオプション"-finstrument-functions"を付与することで、関数の実行開始時および復帰時に下記のフック関数を呼び出せるようになります。

void __cyg_profile_func_enter(void* func, void* caller);
void __cyg_profile_func_exit(void* func, void* caller);

あとはこの2つの関数にログ出力を実装し、ライブラリとして引き込めばOKです。

ということで、C/C++プログラムを動かして関数をトレースする方法を紹介します。

2.ソースコード

まず、トレース用の関数(trace.cpp)です。

前述の__cyg_profile_func_enter()、__cyg_profile_func_exit()を実装し、トレース情報を出力します。

trace.cpp

#include <dlfcn.h>
#include <iostream>
 
extern "C" {
    void __cyg_profile_func_enter(void* func, void* caller);
    void __cyg_profile_func_exit(void* func, void* caller);
}
 
const char* to_name(void* address) {
    Dl_info dli;
    if (0 != dladdr(address, &dli)) {
        return dli.dli_sname;
    }
    return 0;
}
 
void __cyg_profile_func_enter(void* func, void* caller) {
    const char* name = to_name(func);
    if (name) {
        std::cout << "start:" << name << std::endl;
    } else {
        std::cout << "start:" << func << std::endl;
    }
}
 
void __cyg_profile_func_exit(void* func, void* caller) {
    const char* name = to_name(func);
    if (name) {
        std::cout << "end  :" << name << std::endl;
    } else {
        std::cout << "end  :" << func << std::endl;
    }
}

そしてトレース対象となるC++サンプル関数(sample.cpp/sample.c)です。

サンプルでは、main()→hello_world1()→hello_world2()の順に起動するようにします。

ファイルの内容は同じですが、C用とC++用に異なるファイル名で2つ作成します。

sample.cpp/sample.c

#include <stdio.h>
 
void hello_world2(void) {
    printf( "Hello World2!\n" );                                                                                                                              }
 
void hello_world1(void) {
    printf( "Hello World1!\n" );
    hello_world2();
}
 
int main(void) {
    hello_world1();
    return 0;
}

3.コンパイル

トレース用関数(trace.cpp)

% g++ -fPIC -shared trace.cpp -o libctrace.so -ldl

トレース対象のC++サンプル関数(sample.cpp)

% g++ -fPIC -finstrument-functions sample.cpp -o helloworld_cpp

トレース対象のC++サンプル関数(sample.c)

% gcc -fPIC -finstrument-functions sample.c -o helloworld_c

これで下記のとおり、サンプルのhelloworld_c、helloworld_cppと、トレース用ライブラリlibctrace.soが作られます。

% ls -l
-rwxr-xr-x. 1 hoge hoge 7327  1月 17 23:21 2017 helloworld_c
-rwxr-xr-x. 1 hoge hoge 8256  1月 17 23:30 2017 helloworld_cpp
-rwxr-xr-x. 1 hoge hoge 8498  1月 17 23:23 2017 libctrace.so
-rw-r--r--. 1 hoge hoge  342  1月 17 23:20 2017 sample.c
-rw-r--r--. 1 hoge hoge  342  1月 17 23:12 2017 sample.cpp
-rw-r--r--. 1 hoge hoge  784  1月 17 23:23 2017 trace.cpp

4.実行

実行時は環境変数"LD_PRELOAD"を設定します。

LD_PRELOADにlibctrace.soを指定することでこのライブラリが最優先で読み込まれ、__cyg_profile_func_enter()および__cyg_profile_func_exit()が実行されます。

helloworld_cpp(C++)の実行

%  LD_PRELOAD=./libctrace.so ./helloworld_cpp | c++filt                                                                            
start:main
start:hello_world1()
Hello World1!
start:hello_world2()
Hello World2!
end  :hello_world2()
end  :hello_world1()
end  :main

c++filtはC++やJavaのシンボルをデマングルするコマンドです。

helloworld_cpp(C++)でc++filtを使わずに実行

% LD_PRELOAD=./libctrace.so ./helloworld_cpp                                                                                        
start:main
start:_Z12hello_world1v
Hello World1!
start:_Z12hello_world2v
Hello World2!
end  :_Z12hello_world2v
end  :_Z12hello_world1v
end  :main

helloworld_c(C)の実行

% LD_PRELOAD=./libctrace.so ./helloworld_c                                                                                          
start:main
start:hello_world1
Hello World1!
start:hello_world2
Hello World2!
end  :hello_world2
end  :hello_world1
end  :main

なお、実行環境によってトレース関数内で使っているdladdr()が正常に動作しない(=アドレスから関数名が取得できない)ケースがありました。

また、トレース用ライブラリのコンパイルで"-ldl"を指定しないと、実行時に下記のエラーが発生することを確認しています。

./helloworld_cpp: symbol lookup error: ./libctrace.so: undefined symbol: dladdr

5.参考サイト

参考サイトは下記です。ありがとうございました。

関連記事
トラックバックURL


コメントする
greeting

*必須

*必須(非表示)


ご質問のコメントの回答については、内容あるいは多忙の場合、1週間以上かかる場合があります。また、すべてのご質問にはお答えできない可能性があります。予めご了承ください。

太字イタリックアンダーラインハイパーリンク引用
[サインインしない場合はここにCAPTCHAを表示します]

コメント投稿後にScript Errorや500エラーが表示された場合は、すぐに再送信せず、ブラウザの「戻る」ボタンで一旦エントリーのページに戻り(プレビュー画面で投稿した場合は、投稿内容をマウスコピーしてからエントリーのページに戻り)、ブラウザをリロードして投稿コメントが反映されていることを確認してください。

コメント欄に(X)HTMLタグやMTタグを記述される場合、「<」は「&lt;」、「>」は「&gt;」と入力してください。例えば「<$MTBlogURL$>」は「&lt;$MTBlogURL$&gt;」となります(全て半角文字)