私はGCCコンパイラを使ってLinuxに取り組んでいます。私のC++プログラムがクラッシュしたとき、私は自動的にスタックトレースを生成したいと思います。
私のプログラムは、さまざまなユーザーによって実行されています。また、Linux、Windows、およびMacintoshで実行されています(すべてのバージョンはgcc
を使用してコンパイルされています)。
クラッシュしたときにプログラムがスタックトレースを生成できるようにして、次回ユーザーが実行したときに、問題を追跡できるようにスタックトレースを送信してもよいかどうかを確認します。情報の送信は処理できますが、トレース文字列の生成方法がわかりません。何か案は?
LinuxとMac OS Xの場合、gccやglibcを使用するコンパイラを使用している場合は、execinfo.h
のbacktrace()関数を使用してスタックトレースを出力し、セグメンテーション違反が発生したときに正常に終了することができます。ドキュメントは libcマニュアルにあります 。
これはSIGSEGV
ハンドラーをインストールし、それがセグメンテーション違反したときにスタックトレースをstderr
に出力するプログラム例です。ここでのbaz()
関数は、ハンドラを起動させるsegfaultを引き起こします。
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void handler(int sig) {
void *array[10];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, 10);
// print out all the frames to stderr
fprintf(stderr, "Error: signal %d:\n", sig);
backtrace_symbols_fd(array, size, STDERR_FILENO);
exit(1);
}
void baz() {
int *foo = (int*)-1; // make a bad pointer
printf("%d\n", *foo); // causes segfault
}
void bar() { baz(); }
void foo() { bar(); }
int main(int argc, char **argv) {
signal(SIGSEGV, handler); // install our handler
foo(); // this will call foo, bar, and baz. baz segfaults.
}
-g -rdynamic
でコンパイルすると、出力にシンボル情報が得られます。これは、glibcがNiceスタックトレースを作成するために使用できます。
$ gcc -g -rdynamic ./test.c -o test
これを実行すると、次のような出力が得られます。
$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]
これは、スタック内の各フレームのロードモジュール、オフセット、および機能を示しています。ここでは、シグナルハンドラがスタックの一番上にあり、libcがmain
、main
、foo
、およびbar
に加えてbaz
より前に機能していることがわかります。
Execinfo.hでbacktrace()関数を使用してスタックトレースを出力し、セグメンテーションフォルトが発生したときに正常に終了することは すでに提案されています が原因です障害の実際の場所を指しています(少なくとも一部のアーキテクチャー - x86およびARM)。
シグナルハンドラに入ったときのスタックフレームチェーンの最初の2つのエントリには、シグナルハンドラ内のリターンアドレスとlibc内のsigaction()内のリターンアドレスが含まれています。シグナル(フォルトの位置)が失われる前に呼び出された最後の関数のスタックフレーム。
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>
/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
struct sigcontext uc_mcontext;
sigset_t uc_sigmask;
} sig_ucontext_t;
void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
void * array[50];
void * caller_address;
char ** messages;
int size, i;
sig_ucontext_t * uc;
uc = (sig_ucontext_t *)ucontext;
/* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#Elif defined(__x86_64__) // gcc specific
caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other Arch.
#endif
fprintf(stderr, "signal %d (%s), address is %p from %p\n",
sig_num, strsignal(sig_num), info->si_addr,
(void *)caller_address);
size = backtrace(array, 50);
/* overwrite sigaction with caller's address */
array[1] = caller_address;
messages = backtrace_symbols(array, size);
/* skip first stack frame (points here) */
for (i = 1; i < size && messages != NULL; ++i)
{
fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
}
free(messages);
exit(EXIT_FAILURE);
}
int crash()
{
char * p = NULL;
*p = 0;
return 0;
}
int foo4()
{
crash();
return 0;
}
int foo3()
{
foo4();
return 0;
}
int foo2()
{
foo3();
return 0;
}
int foo1()
{
foo2();
return 0;
}
int main(int argc, char ** argv)
{
struct sigaction sigact;
sigact.sa_sigaction = crit_err_hdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
{
fprintf(stderr, "error setting signal handler for %d (%s)\n",
SIGSEGV, strsignal(SIGSEGV));
exit(EXIT_FAILURE);
}
foo1();
exit(EXIT_SUCCESS);
}
signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]
シグナルハンドラ内でbacktrace()関数を呼び出すことによる危険はすべて存在し、見逃してはいけませんが、ここで説明した機能はクラッシュのデバッグに非常に役立つことがわかりました。
私が提供した例はx86 for Linuxで開発/テストされたものであることに注意することは重要です。私はuc_mcontext.arm_pc
の代わりにuc_mcontext.eip
を使ってARMにこれをうまく実装しました。
これは私がこの実装の詳細を学んだ記事へのリンクです: http://www.linuxjournal.com/article/6391
それは "man backtrace"よりもさらに簡単です、glSecと共にlibSegFault.soとして配布されている文書化されていないライブラリ(GNU固有)があります。
これにより、3つの可能性があります。 "program -o hai"を実行する代わりに、
Catchsegv内で実行します。
$ catchsegv program -o hai
実行時にlibSegFaultとリンクします。
$ LD_PRELOAD=/lib/libSegFault.so program -o hai
コンパイル時にlibSegFaultとリンクします。
$ gcc -g1 -lSegFault -o program program.cc
$ program -o hai
3つの場合すべてにおいて、より少ない最適化(gcc -O0または-O1)とデバッグシンボル(gcc -g)でより明確なバックトレースが得られます。そうでなければ、あなたはただメモリアドレスの山になってしまうかもしれません。
以下のようにして、スタックトレースのためのより多くのシグナルをキャッチすることもできます。
$ export SEGFAULT_SIGNALS="all" # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt" # SIGBUS and SIGABRT
出力は次のようになります(一番下のバックトレースに注意してください)。
*** Segmentation fault Register dump:
EAX: 0000000c EBX: 00000080 ECX:
00000000 EDX: 0000000c ESI:
bfdbf080 EDI: 080497e0 EBP:
bfdbee38 ESP: bfdbee20
EIP: 0805640f EFLAGS: 00010282
CS: 0073 DS: 007b ES: 007b FS:
0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000004
OldMask: 00000000 ESP/signal:
bfdbee20 CR2: 00000024
FPUCW: ffff037f FPUSW: ffff0000
TAG: ffffffff IPOFF: 00000000
CSSEL: 0000 DATAOFF: 00000000
DATASEL: 0000
ST(0) 0000 0000000000000000 ST(1)
0000 0000000000000000 ST(2) 0000
0000000000000000 ST(3) 0000
0000000000000000 ST(4) 0000
0000000000000000 ST(5) 0000
0000000000000000 ST(6) 0000
0000000000000000 ST(7) 0000
0000000000000000
Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]
あなたが面倒な詳細を知りたいならば、残念ながら最も良い情報源は情報源です: http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.cを見てください とその親ディレクトリ http://sourceware.org/git/?p=glibc.git;a=tree;f=debug
GNU libc backtrace()
関数の使用方法を説明する 正解 が提供されていても1 そしてシグナルハンドラからのバックトレースがフォルトの実際の位置を指すようにする方法を記述した 私自身の答え を提供しました2、バックトレースから出力される demangling C++シンボルについての言及はありません。
C++プログラムからバックトレースを取得するとき、出力はc++filt
を通じて実行できます。1 シンボルをデマングルするには、または abi::__cxa_demangle
を使用します。1 直接。
c++filt
と__cxa_demangle
はGCC固有のものです。次のC++ Linuxの例では、my other answer と同じシグナルハンドラを使用し、c++filt
を使用してシンボルをデマングルする方法を示します。
コード :
class foo
{
public:
foo() { foo1(); }
private:
void foo1() { foo2(); }
void foo2() { foo3(); }
void foo3() { foo4(); }
void foo4() { crash(); }
void crash() { char * p = NULL; *p = 0; }
};
int main(int argc, char ** argv)
{
// Setup signal handler for SIGSEGV
...
foo * f = new foo();
return 0;
}
出力 (./test
):
signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]
デマングル出力 (./test 2>&1 | c++filt
):
signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]
以下は、my original answer からのシグナルハンドラを基にしており、上記の例のシグナルハンドラを置き換えて、 abi::__cxa_demangle
を使用してシンボルをデマングルすることができます。このシグナルハンドラは、上記の例と同じデマングル出力を生成します。
コード :
void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;
void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific
std::cerr << "signal " << sig_num
<< " (" << strsignal(sig_num) << "), address is "
<< info->si_addr << " from " << caller_address
<< std::endl << std::endl;
void * array[50];
int size = backtrace(array, 50);
array[1] = caller_address;
char ** messages = backtrace_symbols(array, size);
// skip first stack frame (points here)
for (int i = 1; i < size && messages != NULL; ++i)
{
char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
// find parantheses and +address offset surrounding mangled name
for (char *p = messages[i]; *p; ++p)
{
if (*p == '(')
{
mangled_name = p;
}
else if (*p == '+')
{
offset_begin = p;
}
else if (*p == ')')
{
offset_end = p;
break;
}
}
// if the line could be processed, attempt to demangle the symbol
if (mangled_name && offset_begin && offset_end &&
mangled_name < offset_begin)
{
*mangled_name++ = '\0';
*offset_begin++ = '\0';
*offset_end++ = '\0';
int status;
char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
// if demangling is successful, output the demangled function name
if (status == 0)
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << " : "
<< real_name << "+" << offset_begin << offset_end
<< std::endl;
}
// otherwise, output the mangled function name
else
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << " : "
<< mangled_name << "+" << offset_begin << offset_end
<< std::endl;
}
free(real_name);
}
// otherwise, print the whole line
else
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
}
std::cerr << std::endl;
free(messages);
exit(EXIT_FAILURE);
}
Google Breakpad 、クロスプラットフォームのクラッシュダンプジェネレータ、およびダンプを処理するためのツールを見る価値があるかもしれません。
あなたのオペレーティングシステムを指定しなかったので、これは答えるのが難しいです。 gnu libcをベースにしたシステムを使っているなら、libc関数backtrace()
を使うことができるかもしれません。
GCCにはあなたを助けることができる2つの組み込み関数もありますが、それはあなたのアーキテクチャに完全に実装されているかどうかにかかわらず、それらは__builtin_frame_address
と__builtin_return_address
です。どちらも直接整数レベルを必要とします(即時とは、変数にはなれないという意味です)。特定のレベルの__builtin_frame_address
がゼロ以外の場合、同じレベルのリターンアドレスを取得しても安全です。
ulimit -c <value>
は、コアファイルサイズの上限をunixに設定します。デフォルトでは、コアファイルのサイズ制限は0です。ulimit
の値は、ulimit -a
で確認できます。
また、gdb内からプログラムを実行すると、 "segmentation violations"(SIGSEGV
、通常は割り当てられていないメモリにアクセスしたとき)でプログラムが停止するか、ブレークポイントを設定できます。
dddとnemiverはgdbのフロントエンドであり、初心者にとって作業がずっと簡単になります。
私はしばらくこの問題を見てきました。
そして、Google Performance ToolsのREADMEに深く埋め込まれています
http://code.google.com/p/google-perftools/source/browse/trunk/README
libunwindについての話
http://www.nongnu.org/libunwind/
この図書館の意見を聞きたいです。
-rdynamicの問題は、場合によってはバイナリのサイズが比較的大幅に増加する可能性があることです。
私のaddr2lineユーティリティへの関心を引いてくれてありがとう。
Addr2lineユーティリティを使用して、(= jschmierに感謝します) here :の回答の出力を処理するための手早く汚いスクリプトを書きました。
このスクリプトは単一の引数を受け入れます。jschmierのユーティリティからの出力を含むファイルの名前。
トレースの各レベルについて、出力は次のようになります。
BACKTRACE: testExe 0x8A5db6b
FILE: pathToFile/testExe.C:110
FUNCTION: testFunction(int)
107
108
109 int* i = 0x0;
*110 *i = 5;
111
112 }
113 return i;
コード:
#!/bin/bash
LOGFILE=$1
NUM_SRC_CONTEXT_LINES=3
old_IFS=$IFS # save the field separator
IFS=$'\n' # new field separator, the end of line
for bt in `cat $LOGFILE | grep '\[bt\]'`; do
IFS=$old_IFS # restore default field separator
printf '\n'
EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`
ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
echo "BACKTRACE: $EXEC $ADDR"
A2L=`addr2line -a $ADDR -e $EXEC -pfC`
#echo "A2L: $A2L"
FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
echo "FILE: $FILE_AND_LINE"
echo "FUNCTION: $FUNCTION"
# print offending source code
SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
if ([ -f $SRCFILE ]); then
cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
else
echo "File not found: $SRCFILE"
fi
IFS=$'\n' # new field separator, the end of line
done
IFS=$old_IFS # restore default field separator
Libcのいくつかのバージョンはスタックトレースを扱う関数を含みます。あなたはそれらを使うことができるかもしれません:
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
スタックトレースを取得するためにずっと前に libunwind を使ったことを覚えていますが、あなたのプラットフォームではサポートされていないかもしれません。
コアファイルを生成したら、それを調べるにはgdbツールを使用する必要があることに注意することが重要です。 gdbがあなたのコアファイルを理解するためには、gccにデバッグシンボルでバイナリをインスツルメントするように指示しなければなりません:これを行うには、-gフラグを付けてコンパイルします。
$ g++ -g prog.cpp -o prog
それから、コアをダンプするように "ulimit -c unlimited"を設定するか、単にgdb内でプログラムを実行することができます。私は2番目のアプローチがより好きです。
$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...
これが役に立つことを願っています。
ulimit -c unlimited
これはシステム変数で、アプリケーションがクラッシュした後にコアダンプを作成することを可能にします。この場合、無制限の金額です。同じディレクトリでcoreというファイルを探します。デバッグ情報を有効にしてコードをコンパイルしたことを確認してください。
よろしく
あなたのソースを変更することを忘れて、backtrace()関数またはマクロでいくつかのハックをしてください - これらは単に悪い解決策です。
適切に機能する解決策として、私は忠告します:
これはあなたのプログラムの適切な可読バックトレースを人間が読める形式で(ソースファイル名と行番号を使って)表示します。さらに、このアプローチはあなたのシステムを自動化する自由をあなたに与えるでしょう:processがコアダンプを作成したかどうかチェックする短いスクリプトを持って、それから開発者に電子メールでバックトレースを送るか、これをロギングシステムにログインしてください。
DeathHandler - 小さいC++クラスを使用できます。
win:StackWalk64はいかがですか http://msdn.Microsoft.com/ja-jp/library/ms680650.aspx
見る:
男3バックトレース
そして:
#include <exeinfo.h>
int backtrace(void **buffer, int size);
これらはGNU拡張子です。
_ ace _ (ADAPTIVE Communication Environment)のスタックトレース機能を参照してください。すべての主要なプラットフォーム(およびそれ以上)を網羅するようにすでに書かれています。ライブラリはBSDスタイルでライセンスされているので、ACEを使用したくない場合でもコードをコピー/貼り付けできます。
私はLinux版を手助けすることができます:関数backtrace、backtrace_symbolsおよびbacktrace_symbols_fdを使用することができます。対応するマニュアルページを参照してください。
* nix: _ sigsegv _ (通常はこのシグナルはクラッシュする前に発生します)を傍受して情報をファイルに保存することができます。 (gdbを使ってデバッグするのに使うことができるコアファイル以外に)。
win:msdnから this をチェックしてください。
また、Googleのクロムコードを調べてクラッシュの処理方法を確認することもできます。それはいい例外処理メカニズムを持っています。
@ tgamblinによる解決策は完全ではないことがわかりました。 stackoverflowでは処理できません。デフォルトではシグナルハンドラは同じスタックで呼び出され、SIGSEGVは2回スローされるためです。保護するために、シグナルハンドラ用に独立したスタックを登録する必要があります。
以下のコードでこれを確認できます。デフォルトではハンドラは失敗します。定義済みマクロSTACK_OVERFLOWを使えば大丈夫です。
#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <cassert>
using namespace std;
//#define STACK_OVERFLOW
#ifdef STACK_OVERFLOW
static char stack_body[64*1024];
static stack_t sigseg_stack;
#endif
static struct sigaction sigseg_handler;
void handler(int sig) {
cerr << "sig seg fault handler" << endl;
const int asize = 10;
void *array[asize];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, asize);
// print out all the frames to stderr
cerr << "stack trace: " << endl;
backtrace_symbols_fd(array, size, STDERR_FILENO);
cerr << "resend SIGSEGV to get core dump" << endl;
signal(sig, SIG_DFL);
kill(getpid(), sig);
}
void foo() {
foo();
}
int main(int argc, char **argv) {
#ifdef STACK_OVERFLOW
sigseg_stack.ss_sp = stack_body;
sigseg_stack.ss_flags = SS_ONSTACK;
sigseg_stack.ss_size = sizeof(stack_body);
assert(!sigaltstack(&sigseg_stack, nullptr));
sigseg_handler.sa_flags = SA_ONSTACK;
#else
sigseg_handler.sa_flags = SA_RESTART;
#endif
sigseg_handler.sa_handler = &handler;
assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
cout << "sig action set" << endl;
foo();
return 0;
}
ここではシグナルハンドラを実行してから終了するための多くの答えを見ました。これは正しい方法ですが、非常に重要なことを忘れないでください。生成されたエラーのコアダンプを取得したい場合は、exit(status)
を呼び出すことはできません。代わりにabort()
を呼び出してください。
町の新しい王が到着しました https://github.com/bombela/backward-cpp
コードに配置する1つのヘッダーとインストールする1つのライブラリ。
個人的にはこの関数を使って呼び出します
#include "backward.hpp"
void stacker() {
using namespace backward;
StackTrace st;
st.load_here(99); //Limit the number of trace depth to 99
st.skip_n_firsts(3);//This will skip some backward internal function from the trace
Printer p;
p.snippet = true;
p.object = true;
p.color = true;
p.address = true;
p.print(st, stderr);
}
リークしたメモリのスタックトレースを生成するコードを Visual Leak Detector で使用します。ただし、これはWin32上でのみ機能します。
Windows専用の解決策としては、 Windows Error Reporting を使用して、(より多くの、より多くの情報を含む)スタックトレースと同等のものを取得できます。ごく少数のレジストリエントリで、 ユーザーモードダンプを収集する :に設定できます。
Windows Server 2008およびWindows Vista Service Pack 1(SP1)以降では、ユーザーモードのアプリケーションがクラッシュした後に完全なユーザーモードのダンプが収集され、ローカルに格納されるようにWindowsエラー報告(WER)を構成できます。 [...]
この機能はデフォルトでは有効になっていません。機能を有効にするには管理者権限が必要です。この機能を有効にして設定するには、 HKEY_LOCAL_MACHINE¥SOFTWARE¥Microsoft¥Windows¥Windowsエラー報告¥LocalDumps キーの下にある次のレジストリ値を使用します。
必要な特権を持っているインストーラからレジストリエントリを設定できます。
ユーザーモードダンプを作成すると、クライアントでスタックトレースを生成するよりも次のような利点があります。
WERはアプリケーションのクラッシュ(つまり未処理の例外が原因でシステムがプロセスを終了した場合)によってのみ引き起こされることに注意してください。 MiniDumpWriteDump
はいつでも呼び出すことができます。クラッシュ以外の問題を診断するために現在の状態をダンプする必要がある場合、これは役に立ちます。
あなたがミニダンプの適用性を評価したい場合は必須の読書:
上記の回答に加えて、ここでDebian Linux OSにコアダンプを生成させる方法
Linux/unix/MacOSXではコアファイルを使用します(ulimitまたは 互換システムコール を使って有効にすることができます)。 Windowsでは、Microsoftのエラー報告を使用します(パートナーになってアプリケーションのクラッシュデータにアクセスすることができます)。
私のように一人で行きたいのであれば、bfd
に対してリンクし、ここで行ったようにaddr2line
を使用しないようにすることができます。
https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c
これにより出力が生成されます。
[E] crash.linux.c:170 | crit_err_hdlr | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E] crash.linux.c:171 | crit_err_hdlr | signal 11 (Segmentation fault), address is (nil)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E] crash.linux.c:199 | crit_err_hdlr | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E] crash.linux.c:199 | crit_err_hdlr | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]
私は "apport"のGNOME技術を忘れましたが、それを使うことについてはあまり知りません。スタックトレースやその他の処理用の診断を生成するために使用され、自動的にバグを報告することができます。確かにチェックインする価値があります。
プログラムがクラッシュした場合、クラッシュダンプ情報を生成するのはオペレーティングシステム自体です。もしあなたが* nix OSを使っているのであれば、それを妨げないようにする必要があります(ulimitコマンドの 'coredump'オプションを調べてください)。
それはちょうどあなたが欲しいものを提供するためにライブラリが登場した最後のC++ブーストバージョンの1つのように見えます、おそらくコードはマルチプラットフォームでしょう。それは boost :: stacktrace であり、これはboostのサンプルのように のように使うことができる :
#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h> // ::signal, ::raise
#include <boost/stacktrace.hpp>
const char* backtraceFileName = "./backtraceFile.dump";
void signalHandler(int)
{
::signal(SIGSEGV, SIG_DFL);
::signal(SIGABRT, SIG_DFL);
boost::stacktrace::safe_dump_to(backtraceFileName);
::raise(SIGABRT);
}
void sendReport()
{
if (std::filesystem::exists(backtraceFileName))
{
std::ifstream file(backtraceFileName);
auto st = boost::stacktrace::stacktrace::from_dump(file);
std::ostringstream backtraceStream;
backtraceStream << st << std::endl;
// sending the code from st
file.close();
std::filesystem::remove(backtraceFileName);
}
}
int main()
{
::signal(SIGSEGV, signalHandler);
::signal(SIGABRT, signalHandler);
sendReport();
// ... rest of code
}
Linuxの場合上記のコードをコンパイルします。
g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace
boostのドキュメント :からコピーされたバックトレースの例
0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start