web-dev-qa-db-ja.com

呼び出し元の関数の名前を取得するにはどうすればよいですか?

私はgnuツールチェーンを使用しています。実行時に、関数の呼び出し元を見つけるにはどうすればよいですか?たとえば、関数B()は、関数ポインタを使用して多くの関数によって呼び出されます。ここで、Bが呼び出されるたびに、呼び出し元の名前を出力したいと思います。特定の問題をデバッグするためにこれが必要です。

18
binW

GNUを使用している場合は、 backtrace 関数を使用できます。そのmanページに使用例があります。

18
richq

関数の呼び出しのコードの場所は、gccによって __builtin_return_address() 組み込みで保持されます。そのためにnameを取得するには、プログラムのシンボルテーブルを解析する必要があります。 dladdr() を介してそれを行うことは可能ですが、これには制限があります。

  1. backtrace()/dladdr()を呼び出すことは、すべてのコンテキストで安全ではない可能性があります(たとえば、シグナルハンドラーから、またはマルチスレッドプログラムで同時に、またはmalloc()。)。
  2. 操作は瞬時ではありませんが、呼び出し元のスレッドをスリープ状態にする必要がある場合があります。呼び出しの時点でロックが保持されている場合、これは悪いことです。
  3. 返信アドレスを名前に「解決可能」にする方法には制限があります。例えば関数を呼び出すインライン関数がある場合、呼び出し元の呼び出し元が表示されます(backtrace()のマンページには、dladdr()のマンページと同様に、これも記載されています。 「バグ」セクション)。
  4. C++では、con/destructors/initializersを介した呼び出しの追加の課題があります。この場合、関数の「呼び出し元」はコンパイラーによって生成されたメタコードになり、実際に関心のある場所は他の場所(呼び出し元の呼び出し元)になる可能性があります。 、またはコールスタックのさらに深いところ)。

多くの場合、decoupleトレースと関数名の解決に適した方法です。つまり、リターンアドレスを(16進数/バイナリとして)出力し、プログラムの実行中に取得されたシンボルテーブルに対して結果のログを後処理するだけです。

9
FrankH.

同様の質問に答えて Vasil Dimov で指摘されている別の方法は、関数呼び出しを、呼び出し元の関数名を報告または渡すラッパーマクロに置き換えることです。これは、バックトレースが機能しないインライン関数で機能します。一方、参照によって関数を呼び出すか、そうでなければそのアドレスを取得すると、機能しません。

例:これ:

int B(int x){
    ...
}

になる可能性があります:

int _B(int x, char *caller){
    printf("caller is %s\n", caller);
    ...
}

#define B(x) _B((x), __func__)

B()を呼び出すたびに、呼び出し元の名前が出力されます。VasilDimovは、名前をマクロに直接出力し、関数を変更せずに、異なる方法で作成します。

5
Douglas Bagnall