프로그램이 충돌 할 때 자동으로 스택 추적을 생성하는 방법 사용자가 실행 중이며 Linux, Windows 및 Macintosh에서도

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

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

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



답변

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.


답변

“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


답변

리눅스

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


답변

비록 정답 과 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);
}


답변

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


답변

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

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


답변

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