プログラムがクラッシュしたときにスタックトレースを自動的に生成する方法


590

私はLinuxでGCCコンパイラーを使用しています。C ++プログラムがクラッシュしたときに、自動的にスタックトレースを生成します。

私のプログラムは多くの異なるユーザーによって実行されており、Linux、Windows、Macintoshでも実行されます(すべてのバージョンはを使用してコンパイルされますgcc)。

プログラムがクラッシュしたときにスタックトレースを生成できるようにしたいと思います。次にユーザーが実行すると、スタックトレースを私に送信して問題を追跡できるかどうか尋ねられます。情報の送信は処理できますが、トレース文字列を生成する方法がわかりません。何か案は?


4
backtraceおよびbacktrace_symbols_fdは、非同期信号に対して安全ではありません。シグナルハンドラーでこれらの関数を使用しないでください
Parag Bafna

10
backtrace_symbolsはmallocを呼び出すため、シグナルハンドラーでは使用しないでください。他の2つの関数(backtraceおよびbacktrace_symbols_fd)にはこの問題はなく、シグナルハンドラーで一般的に使用されます。
cmccabe 2012

3
正しくない@cmccabe backtrace_symbols_fdは、通常、mallocを呼び出しませんが、catch_errorブロックで問題が発生した場合に発生する可能性があります
Sam Saffron

6
backtrace_symbols_fd(または任意のバックトレース)のPOSIX仕様がないという意味で「可能性があります」。ただし、GNU / Linuxのbacktrace_symbols_fdは、linux.die.net / man / 3 / backtrace_symbols_fdのように、mallocを呼び出さないように指定されています。したがって、Linuxではmallocを呼び出さないと想定しても安全です。
codetaku 2014

どのようにクラッシュしますか?
Ciro Santilli郝海东冠状病六四事件法轮功

回答:


509

LinuxとMac OS Xでは、gccまたはglibcを使用するコンパイラを使用している場合は、でbacktrace()関数を使用してスタックトレースをexecinfo.h出力し、セグメンテーション違反が発生したときに正常に終了できます。ドキュメントはlibcマニュアルにあります

SIGSEGVハンドラーをインストールし、segfaultが発生した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を使用すると、優れたスタックトレースを作成できます。

$ 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に加えてmainfoobar、とbaz


53
LD_PRELOADで使用できる/lib/libSegFault.soもあります。
CesarB

6
バックトレース出力の最初の2つのエントリには、シグナルハンドラ内に戻りアドレスが含まれ、おそらくsigaction()libc 内に1つあるようです。バックトレースは正しいようですがsigaction()、カーネルによって上書きされる可能性があるため、バックトレースに障害の実際の場所が表示されるようにするには、追加の手順が必要な場合があります。
jschmier

9
クラッシュがmalloc内から発生した場合はどうなりますか?次に、ロックを保持し、「バックトレース」がメモリを割り当てようとするときにスタックしませんか?
マティアス・ニルソン

7
catchsegvOPが必要とするものではありませんが、セグメンテーション違反をキャッチしてすべての情報を取得するのに最適です。
マットクラークソン2013年

8
ARMの場合、-funwind-tablesを使用してコンパイルする必要もありました。それ以外の場合、スタックの深さは常に1(空)でした。
jfritz42 2013

128

それは「man backtrace」よりもさらに簡単です。libSegFault.soとしてglibcで配布される少しドキュメント化されたライブラリ(GNU固有)があります。これは、catchsegvプログラム(「man catchsegv」を参照)をサポートするためにUlrich Drepperによって作成されたと私は信じています。

これには3つの可能性があります。「program -o hai」を実行する代わりに:

  1. catchsegv内で実行:

    $ catchsegv program -o hai
  2. 実行時にlibSegFaultとリンクします。

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. コンパイル時に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


1
「可能性3.コンパイル時にlibSegFaultとリンクする」が機能しない。
HHK 2013年

5
@crafter:「機能しない」とはどういう意味ですか。どの言語/コンパイラ/ツールチェーン/配布/ハードウェアで何を試しましたか?コンパイルに失敗しましたか?エラーをキャッチするには?まったく出力を生成するには?使いにくい出力を生成するには?それは皆を助ける詳細についてありがとうございます。
ステフェイン・グーリッホン

