it-swarm-ko.com

프로그램이 충돌 할 때 스택 추적을 자동으로 생성하는 방법

나는 GCC 컴파일러로 리눅스에서 일하고있다. 내 C++ 프로그램이 충돌하면 스택 추적을 자동으로 생성하고 싶습니다.

내 프로그램은 여러 사용자가 운영하며 Linux, Windows 및 Macintosh에서도 실행됩니다 (모든 버전은 gcc을 사용하여 컴파일됩니다).

내 프로그램이 충돌 할 때 스택 추적을 생성 할 수있게하고 다음 번에 사용자가 실행할 때 스택 추적을 보내면 문제를 추적 할 수 있는지 묻습니다. 내가 보내는 정보를 처리 할 수 ​​있지만 추적 문자열을 생성하는 방법을 모르겠습니다. 어떤 아이디어?

535
KPexEA

리눅스와 저는 gcc 나 glibc를 사용하는 컴파일러를 사용한다면 Mac OS X을 믿습니다. 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 stacktrace를 만드는 데 사용할 수있는 심볼 정보가 출력됩니다.

$ 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]

이것은 스택의 각 프레임이 출현 한로드 모듈, 오프셋 및 기능을 표시합니다. main, main, foobar 외에도 baz 이전에 스택의 맨 위에있는 신호 처리기와 libc 함수를 볼 수 있습니다.

465
Todd Gamblin

리눅스

Stacktrace를 인쇄하고 세분화 오류가 발생했을 때 execinfo.h의 backtrace () 함수를 사용하면 정상적으로 종료되지만 이미 제안 된 반면, 생성 된 backtrace가 결함의 실제 위치를 가리키는 데 필요한 복잡성에 대해 언급하지 마십시오 (x86 및 ARM 아키텍처의 경우 최소한).

시그널 핸들러에 들어갈 때 스택 프레임 체인의 처음 두 엔트리는 시그널 핸들러 내부의 리턴 어드레스와 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 용 Linux에서 개발/테스트 한 것입니다. 또한 uc_mcontext.arm_pc 대신 uc_mcontext.eip를 사용하여 ARM에서 성공적으로 구현했습니다.

다음은이 구현에 대한 세부 정보를 얻은 기사 링크입니다. http://www.linuxjournal.com/article/6391

118
jschmier

"man backtrace"보다 훨씬 쉽습니다. libSegFault.so라는 glibc와 함께 배포 된 약간의 문서 라이브러리 (GNU 전용)가 있습니다.이 프로그램은 catchichgv 프로그램을 지원하기 위해 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]

피투성이의 세부 사항을 알고 싶다면, 가장 좋은 소스는 불행히도 소스입니다 : See http://sourceware.org/git/?p=glibc.git;a=blob;f= debug/segfault.c 와 그 부모 디렉토리 http://sourceware.org/git/?p=glibc.git;a=tree;f= 디버그

117
jhclark

GNU libc backtrace() 함수를 사용하는 방법을 설명하는 정답 이 제공되었지만1 그리고 신호 처리기의 백 트레이스가 실제 오류 위치를 가리키는지를 설명하는 내 대답 을 제공했습니다.2backtrace에서 출력되는 demangling C++ 기호에 대한 언급이 없습니다.

C++ 프로그램에서 백 트레이스를 얻는 경우 c++filt를 통해 출력을 실행할 수 있습니다1 심볼을 다듬거나 를 사용하여 abi::__cxa_demangle1 직접.

  • 1 Linux 및 OS X c++filt__cxa_demangle는 GCC에만 해당합니다.
  • 2 리눅스

다음 C++ Linux 예제는 my 와 같은 신호 처리기를 사용하는 다른 응답 을 사용하여 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 원래 응답 에서 신호 처리기를 기반으로 위의 예제에서 신호 처리기를 대체하여 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);
}
80
jschmier

크로스 플랫폼 크래시 덤프 생성기 및 덤프를 처리하는 도구 인 Google Breakpad 를 살펴볼 가치가 있습니다.

33
Simon Steele

운영 체제를 지정하지 않았으므로 이에 대한 대답은 어렵습니다. gnu libc를 기반으로하는 시스템을 사용하는 경우 libc 함수 backtrace()을 사용할 수 있습니다.

