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

January 18,2017 12:03 AM
Category:[C/C++]
Tag:[C/C++]
Permalink

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.参考サイト

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

Comments [0] | Trackbacks [0]

Movable Typeで承認ワークフロー

January 11,2017 12:03 AM
Category:[MTDDC]
Tag:[MTDDC]
Permalink

遅くなりましたが「MTDDC Meetup Tokyo 2016」で発表した「Movable Typeで承認ワークフロー」のスライドを公開します。

スライドはワークフローおよびWorkflowプラグインについての解説で、次のような構成になっています。

  • ワークフローの概要
  • ワークフロープラグイン開発のきっかけ
  • 苦労した点
  • Workflowプラグインの概要
  • 必要な設定
  • その他の機能
  • 実演(スライドなしです、すいません)

また下記のリンクから当日の音声も聴くことができます。

Movable Typeで承認ワークフロー

Comments [0] | Trackbacks [0]

Finaleで3連符に付点をつける方法

January 7,2017 12:55 AM
Category:[Finale]
Tag:[Finale]
Permalink

Finaleで3連符に付点をつける方法を紹介します。

Finaleで3連符に付点をつける

1.問題点

Finaleで3連符に付点をつけたいと思い、3連符を入力するときに「付点」アイコンをクリックして入力してみましたが、入力できませんした。

ということで、Finaleで3連符に付点をつける方法を紹介します。

2.3連符に付点をつける

3連符に付点をつけるには、付点をつけるのは後回しにして、とりあえず4分音符で入力。

4分音符を入力

8分音符に切り替えて、8分音符も入力します(4分より先に入力してもOK)。

8分音符も入力

「高速ステップ入力ツール」のアイコンをクリック。

高速ステップ入力ツール

表示が切り替わった瞬間に音価の足りない部分が休符(ここでは16分休符)で埋まりますが、無視して作業を続行します。

休符

付点をつけたい音符に挿入バーを移動して「.」キーを押下(Finaleの付点アイコンではなくキーボードのキー)。

「.」キーを押下

「規定の拍数を超えています」というダイアログが表示されるので、「余分な拍を削除する」を選択して「OK」をクリック。

余分な拍を削除する

これで16分休符が削除されます。

16分休符が削除

「選択ツール」アイコンなどをクリックして表示を切り替えれば完成です。

表示を切り替えれば完成

Comments [0] | Trackbacks [0]
 1  |  2  |  3  |  4  |  5  | All pages