これを行うための標準 C 関数がないことはわかっています。Windowsと* nixでこれを行うためのテクニックは何ですか? (Windows XP は、現時点でこれを行うのに最も重要な OS です。)
9 に答える
glibc はbacktrace()
機能を提供します。
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
backtrace()
、およびbacktrace_symbols()
:
マニュアルページから:
#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
...
これをより便利な/OOP の方法で使用する 1 つの方法は、結果をbacktrace_symbols()
例外クラス コンストラクターに保存することです。したがって、そのタイプの例外をスローするたびに、スタック トレースがあります。次に、それを印刷する機能を提供するだけです。例えば:
class MyException : public std::exception {
char ** strs;
MyException( const std::string & message ) {
int i, frames = backtrace(callstack, 128);
strs = backtrace_symbols(callstack, frames);
}
void printStackTrace() {
for (i = 0; i < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
}
};
...
try {
throw MyException("Oops!");
} catch ( MyException e ) {
e.printStackTrace();
}
タダ!
注: 最適化フラグを有効にすると、結果のスタック トレースが不正確になる場合があります。理想的には、デバッグ フラグをオンにし、最適化フラグをオフにして、この機能を使用します。
Windows の場合は、StackWalk64() API を確認してください (32 ビット Windows でも)。UNIX の場合は、OS のネイティブな方法を使用するか、可能であれば glibc の backtrace() にフォールバックする必要があります。
ただし、ネイティブ コードで Stacktrace を取得することは、めったに良い考えではないことに注意してください。それが不可能だからではなく、通常、間違ったことを達成しようとしているからです。
ほとんどの場合、例外がキャッチされたとき、アサートが失敗したとき、または致命的な「例外」または次のようなシグナルを受け取ったときなど、例外的な状況でスタックトレースを取得しようとします。セグメンテーション違反。
最後の問題を考慮すると、ほとんどの API ではメモリを明示的に割り当てる必要があるか、内部的に行う場合があります。プログラムが現在置かれている脆弱な状態でこれを行うと、実際には状況がさらに悪化する可能性があります。たとえば、クラッシュ レポート (またはコアダンプ) には、問題の実際の原因は反映されませんが、処理に失敗したことが反映されます)。
スタックトレースの取得に関しては、ほとんどの人がそれを試みているように見えるので、致命的なエラー処理を達成しようとしていると思います。もしそうなら、私はデバッガー (開発中) に依存し、本番環境でプロセスのコアダンプ (または Windows のミニダンプ) を許可します。適切なシンボル管理と合わせて、原因となっている命令の事後分析を問題なく行う必要があります。
WindowsCaptureStackBackTrace()
の場合、これもオプションであり、ユーザー側で必要な準備コードが少なくて済みますStackWalk64()
。(また、私が持っていた同様のシナリオでは、CaptureStackBackTrace()
最終的にはよりも(より確実に)うまく機能するようになりましStackWalk64()
た。)
アンワインド ライブラリを使用する必要があります。
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;
while (unw_step(&cursor) > 0) {
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
if (ctr >= 10) break;
a[ctr++] = ip;
}
共有ライブラリから呼び出しを行わない限り、あなたのアプローチもうまく機能します。
Linuxのコマンドを使用してaddr2line
、対応する PC のソース関数/行番号を取得できます。
プラットフォームに依存しない方法はありません。
最も近い方法は、最適化を行わずにコードを実行することです。そうすることで、(ビジュアル C++ デバッガーまたは GDB を使用して) プロセスにアタッチし、使用可能なスタック トレースを取得できます。
Solaris にはpstackコマンドがあり、これは Linux にもコピーされました。
スタックを後方に歩くことでそれを行うことができます。ただし、実際には、各関数の先頭でコール スタックに識別子を追加し、最後にそれをポップして、その内容を出力するだけの方が簡単な場合がよくあります。これは少し PITA ですが、うまく機能し、最終的に時間を節約できます。