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


590

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

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

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


4
backtrace 및 backtrace_symbols_fd는 비동기 신호 안전이 아닙니다. 시그널 핸들러에서이 함수를 사용해서는 안됩니다
Parag Bafna

10
backtrace_symbols는 malloc을 호출하므로 신호 핸들러에 사용해서는 안됩니다. 다른 두 함수 (backtrace 및 backtrace_symbols_fd)에는이 문제가 없으며 신호 처리기에 일반적으로 사용됩니다.
cmccabe

3
잘못된 backtrace_symbols_fd 인 @cmccabe는 일반적으로 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

답변:


509

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

다음은 SIGSEGV핸들러 를 설치 하고 스택 추적을 stderrsegfaults에 인쇄 하는 예제 프로그램입니다 . 여기서 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.


53
LD_PRELOAD와 함께 사용할 수있는 /lib/libSegFault.so도 있습니다.
CesarB

6
백 트레이스 출력의 처음 두 항목에는 신호 처리기 내부에 리턴 주소가 있고 아마도 sigaction()libc 내부 에 하나의 리턴 주소가 포함되어 있습니다 . 백 트레이스가 올바른 것처럼 보이지만 때로는 장애의 실제 위치가 백 트레이스에 나타나 sigaction()커널에 의해 덮어 쓸 수 있으므로 추가 단계가 필요하다는 것을 알았습니다 .
jschmier

9
malloc 내부에서 충돌이 발생하면 어떻게됩니까? 그런 다음 잠금을 잡고 "backtrace"가 메모리를 할당하려고 할 때 멈추지 않습니까?
Mattias Nilsson

7
catchsegvOP가 필요로하는 것이 아니라 세그먼테이션 오류를 포착하고 모든 정보를 얻는 데 유용합니다.
Matt Clarkson

8
ARM의 경우 -funwind-tables로 컴파일해야했습니다. 그렇지 않으면 내 스택 깊이는 항상 1 (빈)입니다.
jfritz42

128

"man backtrace"보다 훨씬 쉽습니다. glibc와 함께 libSegFault.so로 배포 된 작은 문서 라이브러리 (GNU 전용)가 있습니다.

이것은 우리에게 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

세 가지 경우 모두 최적화 수준이 낮고 (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]

gory 세부 사항을 알고 싶다면 가장 좋은 소스는 불행히도 소스입니다. 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

5
@crafter : "작동하지 않는다"는 것은 무엇을 의미합니까? 어떤 언어 / 컴파일러 / 툴체인 / 배포 / 하드웨어에서 무엇을 시도 했습니까? 컴파일에 실패 했습니까? 오류를 잡으려면? 전혀 출력을 생산하려면? 사용하기 어려운 출력을 생성하려면? 모든 사람을 도울 수있는 자세한 내용에 감사드립니다.
Stéphane Gourichon

1
'최고의 소스는 불행히도 소스입니다'... ... 언젠가 catchsegv의 매뉴얼 페이지에 실제로 SEGFAULT_SIGNALS가 언급되기를 바랍니다. 그때까지는이 답변이 있습니다.
greggo

/ : 나는 5 년 동안 C 프로그래밍이 들어 본 적이 봤는데 믿을 수 없다
DavidMFrey

6
@ StéphaneGourichon @HansKratz libSegFault와 링크하려면 -Wl,--no-as-needed컴파일러 플래그 에 추가 해야합니다. 그렇지 않으면, ld실제로 것입니다 하지 에 대한 링크 libSegFault는 바이너리의 심볼을 사용하지 않는 것을 인식하므로,.
Phillip

122

리눅스

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에서 개발 / 테스트되었다는 점에 유의해야합니다. 또한을 uc_mcontext.arm_pc대신 하여 ARM에서 이것을 성공적으로 구현했습니다 uc_mcontext.eip.

다음은이 구현에 대한 세부 사항을 학습 한 기사에 대한 링크입니다. http://www.linuxjournal.com/article/6391


11
GNU ld를 사용하는 시스템 -rdynamic에서는 링커가 동적 심볼 테이블에 사용 된 심볼뿐만 아니라 모든 심볼을 추가하도록 링커에 지시해야합니다. 이를 통해 backtrace_symbols()주소를 함수 이름으로 변환 할 수 있습니다.
jschmier

