web-dev-qa-db-ja.com

Cで関数呼び出しをトレースする方法は?

なし ソースコードを変更すると、何らかの関数(次の例ではfunc100など)が呼び出されたときに、どの関数がどのパラメーターで呼び出されるかを追跡できます。次のように出力したいと思います。

enter func100(p1001=xxx,p1002=xxx)
        enter func110(p1101=xxx,p1102=xxx)
        exit  func110(p1101=xxx,p1102=xxx)
        enter func120(p1201=xxx,p1202=xxx,p1203=xxx)
                enter func121(p1211=xxx)
                exit  func121(p1211=xxx)
        exit  func120(p1201=xxx,p1202=xxx,p1203=xxx)
exit  func100(p1001=xxx,p1002=xxx)

これは実行可能ですか?または、ソースコードの変更を最小限に抑えたソリューションは何ですか?

14
Andrew

gccを使用する場合は、_-finstrument-functions_コンパイルフラグを使用できます。関数が出入りするたびに、___cyg_profile_func_enter_と___cyg_profile_func_exit_の2つの関数を呼び出すコードを追加します。

やりたいことをするために、これらの関数を実装する必要があります。フラグなしで、またはattribute((no_instrument_function))を使用してコンパイルして、自分自身を呼び出そうとしないようにしてください。

関数の2番目のパラメーターは、呼び出しサイトへのポインター(つまり、呼び出し元の関数内の戻りアドレス)になります。 _%p_で印刷することもできますが、使いにくいです。 nmを使用して、このアドレスを含む実際の関数を把握できます。

この方法で関数パラメーターを取得することはできません。

15
ugoren

GNU Cライブラリでは、 backtrace モジュールを使用できます。その例を次に示します。

#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>


void handler(char *caller) {
  void *array[10];
  size_t size;
  printf("Stack Trace Start for %s\n",caller);
  size = backtrace(array, 10);
  backtrace_symbols_fd(array, size, 2);
  printf("Stack Trace End\n");
}

void car() {
    handler("car()");
    printf("Continue Execution");
}
void baz() {car(); }

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  foo(); 
}

-g -rdynamicコンパイラオプションを使用してコンパイルし、シンボルをロードします

gcc -g -rdynamic Test1.c -o Test

次のような出力が表示されます

Stack Trace Start for car()
./Test(handler+0x2d)[0x80486f1]
./Test(car+0x12)[0x804872e]
./Test(baz+0xb)[0x8048747]
./Test(bar+0xb)[0x8048754]
./Test(foo+0xb)[0x8048761]
./Test(main+0xb)[0x804876e]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0x126e37]
./Test[0x8048631]
Stack Trace End
Continue Execution in car

このハンドラー関数を記述して、プログラムのどこからでもいつでも呼び出すことができます。必要に応じて、arrayサイズを増やすことを忘れないでください。

12
Abhijit

Linuxを使用している場合は、 callgrind が役立つ可能性があります。それは基本的にあなたが探しているものの統計を収集するので、それはその生データにアクセスする方法を提供するかもしれません。

4
vines

デバッガーを使用して、関連するアクションでブレークポイントを設定します。たとえば、gdbでは、トレースする各関数の最初と最後にブレークポイントを設定できます。これらの各ブレークポイントに、次のような実行コマンドを与えることができます。

printf("Enter func100(p1001=%d, p1002=%d)", p1001, p1002)

次に、プログラムを(デバッガーで)実行すると、各コマンドのテキストと関連するパラメーターが出力されます。

gdbに関連するドキュメント をご覧ください。

3
Caleb

制御できない外部ライブラリや変更したくない外部ライブラリの場合でも、多くの関数呼び出しをトレースする必要がある場合があります。

少し前に、gdbの正規表現ブレークポイント(通常のブレークポイントでも問題ありません)を組み合わせて、それらのブレークポイントがトリガーされるたびに実行される一連のコマンドを実行できることに気付きました。参照: http://www.ofb.net/gnu/gdb/gdb_35.html

たとえば、「MPI_」プレフィックスで始まるすべての関数をトレースする場合は、次の操作を実行できます。

(gdb) rbreak MPI_
[...]
(gdb) command 1-XX
(gdb) silent
(gdb) bt 1
(gdb) echo \n\n
(gdb) continue
(gdb) end

