663

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

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

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

4

29 に答える 29

572

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

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]

これは、スタック内の各フレームのロード モジュール、オフセット、および関数を示しています。ここでは、スタックの一番上にあるシグナル ハンドラーとmainmainfoobar、およびbaz.

于 2008-09-16T21:30:26.377 に答える
127

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

私が提供した例は、Linuxforx86で開発/テストされていることに注意してください。uc_mcontext.arm_pcまた、の代わりにを使用して、これをARMに正常に実装しましたuc_mcontext.eip

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

于 2009-12-18T00:05:42.437 に答える
90

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

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

  • 1Linuxおよび OSX およびは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);
}
于 2010-03-26T19:52:15.417 に答える
33

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

于 2008-09-17T11:31:00.867 に答える
21

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

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

于 2008-09-16T20:52:39.683 に答える
12

コアファイルを生成したら、gdbツールを使用してそれを確認する必要があることに注意することが重要です。gdbがコアファイルを理解できるようにするには、デバッグシンボルを使用してバイナリをインストルメント化するようにgccに指示する必要があります。これを行うには、-gフラグを使用してコンパイルします。

$ g++ -g prog.cpp -o prog

次に、「ulimit -cランダム」を設定してコアをダンプするか、gdb内でプログラムを実行することができます。私は2番目のアプローチがもっと好きです:

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

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

于 2008-09-16T20:57:12.730 に答える
12

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

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

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

于 2008-09-16T21:23:20.497 に答える
10

町の新しい王が到着しました 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);
}
于 2016-12-31T00:19:02.450 に答える
10

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

そして、Google Performance Tools README の奥深くに埋もれています

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

libunwind について語る

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

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

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

于 2008-09-18T02:55:24.980 に答える
9

信頼性の高い、すべてを実行する小さなC++クラスであるDeathHandlerを使用できます。

于 2013-03-01T06:38:05.947 に答える
9

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

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

ずっと前にlibunwindを使用してスタック トレースを取得したことを覚えていますが、お使いのプラットフォームではサポートされていない可能性があります。

于 2008-09-16T20:51:24.630 に答える
9

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

適切に機能する解決策として、次のことをお勧めします。

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

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

于 2016-07-06T12:37:15.317 に答える
7
ulimit -c unlimited

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

よろしく

于 2008-09-16T20:47:19.897 に答える
6

見る:

男3バックトレース

と:

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

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

于 2008-09-16T20:55:21.470 に答える
6

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

于 2008-09-17T00:07:57.887 に答える
5

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

于 2008-09-16T21:01:04.903 に答える
4

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

win:msdnからこれを確認してください。

また、グーグルのクロームコードを見て、クラッシュをどのように処理するかを確認することもできます。優れた例外処理メカニズムがあります。

于 2008-09-16T21:09:52.107 に答える
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;
} 
于 2014-03-20T11:59:22.827 に答える
3

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

于 2008-09-16T21:00:42.067 に答える
1

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

于 2008-09-16T21:16:42.807 に答える
0

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

于 2008-09-16T21:24:29.560 に答える