1
또한 ARM 플랫폼에서 스택 프레임을 생성하려면 "-mapcs-frame"옵션을 GCC의 명령 줄에 추가해야합니다
qehgt

3
너무 늦을 수 있지만 addr2line충돌이 발생한 정확한 줄을 얻기 위해 어떻게 든 명령 을 사용할 수 있습니까?
enthusiasticgeek

4
보다 최근 빌드 glibc uc_mcontext에는이라는 필드가 없습니다 eip. 이제 색인을 생성해야하는 배열이 있습니다 uc_mcontext.gregs[REG_EIP].
mmlb

6
ARM의 경우 컴파일러에 -funwind-tables 옵션을 추가 할 때까지 역 추적의 깊이는 항상 1입니다.
jfritz42

84

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

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

  • 1 리눅스 및 OS X의 참고 c++filt__cxa_demangleGCC의 특정
  • 2 리눅스

다음 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 스크립트를 만들었습니다. 다음을 참조하십시오 : stackoverflow.com/a/15801966/1797414
arr_sea

4
잊지 말고 #include <cxxabi.h>
Bamaco

1
좋은 문서, 간단한 헤더 파일은 여기에 게시 2008 년 이후 ... 된 panthema.net/2008/0901-stacktrace-demangled : 당신의 접근 방식과 매우 유사
kevinf

abi :: __ cxa_demangle은 비동기 신호 안전이 아닌 것으로 보이므로 신호 처리기는 malloc의 어딘가에서 교착 상태에 빠질 수 있습니다.
orcy

의 사용 std::cerr, free()그리고 exit()모든 POSIX 시스템이 아닌 비동기 시그널에 안전 통화 호출에 대한 제한 사항을 위반. 프로세스는 같은 호출에 실패 할 경우이 코드는 교착 것 free(), malloc() new또는 detete.
Andrew Henle

31

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


세그먼테이션 오류와 같은 내용은보고하지만 처리되지 않은 C ++ 예외에 대한 정보는보고하지 않습니다.
DBedrenko

21

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

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


13

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 

12

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

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

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


6
코어 덤프는 디버거에 코어 덤프를로드하고 충돌 시점에서 전체 프로그램 및 해당 데이터의 상태를 볼 수 있기 때문에 스택 추적보다 훨씬 유용합니다.
Adam Hawes

1
다른 사람들이 제안한 역 추적 시설은 아마도 아무것도 아닌 것보다 낫지 만 매우 기본적입니다. 심지어 줄 번호도 제공하지 않습니다. 반면에 코어 덤프를 사용하면 충돌이 발생한 시점 (자세한 스택 추적 포함)에서 애플리케이션의 전체 상태를 소급하여 볼 수 있습니다. 필드 디버깅에이를 사용하는 데는 실제 문제 가 있을 수 있지만 개발 중 (최소한 Linux에서는) 충돌 및 어설 션을 분석하는 데 더욱 강력한 도구입니다.
nobar

10

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

이게 도움이 되길 바란다.


4
gdb충돌하는 프로그램에서 바로 전화 할 수도 있습니다 . gdb를 호출 할 SIGSEGV, SEGILL, SIGBUS, SIGFPE의 설정 핸들러. 세부 사항 : stackoverflow.com/questions/3151779/… 장점은 에서처럼 주석이 달린 아름다운 백 bt full트레이스를 얻을 수 있으며 모든 스레드의 스택 추적을 얻을 수 있다는 것입니다.
Vi.

GDB -silent ./prog '이라고 핵심 --eval-명령 = 백 트레이스와 가까운 디버거 보여줄 것이다 역 추적 --batch - 그것은 : 당신은 또한 대답보다 쉽게 역 추적을 얻을 수 있습니다
baziorek

10

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

Google 성능 도구 읽어보기

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

libunwind에 대해 이야기

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

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

-rdynamic의 문제점은 경우에 따라 바이너리의 크기를 상당히 크게 증가시킬 수 있다는 것입니다


2
x86 / 64에서는 바이너리 크기가 크게 증가하지 않았습니다. -g를 추가하면 훨씬 더 크게 증가합니다.
Dan