1
「最高のソースは残念ながらソースです」...うまくいけば、いつの日か、catssegvのmanページで実際にSEGFAULT_SIGNALSについて言及することができます。それまでは、この答えを参考にしてください。
greggo 2014

私はCを5年間プログラミングしていて、これについて聞いたことがないとは信じられません:/
DavidMFrey

6
@StéphaneGourichon@HansKratz libSegFaultとリンクする-Wl,--no-as-neededには、コンパイラフラグに追加する必要があります。それ以外の場合は、バイナリがそのシンボルを使用していないことを認識するため、ld実際にはにリンクしlibSegFaultません。
Phillip

122

Linux

execinfo.hのbacktrace()関数を使用してスタックトレースを出力し、セグメンテーション違反が発生したときに正常に終了することはすでに提案されいますが、結果のバックトレースが実際の場所を指すようにするために必要な複雑さについては触れられていません障害(少なくとも一部のアーキテクチャ-x86とARM)。

シグナルハンドラーに入ったときのスタックフレームチェーンの最初の2つのエントリには、シグナルハンドラー内のリターンアドレスとlibcのsigaction()内の1つが含まれています。シグナル(障害の場所)が失われる前に呼び出された最後の関数のスタックフレーム。

コード

#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()関数を呼び出すことによる危険はすべて存在し、見逃してはなりませんが、ここで説明した機能は、クラッシュのデバッグに非常に役立ちます。

私が提供した例はLinux for x86で開発/テストされていることに注意することが重要です。これをARMに実装するuc_mcontext.arm_pc代わりに、uc_mcontext.eip

この実装の詳細を学んだ記事へのリンクは次のとおりです。http//www.linuxjournal.com/article/6391


11
GNU ldを使用するシステムでは、でコンパイルし-rdynamicて、使用中のシンボルだけでなく、すべてのシンボルを動的シンボルテーブルに追加するようにリンカーに指示してください。これによりbacktrace_symbols()、アドレスを関数名に変換できるようになります
jschmier

1
また、「-mapcs-frame」オプションをGCCのコマンドラインに追加して、ARMプラットフォームでスタックフレームを生成する必要があります
qehgt

3
これは遅すぎるかもしれませんが、addr2line何らかの方法でコマンドを使用して、クラッシュが発生した正確な行を取得できますか?
enthusiasticgeek

4
より最近のビルドでglibc uc_mcontextは、というフィールドは含まれていませんeip。これで、インデックスを付ける必要がある配列がありますuc_mcontext.gregs[REG_EIP]。これは同等です。
mmlb

6
ARMの場合、コンパイラに-funwind-tablesオプションを追加するまで、バックトレースの深さは常に1でした。
jfritz42 2013

84

GNU libc 関数1の使用方法を説明する正しい答えが提供されており、シグナルハンドラーからのバックトレースが障害の実際の場所を指すようにする方法を説明する私自身の回答を提供しましたが2、表示されませんバックトレースから出力されたC ++シンボルのデマングルについての言及。backtrace()

C ++プログラムからバックトレースを取得する場合、シンボルを解読するためにc++filt1を介して、または1を直接使用して、出力を実行できます。abi::__cxa_demangle

  • LinuxおよびOS X 1 およびGCC固有であることに注意してくださいc++filt__cxa_demangle
  • 2 Linux

次のC ++ Linuxの例では、他の回答と同じシグナルハンドラーを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]

以下は、私の元の回答からのシグナルハンドラーに基づいて構築されており、上記の例のシグナルハンドラーを置き換えて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);
}

1
jschmierさん、ありがとうございました。この出力をaddr2lineユーティリティにフィードするための小さなbashスクリプトを作成しました。See:stackoverflow.com/a/15801966/1797414
arr_sea

4
#include <cxxabi.h>を忘れないでください
Bamaco '7/07/19

1
優れたドキュメントと簡単なヘッダーファイルが2008年からここに投稿されています... panthema.net/2008/0901-stacktrace-demangledアプローチと非常によく似ています:)
kevinf '23年

abi :: __ cxa_demangleはasync-signal-safeではないようです。そのため、シグナルハンドラーはmallocのどこかでデッドロックする可能性があります。
orcy 2015年