GCC는 또한 당신을 도울 수있는 두 가지 내장 함수를 가지고 있지만 아키텍처에서 완전히 구현되거나 구현되지 않을 수도 있습니다. 이들은 __builtin_frame_address__builtin_return_address입니다. 둘 다 즉각적인 정수 레벨을 원한다. (즉각적으로 변수가 될 수 없다는 뜻이다.) 주어진 레벨에 대한 __builtin_frame_address이 0이 아닌 경우 같은 레벨의 반송 주소를 가져 오는 것이 안전해야합니다.

21
Brian Mitchell

ulimit -c <value>는 유닉스의 코어 파일 크기 제한을 설정합니다. 기본적으로 코어 파일 크기 제한은 0입니다. ulimit -aulimit 값을 볼 수 있습니다.

또한 gdb에서 프로그램을 실행하면 "세그먼트 화 위반"(일반적으로 할당되지 않은 메모리에 액세스 할 때 SIGSEGV) 또는 중단 점을 설정할 수있는 프로그램이 중단됩니다.

ddd와 nemiver는 gdb의 프론트 엔드로서 초보자도 쉽게 작업 할 수 있습니다.

12
Joseph

Addr2line 유틸리티에 관심을 갖기를 열렬히 생각해 주셔서 감사합니다.

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 
11
arr_sea

필자는 잠시 동안이 문제를보고있었습니다.

Google 실적 도구 README에 깊이 파묻혀 있습니다.

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

libunwind에 대한 이야기

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

이 도서관의 의견을 듣고 싶습니다.

-rdynamic의 문제점은 어떤 경우에는 이진의 크기를 상대적으로 크게 증가시킬 수 있다는 것입니다

10
Gregory

코어 파일을 생성 한 후에는 gdb 도구를 사용하여 코어 파일을 볼 필요가 있다는 것을 알아 두는 것이 중요합니다. gdb가 코어 파일을 이해하려면, gcc가 디버깅 기호로 바이너리를 계측하도록 명령해야한다 : 이렇게하려면 -g 플래그로 컴파일한다.

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

그런 다음 "ulimit -c unlimited"를 설정하여 코어를 덤프하거나 gdb에서 프로그램을 실행할 수 있습니다. 나는 두 번째 접근 방식을 더 좋아한다.

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

이게 도움이 되길 바란다.

10
Benson

Backtrace () 함수 또는 매크로를 사용하여 소스를 변경하고 해킹하는 것을 잊어 버리십시오.

제대로 작동하는 솔루션으로서 나는 조언을 구한다.

  1. 바이너리에 디버그 심볼을 임베드하기 위해 "-g"플래그로 프로그램을 컴파일하십시오 (성능에 영향을 미치지 않을까 걱정하지 마십시오).
  2. 리눅스에서 다음 명령을 실행합니다 : "ulimit -c unlimited"- 시스템이 큰 크래시 덤프를 만들 수있게합니다.
  3. 프로그램이 충돌했을 때 작업 디렉토리에 "core"파일이 표시됩니다.
  4. 다음 명령을 실행하여 백 트레이스를 stdout에 인쇄하십시오. gdb -batch -ex "backtrace"./your_program_exe ./core

이렇게하면 프로그램의 적절한 읽을 수있는 백 트레이스가 사람이 읽을 수있는 방식으로 인쇄됩니다 (소스 파일 이름과 줄 번호 포함). 또한이 방법은 시스템을 자동화 할 수있는 자유를줍니다. 프로세스가 코어 덤프를 생성했는지 확인하고 개발자에게 전자 메일로 백 트랙을 보내거나 일부 로깅 시스템에 로그하는 간단한 스크립트를 만듭니다.

9
loopzilla

Libc의 일부 버전에는 스택 추적을 처리하는 함수가 포함되어 있습니다. 당신은 그들을 사용할 수 있습니다 :

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

오래 전 스택 추적을 얻기 위해 libunwind 를 사용 했었지만 플랫폼에 따라 지원되지 않을 수도 있습니다.

9
Stephen Deken
ulimit -c unlimited

는 응용 프로그램이 충돌 한 후 코어 덤프를 만들 수있게 해주는 시스템 변수입니다. 이 경우에는 무제한입니다. 동일한 디렉토리에서 core라는 파일을 찾으십시오. 디버깅 정보가 활성화 된 상태에서 코드를 컴파일했는지 확인하십시오!

문안 인사

8
mana

