IT박스

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

itboxs 2020. 10. 4. 10:43
반응형

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


GCC 컴파일러로 Linux에서 작업하고 있습니다. 내 C ++ 프로그램이 충돌 할 때 자동으로 스택 추적을 생성하고 싶습니다.

내 프로그램은 다양한 사용자가 실행하고 있으며 Linux, Windows 및 Macintosh에서도 실행됩니다 (모든 버전은를 사용하여 컴파일 됨 gcc).

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


Linux 및 Mac OS X의 경우 gcc 또는 glibc를 사용하는 컴파일러를 사용하는 경우 backtrace () 함수 execinfo.h를 사용하여 스택 추적 을 인쇄하고 세분화 오류가 발생하면 정상적으로 종료 할 수 있습니다. 설명서는 libc 매뉴얼에서 찾을 수 있습니다 .

다음은 SIGSEGV핸들러 를 설치하고 stderrsegfault가 실행될 스택 추적을 인쇄 하는 예제 프로그램입니다 . 여기서 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첨가에 main, foo, bar,와 baz.


리눅스

execinfo.h에서 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에서 개발 / 테스트되었습니다. 나는 또한 성공적으로 사용하여 ARM에서이를 구현 한 uc_mcontext.arm_pc대신 uc_mcontext.eip.

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


"man backtrace"보다 훨씬 쉽습니다. libSegFault.so로 glibc와 함께 배포 된 약간 문서화 된 라이브러리 (GNU 특정)가 있습니다.이 라이브러리는 Ulrich Drepper가 프로그램 catchsegv를 지원하기 위해 작성했다고 믿습니다 ( "man catchsegv"참조).

이것은 우리에게 세 가지 가능성을 제공합니다. "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


비록 정답 과 libc는 GNU를 사용하는 방법에 대해 설명이 제공되어 backtrace()기능 (1) 내가 제공하는 내 자신의 대답 오류의 실제 위치에 신호 처리기 점에서 역 추적을 확인하는 방법에 대해 설명합니다 (2) , I는 표시되지 않습니다 역 추적에서 출력 된 C ++ 기호 분해에 대한 언급 .

C ++ 프로그램에서 역 추적을 가져올 때 출력을 c++filt1통해 실행 하여 기호를 엉망으로 만들거나 1을 직접 사용할 수 있습니다.abi::__cxa_demangle

  • 1 Linux 및 OS X 참고 c++filt__cxa_demangleGCC에 따라 다릅니다.
  • 2 리눅스

다음 C ++ Linux 예제는 다른 답변동일한 신호 처리기를 c++filt사용하고 기호를 demangle하는 데 사용되는 방법 보여줍니다 .

코드 :

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 Breakpad를 살펴볼 가치가 있습니다 .


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

GCC는 또한 당신을 도움이 개 내장 명령이 있지만, 이는 또는 아키텍처를 완전히 구현되지 않을 수 있으며, 그는 __builtin_frame_address__builtin_return_address. 둘 다 즉치 정수 수준을 원합니다 (즉시 적으로 변수가 될 수 없음을 의미합니다). 경우 __builtin_frame_address특정 레벨 0이 아닌, 동일한 수준의 반환 주소를 잡기 위해 안전합니다.


ulimit -c <value>유닉스에서 코어 파일 크기 제한을 설정합니다. 기본적으로 코어 파일 크기 제한은 0 ulimit입니다 ulimit -a..

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

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


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 

코어 파일을 생성 한 후에는 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 ...

이게 도움이 되길 바란다.


나는이 문제를 한동안보고 있었다.

그리고 Google Performance Tools README에 깊이 묻혀 있습니다.

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

libunwind에 대해 이야기

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

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

-rdynamic의 문제는 경우에 따라 바이너리의 크기를 상대적으로 크게 늘릴 수 있다는 것입니다.


libc의 일부 버전에는 스택 추적을 처리하는 함수가 포함되어 있습니다. 다음과 같이 사용할 수 있습니다.

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

오래 전에 libunwind를 사용하여 스택 추적을 얻은 것을 기억 하지만 플랫폼에서 지원되지 않을 수 있습니다.


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


소스 변경에 대해 잊어 버리고 backtrace () 함수 또는 매크로로 해킹을 수행하십시오. 이는 좋지 않은 솔루션 일뿐입니다.