の使用std::cerrfree()およびexit()POSIXシステムでの非同期シグナルセーフでない呼び出しの呼び出しに対するすべての違反。 あなたのプロセスのような任意の呼び出しに失敗した場合、このコードはデッドロックになるfree()malloc() newまたはdetete
Andrew Henle

31

クロスプラットフォームのクラッシュダンプジェネレーターであり、ダンプを処理するツールであるGoogle Breakpadも一見の価値があります。


セグメンテーション違反のようなものについて報告しますが、未処理のC ++例外に関する情報は報告しません。
DBedrenko 2016

21

オペレーティングシステムを指定しなかったため、これを回答するのは困難です。gnu libcベースのシステムを使用している場合は、libc関数を使用できる場合がありますbacktrace()

GCCには2つのビルトインもありますが、これらは組み込みで完全に実装されている場合とされていない場合が__builtin_frame_addressあり、それらはおよび__builtin_return_addressです。どちらも即時整数レベルが必要です(即時とは、変数にすることはできないことを意味します)。場合は__builtin_frame_address与えられたレベルのためにゼロで、同じレベルのリターンアドレスをつかむことは安全でなければなりません。


13

addr2lineユーティリティに注意を向けてくれたenthusiasticgeekに感謝します。

addr2lineユーティリティを使用して、ここで提供さた回答の出力を処理するための迅速で汚れたスクリプトを書きました(jschmierのおかげです!)。

スクリプトは単一の引数を受け入れます: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 

12

ulimit -c <value>UNIXのコアファイルサイズ制限を設定します。デフォルトでは、コアファイルのサイズ制限は0です。ulimit値はで確認できますulimit -a

また、gdb内からプログラムを実行すると、「セグメンテーション違反」でプログラムが停止します(SIGSEGV通常、割り当てていないメモリにアクセスした場合)、またはブレークポイントを設定できます。

dddとnemiverはgdbのフロントエンドであり、初心者にとってはgdbでの作業がはるかに簡単になります。


6
コアダンプは、スタックトレースよりも無限に役立ちます。これは、コアダンプをデバッガーに読み込んで、プログラム全体の状態とクラッシュした時点のデータを確認できるためです。
Adam Hawes、

1
他の人が提案したバックトレース機能はおそらく何もないよりはましですが、それは非常に基本的なもので、行番号すら与えていません。一方、コアダンプを使用すると、クラッシュしたときのアプリケーションの状態全体(詳細なスタックトレースを含む)を遡及的に表示できます。これをフィールドデバッグに使用しようとすると実際的な問題が発生する可能性があります、開発中のクラッシュとアサートを分析するためのより強力なツールです(少なくともLinuxでは)。
nobar

10

コアファイルを生成したら、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 ...

これがお役に立てば幸いです。


4
gdbクラッシュしたプログラムから直接呼び出すこともできます。gdbを呼び出すSIGSEGV、SEGILL、SIGBUS、SIGFPEのセットアップハンドラ。詳細:stackoverflow.com/questions/3151779/… の利点は、のように美しい注釈付きのバックトレースbt fullを取得できることです。また、すべてのスレッドのスタックトレースを取得できます。
Vi。

答えよりも簡単にバックトレースを取得することもできます:gdb -silent ./prog core --eval-command = backtrace --batch -itはバックトレースを表示し、デバッガーを閉じます
baziorek

10

私はしばらくの間この問題を見てきました。

そして、GoogleパフォーマンスツールのREADMEに深く埋め込まれています

http://code.google.com/p/google-perftools/source/browse/trunk/README

libunwindについて話す

http://www.nongnu.org/libunwind/

このライブラリの意見を聞きたいです。

-rdynamicの問題は、場合によってはバイナリのサイズが比較的大幅に増加する可能性があることです


2
x86 / 64では、-rdynamicでバイナリサイズが大幅に増加することはありません。-gを追加すると、大幅に増加します。
Dan

1
libunwindには行番号を取得する機能がないことに気付き、(テストしなかった)unw_get_proc_nameは、元の名前ではなく関数シンボル(オーバーロードなどのために難読化されている)を返します。
ハーバート

1
そのとおりです。これを正しく行うのは非常に困難ですが、私はgaddr2lineで優れた成功を収めてきました。実用的な情報がたくさんありますblog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

あなたはDeathHandlerを使用できます-信頼できる、あなたのためにすべてを行う小さなC ++クラス。