1
libunwind에는 줄 번호를 가져 오는 기능이 없으며 unw_get_proc_name은 원래 이름 대신 함수 기호 (오버로드와 혼동되는)를 반환합니다.
허버트

1
맞습니다. 이 작업을 올바르게 수행하는 것은 매우 까다로워 지지만 gaddr2line을 사용하여 큰 성공을 거두었습니다. blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

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


1
불행히도 execlp()addr2line 호출을 수행 하는 데 사용 됩니다 ... 자체 프로그램에 완전히 머무르는 것이 좋을 것입니다 (addr2line 코드를 어떤 형식으로 포함하여 가능)

9

소스 변경을 잊고 backtrace () 함수 또는 매크로를 사용하여 해킹을 수행하십시오.

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

  1. 디버그 기호를 바이너리에 포함시키기 위해 "-g"플래그를 사용하여 프로그램을 컴파일하십시오 (이것이 성능에 영향을 미치지 않을까 걱정하지 마십시오).
  2. 리눅스에서 다음 명령을 실행하십시오 : "ulimit -c unlimited"-시스템에서 크래시 덤프를 만들 수 있습니다.
  3. 프로그램이 충돌하면 작업 디렉토리에 "core"파일이 표시됩니다.
  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
토드 도박꾼에게

1
코어 덤프에는 크래시가 발생했을 때 호출 스택이 포함됩니까?
Mo.

3
그가 유닉스에 있다고 가정하고 Bash를 사용하고 있습니다.
Paul Tomblin

2
tcsh를 사용하고 있다면limit coredumpsize unlimited
sivabudh


6

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


연결이 끊어진 것 같습니다.
tglas '

5

Linux 버전에 도움을 줄 수 있습니다. backtrace, backtrace_symbols 및 backtrace_symbols_fd 함수를 사용할 수 있습니다. 해당 매뉴얼 페이지를 참조하십시오.


5

마지막 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
}

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

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

4

* nix : SIGSEGV (일반적으로 충돌하기 전에이 신호가 발생 함)를 가로 채 정보를 파일에 보관할 수 있습니다. (예를 들어 gdb를 사용하여 디버깅하는 데 사용할 수있는 코어 파일 외에)

win : msdn에서 이것을 확인하십시오 .

Google 크롬 코드에서 충돌을 처리하는 방법을 확인할 수도 있습니다. 좋은 예외 처리 메커니즘이 있습니다.


SEH는 스택 추적을 생성하는 데 도움이되지 않습니다. 솔루션의 일부일 수는 있지만 해당 솔루션은 실제 솔루션 보다 응용 프로그램에 대한 더 많은 정보를 공개하는 대신 비용이 적게 들고 정보를 덜 제공합니다 . 미니 덤프를 작성하십시오. Windows가 자동으로 수행되도록 설정하십시오.
IInspectable

4

@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

마을의 새로운 왕이 도착했습니다 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

3

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


3

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

Windows Server 2008 및 Windows Vista SP1 (서비스 팩 1)부터는 WER (Windows Error Reporting)을 구성하여 사용자 모드 응용 프로그램 충돌 후 전체 사용자 모드 덤프를 수집하여 로컬에 저장할 수 있습니다. [...]

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

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

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

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

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

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


2

위의 답변 외에도 데비안 리눅스 OS에서 코어 덤프를 생성하는 방법

  1. 사용자의 홈 폴더에 "coredumps"폴더를 만듭니다.
  2. /etc/security/limits.conf로 이동하십시오. ''행 아래에 코어 덤프를 무제한으로 사용할 수 있도록 루트에 코어 덤프를 사용할 수있는 경우 "soft core 무제한"및 "root 소프트 코어 무제한"을 입력하십시오.
  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"로 프로세스를 종료하여 코어 덤프를 테스트 할 수 있습니다. 코어 덤프가 성공하면 분할 오류 표시 후 "(코어 덤프)"가 표시됩니다.

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"기술을 잊어 버렸지 만 그 사용법에 대해서는 잘 모릅니다. 처리를 위해 스택 추적 및 기타 진단을 생성하는 데 사용되며 자동으로 버그를 제기 할 수 있습니다. 체크인 할 가치가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.