제대로 작동하는 솔루션으로 다음과 같이 조언합니다.

  1. 디버그 기호를 바이너리에 포함하기 위해 "-g"플래그를 사용하여 프로그램을 컴파일하십시오 (성능에 영향을주지 않을 것이라는 점에 대해 걱정하지 마십시오).
  2. Linux에서 다음 명령을 실행하십시오 : "ulimit -c unlimited"-시스템이 큰 크래시 덤프를 만들 수 있도록합니다.
  3. 프로그램이 충돌하면 작업 디렉토리에 "core"파일이 표시됩니다.
  4. 다음 명령을 실행하여 역 추적을 stdout에 인쇄합니다. gdb -batch -ex "backtrace"./your_program_exe ./core

이것은 사람이 읽을 수있는 방식 (소스 파일 이름과 줄 번호 포함)으로 프로그램의 적절하고 읽을 수있는 역 추적을 인쇄합니다. 또한이 접근 방식은 시스템을 자동화 할 수있는 자유를 제공합니다. 프로세스가 코어 덤프를 생성했는지 확인한 다음 이메일로 역 추적을 개발자에게 보내거나이를 일부 로깅 시스템에 로그인하는 짧은 스크립트가 있습니다.


ulimit -c unlimited

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

문안 인사


보다:

남자 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를 사용하여 디버그하는 데 사용할 수있는 코어 파일 외에).

승리 : 확인 MSDN에서.

또한 Google의 크롬 코드를보고 충돌을 처리하는 방법을 확인할 수 있습니다. 멋진 예외 처리 메커니즘이 있습니다.


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

Visual Leak Detector 에서 누수 된 메모리에 대한 스택 추적을 생성하는 코드를 사용합니다 . 그러나 이것은 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 soft core unlimited"를 입력하여 코어 덤프에 대한 무제한 공간을 허용합니다.
  3. 참고 : "* 소프트 코어 무제한"은 루트를 포함하지 않으므로 루트를 자체 행에 지정해야합니다.
  4. 이러한 값을 확인하려면 로그 아웃하고 다시 로그인 한 다음 "ulimit -a"를 입력하십시오. "코어 파일 크기"는 무제한으로 설정되어야합니다.
  5. .bashrc 파일 (사용자 및 해당되는 경우 루트)을 확인하여 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"로 종료하여 테스트 할 수 있습니다. 코어 덤프가 성공하면 세그멘테이션 오류 표시 후에 "(코어 덤프 됨)"이 표시됩니다.

Windows 전용 솔루션으로 Windows 오류보고를 사용하여 훨씬 더 많은 정보가 포함 된 스택 추적을 얻을 수 있습니다 . 몇 개의 레지스트리 항목만으로 사용자 모드 덤프수집 하도록 설정할 수 있습니다 .

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

이 기능은 기본적으로 활성화되어 있지 않습니다. 이 기능을 활성화하려면 관리자 권한이 필요합니다. 기능을 활성화하고 구성하려면 HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Error Reporting \ LocalDumps아래에있는 다음 레지스트리 값을 사용하십시오 .

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

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

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

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

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


마지막 C ++ 부스트 버전 중 하나에서 원하는 것을 정확히 제공하는 라이브러리가 나타났습니다. 아마도 코드는 멀티 플랫폼 일 것입니다. 그것은이다 부스트 :: 스택 트레이스 당신처럼 사용할 수 부스트 샘플로 :

#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

Linux / unix / MacOSX에서는 코어 파일을 사용합니다 (ulimit 또는 호환 가능한 시스템 호출로 활성화 할 수 있음 ). Windows에서는 Microsoft 오류보고를 사용합니다 (파트너가되어 애플리케이션 충돌 데이터에 액세스 할 수 있음).


나는 "apport"라는 그놈 기술을 잊어 버렸지 만 그것을 사용하는 것에 대해 많이 알지 못합니다. 처리를위한 스택 추적 및 기타 진단을 생성하는 데 사용되며 자동으로 버그를 신고 할 수 있습니다. 확실히 확인할 가치가 있습니다.


내가 한 것처럼 여전히 혼자 가고 싶다면 여기에서 한 것처럼 링크 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]

참고 URL : https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes

반응형