당신은 DeathHandler - 신뢰할 수있는 모든 것을하는 작은 C++ 클래스를 사용할 수 있습니다.

8
markhor
8
Roskoto

보다:

남자 3 역 추적

과:

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

이것들은 GNU 확장자입니다.

6
Stéphane

CE (ADAPTIVE Communication Environment)의 스택 추적 기능을 참조하십시오. 이미 모든 주요 플랫폼 (및 기타)을 다루기 위해 작성되었습니다. 라이브러리는 BSD 스타일 라이센스이므로 ACE를 사용하지 않으려는 경우에도 코드를 복사/붙여 넣을 수 있습니다.

6
Adam Mitz

나는 리눅스 버전을 도울 수있다 : 함수 역 추적, backtrace_symbols, backtrace_symbols_fd가 사용될 수있다. 해당 매뉴얼 페이지를 참조하십시오.

5
terminus

* nix : 당신은 요격 할 수 있습니다 SIGSEGV (보통이 신호는 충돌 전에 발생합니다) 정보를 파일로 유지하십시오. (예를 들어 gdb를 사용하여 디버깅하는 데 사용할 수있는 코어 파일 외에).

win : msdn에서 확인 .

Google 크롬 코드를보고 충돌을 처리하는 방법을 확인할 수도 있습니다. 그것은 멋진 예외 처리 메커니즘을 가지고 있습니다.

4
INS

나는 @ tgamblin 해결책이 완전하지 않다는 것을 발견했다. 그것은 stackoverflow 처리 할 수 ​​없습니다. 나는 기본적으로 시그널 핸들러가 같은 스택으로 호출되고 SIGSEGV가 두 번 발생하기 때문에 생각한다. 당신을 보호하기 위해서는 시그널 핸들러를위한 독립적 인 스택을 등록해야합니다.

아래 코드를 사용하여이를 확인할 수 있습니다. 기본적으로 처리기는 실패합니다. 정의 된 매크로 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
Daneel S. Yaitskov

나는 여기서 신호 처리기를 수행하고 종료하는 많은 해답을 보았다. 그렇게하는 것이 중요하지만 매우 중요한 사실을 기억하십시오. 생성 된 오류에 대한 코어 덤프를 얻으려면 exit(status)을 호출 할 수 없습니다. 대신 abort()을 호출하십시오!

3
jard18

새로운 왕이 도착했습니다 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);
}
3
Roy

누출 메모리 비주얼 누출 감지기 의 스택 추적을 생성하는 코드를 사용합니다. Win32에서만 작동합니다.

3
Jim Buck

Windows 전용 솔루션으로 Windows 오류보고 를 사용하여 스택 추적과 동등한 정보를 얻을 수 있습니다. 레지스트리 항목이 몇 개만 있으면 사용자 모드 덤프 :를 수집 할 수 있습니다.

Windows Server 2008 및 Windows Vista 서비스 팩 1 (SP1)부터는 사용자 모드 응용 프로그램이 충돌 한 후 전체 사용자 모드 덤프가 로컬로 수집되고 저장되도록 Windows 오류보고 (WER)를 구성 할 수 있습니다. [...]

이 기능은 기본적으로 사용하도록 설정되어 있지 않습니다. 이 기능을 사용하려면 관리자 권한이 필요합니다. 기능을 활성화하고 구성하려면 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows 오류보고\LocalDumps 키에서 다음 레지스트리 값을 사용하십시오.

필요한 권한이있는 설치 프로그램에서 레지스트리 항목을 설정할 수 있습니다.

사용자 모드 덤프를 만들면 클라이언트에서 스택 추적을 생성하는 것보다 다음과 같은 이점이 있습니다.

  • 이미 시스템에 구현되어 있습니다. 위에 설명 된대로 WER를 사용하거나 덤프 할 정보의 양을 더 세부적으로 제어해야하는 경우 MiniDumpWriteDump 직접 호출 할 수 있습니다. (다른 프로세스에서 호출해야합니다.)
  • 웨이 는 스택 트레이스보다 더 완전합니다. 그 중에는 지역 변수, 함수 인수, 다른 스레드에 대한 스택,로드 된 모듈 등이 포함될 수 있습니다. 데이터의 양 (결과적으로 크기)은 사용자 정의가 가능합니다.
  • 디버그 기호를 보낼 필요가 없습니다. 이렇게하면 배포 크기가 크게 줄어들뿐만 아니라 응용 프로그램을 리버스 엔지니어링하기가 더 어려워집니다.
  • 주로 사용하는 컴파일러와 독립적입니다. WER를 사용하면 코드가 필요하지 않습니다. 어느 쪽이든, 기호 데이터베이스 (PDB)를 얻는 방법은 매우 오프라인 분석에 유용합니다. 나는 GCC가 PDB를 생성 할 수 있거나 심볼 데이터베이스를 PDB 형식으로 변환하는 도구가 있다고 생각한다.