サイレントコマンドは、ブレークポイントが見つかったときにgdbメッセージを非表示にするために使用されます。私は通常、読みやすくするために空の行を2、3行印刷します。

次に、プログラムを実行します:(gdb)run

プログラムの実行が開始されると、gdbは最上位のN個のバックトレースレベルを出力します。

#0  0x000000000040dc60 in MPI_Initialized@plt ()


#0  PMPI_Initialized (flag=0x7fffffffba78) at ../../src/mpi/init/initialized.c:46


#0  0x000000000040d9b0 in MPI_Init_thread@plt ()


#0  PMPI_Init_thread (argc=0x7fffffffbe78, argv=0x7fffffffbde0, required=3, provided=0x7fffffffba74) at ../../src/mpi/init/initthread.c:946


#0  0x000000000040e390 in MPI_Comm_rank@plt ()


#0  PMPI_Comm_rank (comm=1140850688, rank=0x7fffffffba7c) at ../../src/mpi/comm/comm_rank.c:53


#0  0x000000000040e050 in MPI_Type_create_struct@plt ()


#0  PMPI_Type_create_struct (count=3, array_of_blocklengths=0x7fffffffba90, array_of_displacements=0x7fffffffbab0, array_of_types=0x7fffffffba80, newtype=0x69de20) at ../../src/mpi/datatype/type_create_struct.c:116


#0  0x000000000040e2a0 in MPI_Type_commit@plt ()


#0  PMPI_Type_commit (datatype=0x69de20) at ../../src/mpi/datatype/type_commit.c:75

より詳細な情報が必要な場合は、特定のブレークポイントのローカル変数を出力することもできます。commandendの間にコマンドを挿入するだけです。

ボーナスのヒント:これらすべてを.gdbinitファイルに追加し、実行をファイルにパイプします。

2
Jorge Bellon

また、関数呼び出しのトレースが適切であるというこの問題も発生しました。したがって、すべての興味深い関数にブレークポイントを設定するPython GDBスクリプト( https://Gist.github.com/stettberger/e6f2fe61206471e22e9e6f192666809 )を作成しました。次に、環境変数TRACE_FUNCTION)。GDBはpython関数を呼び出し、フレームとそのすべての引数をデコードします。ポインターを検出すると、参照を解除しようとするため、関数呼び出しトレースをTRACE_FILEに出力します。 (デフォルト:/ tmp/log)引数付き。次のプログラムの場合

_#include <stdio.h>

struct foo {
    int a;
    struct foo * next;
};

int fib(int a, struct foo *b) {
    if (a <= 1) return 1;
    printf("%d\n", a);
    return fib(a-1, 0)+fib(a-2, 0);
}

int main() {
    struct foo b = {23, 0};
    return fib(5, &b);
}
_

詳細なトレースが得られます。ここで、すべての行はeval()で読み取ることができるpythonタプルです。

_('call', None, 1, 'main', 'main', {})
('call', 1, 2, 'fib', 'fib', {'a': {'type': 'int', 'value': 5}, 'b': {'type': 'struct foo *', 'value': 140737488344320, 'deref': {'type': 'struct foo', 'value': {'a': {'type': 'int', 'value': 23}, 'next': {'type': 'struct foo *', 'value': 0, 'deref': None}}}}})
('call', 2, 3, 'fib', 'fib', {'a': {'type': 'int', 'value': 4}, 'b': {'type': 'struct foo *', 'value': 0, 'deref': None}})
....
('return', 'fib', 2, {'type': 'int', 'value': 8})
('exit', 8)
_

要旨には、ログファイル形式に関する詳細情報が含まれています。

2
stettberger

動的モジュールを使用する場合は、コマンドltraceを使用してこの情報を取得できます。 -lフラグを使用して監視対象ライブラリを指定することもできます

0
debuti

ApacheFoundationによってホストされているプロジェクトであるlog4cxxを調べることができます。 log4j、Javaバリアントを使用すると、感度を設定でき、プログラムで実行されたすべてのことを追跡できることを知っています。c++バリアントは同じかもしれませんが、いくつかの選択肢-アスペクト指向のc ++コンパイラがあり、すべての関数にわたってアスペクトを定義し、変数をキャッチして出力させることができます。別の選択肢は、デバッガーを使用することです。

要約すると、デバッガー、log4cxxまたはAOP

0
Adam Miller