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

StackOverflow https://stackoverflow.com/questions/77005

質問

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

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

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

役に立ちましたか?

解決

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

これは、 SIGSEGV ハンドラーを作成し、スタックトレースを出力します stderr セグメンテーション違反が発生したとき。の baz() ここでの関数は、ハンドラーをトリガーするセグメンテーション違反を引き起こします。

#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 に加えて main, foo, bar, 、 そして baz.

他のヒント

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

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

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

これは「man backtrace」よりもさらに簡単です。ほとんど文書化されていないライブラリ (GNU 固有) が libSegFault.so として glibc とともに配布されています。これはプログラム catchsegv をサポートするために Ulrich Drepper によって書かれたものだと思います (「man catchsegv」を参照)。

これにより、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

たとえ 正解 GNU libc の使用方法を説明するものが提供されています。 backtrace() 関数1 そして私が提供した 私自身の答え シグナル ハンドラーからのバックトレースが障害の実際の位置を指していることを確認する方法について説明します。2, 、についての言及は見当たりません。 デマングリング バックトレースから出力される C++ シンボル。

C++ プログラムからバックトレースを取得する場合、出力を実行できます。 c++filt1 シンボルをデマングルするか、次を使用します。 abi::__cxa_demangle1 直接。

  • 1 Linux と OS Xご了承ください c++filt そして __cxa_demangle GCC 固有のものである
  • 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);
}

見てみる価値はあるかもしれない Google ブレークパッド, 、クロスプラットフォームのクラッシュ ダンプ ジェネレーターとダンプを処理するツール。

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

GCC には、ユーザーを支援する 2 つのビルトインもありますが、これらはアーキテクチャーに完全に実装されている場合とそうでない場合があります。 __builtin_frame_address そして __builtin_return_address. 。どちらも即時整数レベルを必要とします (即時というのは、変数にはできないことを意味します)。もし __builtin_frame_address 特定のレベルがゼロ以外の場合、同じレベルの戻りアドレスを安全に取得できるはずです。

ulimit -c <value> UNIX のコア ファイル サイズ制限を設定します。デフォルトでは、コア ファイルのサイズ制限は 0 です。あなたはあなたのものを見ることができます ulimit の値 ulimit -a.

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

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

addr2line ユーティリティに注意を向けてくださった熱心なオタクさんに感謝します。

提供された回答の出力を処理するための簡単で汚いスクリプトを作成しました ここ:(jschmier に感謝します!) addr2line ユーティリティを使用します。

スクリプトは 1 つの引数を受け取ります。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 

コア ファイルを生成したら、それを確認するために 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 ...

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

私はしばらくこの問題を検討していました。

Google パフォーマンス ツールの README の奥深くに埋もれています

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

リブンウィンドについて語る

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

この図書館についてのご意見をぜひお聞きしたいです。

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

libc の一部のバージョンには、スタック トレースを処理する関数が含まれています。それらを使用できるかもしれません:

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

使っていたのを覚えています リブンウィンド 昔はスタック トレースを取得していましたが、お使いのプラットフォームではサポートされていない可能性があります。

ソースを変更することは忘れて、backtrace() 関数やマクロを使ってハッキングを行ってください。これらは単なる貧弱な解決策です。

適切に機能する解決策として、次のようにアドバイスします。

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

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

ulimit -c unlimited

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

よろしく

勝つ:StackWalk64はどうですか? http://msdn.microsoft.com/en-us/library/ms680650.aspx

使用できます デスハンドラー - あなたの代わりにすべてを行う、信頼性の高い小さな C++ クラス。

見る:

男3のバックトレース

そして:

#include <exeinfo.h>
int backtrace(void **buffer, int size);

これらは GNU 拡張機能です。

のスタック トレース機能を参照してください。 エース (適応型通信環境)。すべての主要なプラットフォーム (およびそれ以上) をカバーするようにすでに書かれています。このライブラリは BSD スタイルでライセンスされているため、ACE を使用したくない場合はコードをコピー/ペーストすることもできます。

Linux バージョンについてお手伝いできます。関数 backtrace、backtrace_symbols、および backtrace_symbols_fd を使用できます。対応するマニュアルページを参照してください。

*ニックス:傍受できます シグセグブ (通常、このシグナルはクラッシュする前に発生します) 情報をファイルに保存します。(たとえば、gdb を使用してデバッグするために使用できるコア ファイルとは別に)。

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

Google の Chrome コードを見て、クラッシュがどのように処理されるかを確認することもできます。優れた例外処理メカニズムを備えています。

@tgamblin ソリューションが完全ではないことがわかりました。stackoverflowでは扱えません。デフォルトでは、同じスタックでSignal Handlerが呼び出され、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;
} 

リークしたメモリのスタック トレースを生成するコードを使用します。 視覚的リークディテクタ. 。ただし、これは Win32 でのみ機能します。

ここでシグナルハンドラーを実行して終了するという多くの回答を見てきました。それが本来のやり方ですが、非常に重要な事実を覚えておいてください。生成されたエラーのコア ダンプを取得したい場合は、 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);
}

上記の回答に加えて、ここでは Debian Linux OS でコアダンプを生成する方法を説明します。

  1. ユーザーのホーム フォルダーに「coredumps」フォルダーを作成します。
  2. /etc/security/limits.conf に移動します。「 」行の下に「soft core unlimited」と入力し、root のコア ダンプを有効にする場合は「root Soft core unlimited」と入力して、コア ダンプに無制限のスペースを許可します。
  3. 注記:「* ソフトコア無制限」は root をカバーしないため、root を独自の行で指定する必要があります。
  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 」でプロセスを強制終了することでテストできます。コア ダンプが成功すると、セグメンテーション フォールトの表示の後に「(core dumped)」と表示されます。

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

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

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

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

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

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

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

ミニダンプの適用性を評価したい場合は、必ず読んでください。

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

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

最後の C++ ブースト バージョンの 1 つでは、まさに必要なものを提供するライブラリが登場したようです。おそらくコードはマルチプラットフォームになるでしょう。それは ブースト::スタックトレース, 、次のように使用できます ブーストサンプルと同様:

#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

コピー元のバックトレースの例 ブーストドキュメント:

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

私のように単独でやりたい場合は、リンクしてください 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]
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top