WER은 응용 프로그램 장애 (즉, 처리되지 않은 예외로 인해 프로세스를 종료하는 시스템)에 의해서만 트리거 될 수 있습니다. MiniDumpWriteDump은 언제든지 호출 할 수 있습니다. 이것은 크래시 이외의 다른 문제를 진단하기 위해 현재 상태를 덤프해야하는 경우에 유용 할 수 있습니다.

미니 덤프의 적용 가능성을 평가하려면 필수 읽기 :

2
IInspectable

위의 답변 외에도 Debian Linux OS에서 코어 덤프를 생성하는 방법은 다음과 같습니다.

  1. 사용자의 홈 폴더에 "coredumps"폴더 만들기
  2. /etc/security/limits.conf로 이동하십시오. ''줄 아래에 "soft core unlimited"및 "root soft core unlimited"를 입력하면 코어 덤프를 허용하고 코어 덤프 공간을 무제한으로 허용합니다.
  3. 참고 : "* soft core unlimited"는 루트를 포함하지 않으므로 루트가 자체 줄에 지정되어야합니다.
  4. 이 값을 확인하려면 로그 아웃하고 다시 로그인 한 다음 "ulimit -a"를 입력하십시오. "코어 파일 크기"를 무제한으로 설정해야합니다.
  5. . ulimit가 설정되어 있지 않은지 확인하려면 .bashrc 파일 (사용자 및 가능한 경우 루트)을 확인하십시오. 그렇지 않으면 위의 값이 시작시 덮어 쓰기됩니다.
  6. /etc/sysctl.conf를 엽니 다. 아래쪽에 "kernel.core_pattern = /home//coredumps/%e_%t.dump"를 입력하십시오. (% e는 프로세스 이름이고, % t는 시스템 시간입니다.)
  7. Exit "sysctl -p"를 입력하여 새 구성을로드하십시오./proc/sys/kernel/core_pattern을 확인하고 방금 입력 한 내용과 일치하는지 확인하십시오.
  8. 코어 덤프는 명령 행 ( "&")에서 프로세스를 실행 한 다음 "kill -11"로 종료하여 테스트 할 수 있습니다. 코어 덤핑이 성공하면 세그멘테이션 오류 표시 후에 "(코어가 덤프 됨)"이 표시됩니다.
2
enthusiasticgeek

Linux/unix/MacOSX에서는 코어 파일을 사용합니다 (ulimit 또는 호환 시스템 호출 을 사용하여 활성화 할 수 있습니다). Windows에서는 Microsoft 오류보고를 사용합니다 (파트너가되어 응용 프로그램 충돌 데이터에 액세스 할 수 있음).

1
Kasprzol

내가 한 것처럼 여전히 혼자 가고 싶다면 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]
0
Geoffrey

"apport"라는 GNOME 기술을 잊어 버렸지 만, 사용법에 대해서는 잘 모릅니다. 스택 트레이스 및 기타 처리를위한 진단을 생성하는 데 사용되며 버그를 자동으로 기록 할 수 있습니다. 체크인할만한 가치가 있습니다.

0
Joseph

프로그램이 충돌하면 크래시 덤프 정보를 생성하는 것은 운영 체제 자체입니다. * nix OS를 사용하는 경우, ulimit 명령의 'coredump'옵션을 확인하십시오.

0
nsayer

마지막 C++ 부스트 버전 중 하나 인 것처럼 보입니다. 원하는 라이브러리를 제공하는 라이브러리가 등장했습니다. 아마도 코드가 멀티 플랫폼이 될 것입니다. 그것은 boost :: stacktrace 이며, 부스트 샘플에서와 같이 와 같이 사용할 수 있습니다. _ :

#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
}

리눅스에서는 위의 코드를 컴파일합니다 :

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
0
Grzegorz Bazior