1
残念ながら、それはexeclp()addr2line呼び出しを実行するために使用します...独自のプログラムに完全にとどまるのは良いことです(これは、何らかの形式でaddr2lineコードを含めることによって可能です)

9

ソースの変更を忘れて、backtrace()関数またはマクロでハックを実行してください-これらは単に貧弱なソリューションです。

適切に機能するソリューションとして、私はアドバイスします:

  1. デバッグシンボルをバイナリに埋め込むための "-g"フラグを指定してプログラムをコンパイルします(これがパフォーマンスに影響しないことを心配しないでください)。
  2. Linuxで次のコマンドを実行します: "ulimit -c unlimited"-システムが大きなクラッシュダンプを作成できるようにします。
  3. プログラムがクラッシュすると、作業ディレクトリに「コア」ファイルが表示されます。
  4. 次のコマンドを実行して、バックトレースをstdoutに出力します。gdb -batch -ex "backtrace" ./your_program_exe ./core

これにより、プログラムの適切な読み取り可能なバックトレースが人間が読み取り可能な方法で(ソースファイル名と行番号とともに)印刷されます。さらに、このアプローチにより、システムを自動化する自由が得られます。プロセスがコアダンプを作成したかどうかを確認する短いスクリプトを用意し、バックトレースを電子メールで開発者に送信するか、これをログシステムに記録します。


それは間違った行番号を与えます。改善できますか?
HeyJude

7
ulimit -c unlimited

システム変数です。アプリケーションがクラッシュした後にコアダンプを作成できます。この場合は無制限です。同じディレクトリでcoreというファイルを探します。デバッグ情報を有効にしてコードをコンパイルしたことを確認してください!

よろしく


5
ユーザーはコアダンプを要求していません。彼はスタックトレースを求めています。delorie.com/gnu/docs/glibc/libc_665.htmlを
Todd Gamblin

1
コアダンプには、クラッシュ時のコールスタックが含まれますね。
Mo.

3
彼はUnixを使用していると想定し、Bashを使用しています。
ポールトンブリン

2
tcshを使用している場合は、実行する必要がありますlimit coredumpsize unlimited
sivabudh


6

ACE(ADAPTIVE Communication Environment)のスタックトレース機能を参照してください。すべての主要なプラットフォーム(およびその他)をカバーするようにすでに作成されています。ライブラリはBSDスタイルのライセンスが付与されているため、ACEを使用したくない場合は、コードをコピーして貼り付けることもできます。


リンクが機能していないようです。
tglas

5

私はLinuxバージョンを支援できます。関数backtrace、backtrace_symbols、backtrace_symbols_fdを使用できます。対応するマニュアルページを参照してください。


5

それは最後の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

4

* nix:SIGSEGVをインターセプトできます(通常、この信号はクラッシュする前にして、情報をファイルに保存できます。(たとえば、gdbを使用してデバッグするために使用できるコアファイルのほか)。

勝つ:チェックこれを MSDNから。

また、GoogleのChromeコードを見て、クラッシュの処理方法を確認することもできます。素晴らしい例外処理メカニズムがあります。


SEHはスタックトレースの生成には役立ちません。ソリューションの一部である可能性もありますが、そのソリューションは実装が難しく、提供する情報は少なくなりますが、実際のソリューションよりもアプリケーションに関する情報を多く公開します。ミニダンプを作成します。そして、これを自動的に行うようにWindowsを設定します。
IInspectable 2018

4

@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;
} 

4

町の新しい王が到着しました 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);
}

うわー!それが最後に行われる方法です!私はこれを支持して自分の解決策で捨てたところです。
tglas

3

Visual Leak Detectorでメモリリークのスタックトレースを生成するコードを使用します。ただし、これはWin32でのみ機能します。


また、コードにデバッグシンボルを添付する必要があります。一般的には望ましくありません。ミニダンプを作成し、未処理の例外で自動的に実行されるようにWindowsを設定します。
IInspectable 2018

3

私はここでシグナルハンドラを実行してから終了する多くの答えを見てきました。それが方法ですが、非常に重要な事実を覚えておいてくださいexit(status)。生成されたエラーのコアダンプを取得したい場合は、を呼び出すことはできません。abort()代わりに電話してください!


3

Windowsのみのソリューションとして、Windows エラー報告を使用して、スタックトレースと同等の情報(多くの、より多くの情報を含む)を取得できます。いくつかのレジストリエントリがあれば、ユーザーモードのダンプ収集するように設定できます。

Windows Server 2008およびWindows Vista Service Pack 1(SP1)以降、Windowsエラー報告(WER)を構成して、ユーザーモードアプリケーションがクラッシュした後、完全なユーザーモードダンプを収集してローカルに保存することができます。[...]

この機能はデフォルトでは有効になっていません。この機能を有効にするには、管理者権限が必要です。この機能を有効にして構成するには、HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Error Reporting \ LocalDumpsキーの下にある次のレジストリ値を使用します。

必要な特権を持つインストーラーからレジストリエントリを設定できます。

ユーザーモードダンプを作成すると、クライアントでスタックトレースを生成するよりも次の利点があります。

  • それはすでにシステムに実装されています。ダンプする情報の量をさらに細かく制御する必要がある場合は、上記のようにWERを使用するか、MiniDumpWriteDumpを自分で呼び出すことができます。(必ず別のプロセスから呼び出してください。)
  • 仕方スタックトレースよりも、より完全な。特に、ローカル変数、関数引数、他のスレッドのスタック、ロードされたモジュールなどを含めることができます。データの量(および結果としてサイズ)は、高度にカスタマイズ可能です。
  • デバッグシンボルを出荷する必要はありません。これにより、デプロイメントのサイズが大幅に縮小されるだけでなく、アプリケーションのリバースエンジニアリングが困難になります。
  • 使用するコンパイラーに大きく依存しません。WERを使用しても、コードは必要ありません。いずれにせよ、シンボルデータベース(PDB)を取得する方法があると、オフライン分析に非常に役立ちます。GCCはPDBを生成できるか、シンボルデータベースをPDB形式に変換するツールがあると思います。

WERはアプリケーションのクラッシュ(つまり、未処理の例外のためにシステムがプロセスを終了する)によってのみトリガーされることに注意してください。MiniDumpWriteDumpいつでも呼び出すことができます。これは、クラッシュ以外の問題を診断するために現在の状態をダンプする必要がある場合に役立ちます。

ミニダンプの適用性を評価する場合の必須の読み取り:


2

上記の回答に加えて、Debian Linux OSにコアダンプを生成させる方法

  1. ユーザーのホームフォルダーに「coredumps」フォルダーを作成する
  2. /etc/security/limits.confに移動します。ルートのコアダンプを有効にする場合は、「」行の下に「ソフトコア無制限」と入力し、「ルートソフトコア無制限」と入力して、コアダンプにスペースを無制限に許可します。
  3. 注:「*ソフトコア無制限」はルートをカバーしないため、ルートを独自の行で指定する必要があります。
  4. これらの値を確認するには、ログアウトしてから再度ログインし、「ulimit -a」と入力します。「コアファイルサイズ」は無制限に設定する必要があります。
  5. .bashrcファイル(ユーザー、および該当する場合はroot)を調べて、ulimitがそこに設定されていないことを確認します。それ以外の場合、上記の値は起動時に上書きされます。
  6. /etc/sysctl.confを開きます。下部に「kernel.core_pattern = /home//coredumps/%e_%t.dump」と入力します。(%eはプロセス名、%tはシステム時間になります)
  7. 終了し、「sysctl -p」と入力して新しい構成をロードします/ proc / sys / kernel / core_patternを確認し、これが入力した内容と一致することを確認します。
  8. コアダンプをテストするには、コマンドライン(「&」)でプロセスを実行し、「kill -11」でプロセスを強制終了します。コアダンプが成功した場合、セグメンテーションエラーの表示の後に「(コアダンプ)」と表示されます。

2

私がしたようにそれでも一人で行きたい場合は、ここで行ったようにリンクし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]

1

Linux / unix / MacOSXでは、コアファイルを使用します(ulimitまたは互換性のあるシステムコールでそれらを有効にできます)。Windowsでは、Microsoftエラーレポートを使用します(パートナーになり、アプリケーションクラッシュデータにアクセスできます)。


0

「apport」のGNOMEテクノロジーを忘れてしまいましたが、それを使用することについてはあまり知りません。これは、処理のためのスタックトレースおよびその他の診断を生成するために使用され、バグを自動的にファイルできます。確かにチェックインする価値があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.