it-swarm-pt.tech

Como gerar automaticamente um stacktrace quando meu programa falha

Eu estou trabalhando no Linux com o compilador GCC. Quando meu programa C++ trava, eu gostaria que ele gerasse automaticamente um stacktrace.

Meu programa está sendo executado por muitos usuários diferentes e também roda em Linux, Windows e Macintosh (todas as versões são compiladas usando gcc).

Eu gostaria que meu programa fosse capaz de gerar um rastreamento de pilha quando ele falha e na próxima vez que o usuário o executar, ele perguntará se está tudo bem enviar o rastreamento de pilha para que eu possa rastrear o problema. Eu posso lidar com o envio da informação para mim, mas eu não sei como gerar a string de rastreamento. Alguma ideia?

518
KPexEA

Para Linux e acredito que o Mac OS X, se você estiver usando o gcc, ou qualquer compilador que use a glibc, você pode usar as funções backtrace () em execinfo.h para imprimir um stacktrace e sair normalmente quando receber uma falha de segmentação. Documentação pode ser encontrada no manual da libc .

Aqui está um exemplo de programa que instala um manipulador SIGSEGV e imprime um stacktrace para stderr quando ele é segfaults. A função baz() aqui faz com que o segfault que aciona o manipulador:

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

Compilando com -g -rdynamic você obtém informações de símbolos em sua saída, que a glibc pode usar para criar um stacktrace:

$ gcc -g -rdynamic ./test.c -o test

Executando isso você recebe esta saída:

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

Isso mostra o módulo de carregamento, deslocamento e função de onde cada quadro da pilha veio. Aqui você pode ver o manipulador de sinal no topo da pilha, e as funções libc antes de main, além de main, foo, bar e baz.

447
Todd Gamblin

Linux

Embora o uso das funções backtrace () em execinfo.h para imprimir um stacktrace e sair normalmente quando você recebe uma falha de segmentação tenha já foi sugerido , não vejo nenhuma menção aos detalhes necessários para garantir o rastreio resultante aponta para a localização real da falha (pelo menos para algumas arquiteturas - x86 e ARM).

As duas primeiras entradas na cadeia do quadro de pilha quando você entra no manipulador de sinal contêm um endereço de retorno dentro do manipulador de sinal e um dentro de sigaction () na libc. O quadro da pilha da última função chamada antes do sinal (que é o local da falha) é perdido.

Código

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

Saída

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]

Todos os perigos de chamar as funções backtrace () em um manipulador de sinal ainda existem e não devem ser negligenciados, mas acho que a funcionalidade descrita aqui é bastante útil para depurar falhas.

É importante observar que o exemplo fornecido é desenvolvido/testado no Linux para x86. Eu também tenho implementado com sucesso isso em ARM usando uc_mcontext.arm_pc em vez de uc_mcontext.eip

Aqui está um link para o artigo onde eu aprendi os detalhes para esta implementação: Http://www.linuxjournal.com/article/6391

115
jschmier

É ainda mais fácil do que o "homem backtrace", há uma biblioteca pouco documentada (específica do GNU) distribuída com o glibc como libSegFault.so, que foi escrito por Ulrich Drepper para suportar o programa catchsegv (veja "man catchsegv").

Isso nos dá 3 possibilidades. Em vez de executar "program -o hai":

  1. Executar dentro do catchsegv:

    $ catchsegv program -o hai
    
  2. Vincular com libSegFault no tempo de execução:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Vincular com libSegFault no tempo de compilação:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
    

Em todos os 3 casos, você obterá backtraces mais claros com menos otimização (gcc -O0 ou -O1) e símbolos de depuração (gcc -g). Caso contrário, você pode acabar com uma pilha de endereços de memória.

Você também pode capturar mais sinais para rastreamentos de pilha com algo como:

$ export SEGFAULT_SIGNALS="all"       # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT

A saída será algo como isto (observe o backtrace na parte inferior):

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

Se você quiser saber os detalhes, a melhor fonte é, infelizmente, a fonte: Veja http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c e seu diretório pai http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

112
jhclark

Mesmo que tenha sido fornecida uma resposta correta que descreva como usar a função GNU libc backtrace()1 e eu forneci minha própria resposta que descreve como garantir um backtrace de um manipulador de sinal aponta para a localização real da falha2, Eu não vejo qualquer menção de demangling C++ símbolos de saída do backtrace.

Ao obter backtraces de um programa C++, a saída pode ser executada através de c++filt1 para desmanchar os símbolos ou usando abi::__cxa_demangle1 diretamente.

  • 1 Linux e OS X Observe que c++filt e __cxa_demangle são específicos do GCC
  • 2 Linux

O exemplo C++ Linux a seguir usa o mesmo manipulador de sinal que meu other answer e demonstra como c++filt pode ser usado para desmanchar os símbolos.

Code:

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

Output (./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]

Demangled Output (./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]

O seguinte constrói no manipulador de sinal do meu resposta original e pode substituir o manipulador de sinal no exemplo acima para demonstrar como abi::__cxa_demangle pode ser usado para desmanchar os símbolos. Este manipulador de sinal produz a mesma saída que o exemplo acima.

Code:

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);
}
76
jschmier

Pode valer a pena olhar em Google Breakpad , um gerador de despejo de memória e ferramentas para processar os despejos.

33
Simon Steele

Você não especificou seu sistema operacional, portanto, é difícil responder. Se você estiver usando um sistema baseado no gnu libc, você poderá usar a função libc backtrace().

O GCC também tem dois recursos internos que podem ajudá-lo, mas que podem ou não ser implementados totalmente em sua arquitetura, e esses são __builtin_frame_address e __builtin_return_address. Ambas as quais querem um nível inteiro imediato (por imediato, quero dizer que não pode ser uma variável). Se __builtin_frame_address para um determinado nível for diferente de zero, deve ser seguro pegar o endereço de retorno do mesmo nível.

21
Brian Mitchell

ulimit -c <value> define o limite do tamanho do arquivo principal no unix. Por padrão, o limite de tamanho do arquivo principal é 0. Você pode ver seus valores de ulimit com ulimit -a.

além disso, se você executar seu programa a partir do gdb, ele parará seu programa em "violações de segmentação" (SIGSEGV, geralmente quando você acessou uma parte da memória que você não alocou) ou pode definir pontos de interrupção.

o ddd e o nemiver são front-ends para o gdb, que tornam o trabalho com ele muito mais fácil para o iniciante.

12
Joseph

Eu tenho olhado para este problema por um tempo.

E enterrado profundamente no Google Performance Tools LEIA-ME

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

fala sobre o libunwind

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

Adoraria ouvir as opiniões desta biblioteca.

O problema com -rdynamic é que ele pode aumentar o tamanho do binário de forma relativamente significativa em alguns casos

10
Gregory

Obrigado a enthusiasticgeek por chamar minha atenção para o utilitário addr2line.

Escrevi um script rápido e sujo para processar a saída da resposta fornecida aqui : (Muito obrigado ao jschmier!) Usando o utilitário addr2line.

O script aceita um único argumento: O nome do arquivo que contém a saída do utilitário do jschmier.

A saída deve imprimir algo como o seguinte para cada nível do rastreio:

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;

Código:

#!/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 
10
arr_sea

Algumas versões da libc contêm funções que lidam com rastreamentos de pilha; você pode usá-los:

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

Lembro-me de usar o libunwind há muito tempo para obter rastreios de pilha, mas pode não ser suportado em sua plataforma.

10
Stephen Deken

É importante observar que, depois de gerar um arquivo principal, você precisará usar a ferramenta gdb para examiná-lo. Para o gdb fazer sentido do seu arquivo principal, você deve dizer ao gcc para instrumentar o binário com símbolos de depuração: para fazer isso, você compila com o sinalizador -g:

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

Em seguida, você pode definir "ulimit -c unlimited" para deixar um core ou apenas executar o programa dentro do gdb. Eu gosto da segunda abordagem mais: 

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

Eu espero que isso ajude. 

10
Benson
ulimit -c unlimited

é uma variável de sistema, que permitirá criar um dump de core após o seu aplicativo falhar. Neste caso, um montante ilimitado. Procure por um arquivo chamado core no mesmo diretório. Certifique-se de ter compilado seu código com as informações de depuração ativadas!

saudações

9
mana

Esqueça sobre mudar suas fontes e fazer alguns hacks com função backtrace () ou macros - estas são apenas soluções ruins.

Como solução adequada, aconselho:

  1. Compile seu programa com o sinalizador "-g" para incorporar símbolos de depuração ao binário (não se preocupe, isso não afetará seu desempenho). 
  2. No linux execute o próximo comando: "ulimit -c unlimited" - para permitir que o sistema faça grandes "crash dumps".
  3. Quando o seu programa falhou, no diretório de trabalho você verá o arquivo "core".
  4. Execute o próximo comando para imprimir o backtrace para stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Isso imprimirá o backtrace legível do seu programa de maneira legível (com nomes de arquivos de origem e números de linha). Além disso, essa abordagem lhe dará liberdade para automatizar seu sistema: Tenha um script curto verifica se o processo criou um dump principal e, em seguida, envia os rastreamentos por e-mail para os desenvolvedores ou registra isso em algum sistema de registro.

9
loopzilla

Você pode usar DeathHandler - pequena classe C++ que faz tudo para você, confiável.

8
markhor
8
Roskoto

Olhe para a:

homem 3 backtrace

E:

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

Estas são extensões GNU.

7
Stéphane

Veja o recurso Stack Trace em ACE (ADAPTIVE Communication Environment). Já está escrito para cobrir todas as principais plataformas (e mais). A biblioteca é licenciada no estilo BSD, então você pode até mesmo copiar/colar o código se não quiser usar o ACE.

6
Adam Mitz

Eu posso ajudar com a versão Linux: a função backtrace, backtrace_symbols e backtrace_symbols_fd pode ser usada. Veja as páginas de manual correspondentes.

5
terminus

* nix: você pode interceptar SIGSEGV (normalmente este sinal é gerado antes de travar) e manter as informações em um arquivo. (além do arquivo principal que você pode usar para depurar usando o gdb, por exemplo).

win: Verifique this from msdn.

Você também pode ver o código do Chrome do Google para ver como ele trava as falhas. Ele tem um mecanismo de manipulação de exceção agradável.

4
INS

Descobri que a solução @tgamblin não está completa. Não é possível manipular com stackoverflow. Acho que, por padrão, o manipulador de sinal é chamado com a mesma pilha e SIGSEGV é lançado duas vezes. Para proteger você precisa registrar uma pilha independente para o manipulador de sinal.

Você pode verificar isso com o código abaixo. Por padrão, o manipulador falha. Com a macro definida STACK_OVERFLOW, tudo bem.

#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

Eu vi muitas respostas aqui realizando um manipulador de sinal e então saindo. Esse é o caminho a seguir, mas lembre-se de um fato muito importante: se você quiser obter o dump principal para o erro gerado, você pode ' chame exit(status). Chame abort() em vez disso!

3
jard18

O novo rei da cidade chegou https://github.com/bombela/backward-cpp

1 cabeçalho para colocar no seu código e 1 biblioteca para instalar.

Pessoalmente eu chamo isso usando essa função

#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

Eu usaria o código que gera um rastreamento de pilha para a memória vazada em Visual Leak Detector . Isso só funciona no Win32, no entanto.

3
Jim Buck

Como uma solução somente do Windows, você pode obter o equivalente a um rastreamento de pilha (com muito, muito mais informações) usando Windows Error Reporting . Com apenas algumas entradas de registro, ele pode ser configurado para coletar dumps do modo de usuário :

A partir do Windows Server 2008 e do Windows Vista com Service Pack 1 (SP1), o Relatório de Erros do Windows (WER) pode ser configurado para que os despejos completos do modo de usuário sejam coletados e armazenados localmente após um aplicativo de modo de usuário falhar. [...]

Esse recurso não está habilitado por padrão. Ativar o recurso requer privilégios de administrador. Para habilitar e configurar o recurso, use os seguintes valores do Registro na chave HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Relatório de Erros\Bombas Locais.

Você pode definir as entradas de registro do seu instalador, que possui os privilégios necessários.

Criar um dump do modo de usuário tem as seguintes vantagens sobre a geração de um rastreamento de pilha no cliente:

  • Já está implementado no sistema. Você pode usar o WER conforme descrito acima ou chamar MiniDumpWriteDump yourself, se precisar de um controle mais refinado sobre a quantidade de informações a ser descarregada. (Certifique-se de chamá-lo de um processo diferente.)
  • Way mais completo que um rastreamento de pilha. Entre outros, pode conter variáveis ​​locais, argumentos de função, pilhas para outros threads, módulos carregados e assim por diante. A quantidade de dados (e consequentemente o tamanho) é altamente personalizável.
  • Não há necessidade de enviar símbolos de depuração. Isso diminui drasticamente o tamanho de sua implantação, além de dificultar a engenharia reversa de seu aplicativo.
  • Em grande parte independente do compilador que você usa. O uso do WER nem requer qualquer código. De qualquer maneira, ter uma maneira de obter um banco de dados de símbolos (PDB) é very útil para análise offline. Eu acredito que o GCC pode gerar PDBs, ou existem ferramentas para converter o banco de dados de símbolo para o formato PDB.

Observe que o WER só pode ser acionado por uma falha do aplicativo (isto é, o sistema finalizando um processo devido a uma exceção não tratada). MiniDumpWriteDump pode ser chamado a qualquer momento. Isso pode ser útil se você precisar descartar o estado atual para diagnosticar problemas diferentes de uma falha.

Leitura obrigatória, se você quiser avaliar a aplicabilidade de mini dumps:

2
IInspectable

Além das respostas acima, aqui como você faz o Debian Linux OS gerar core dump 

  1. Crie uma pasta “coredumps” na pasta pessoal do usuário
  2. Vá para /etc/security/limits.conf. Abaixo da linha '', digite 'soft core unlimited' e 'root soft core unlimited' se habilitar core dumps para o root, para permitir espaço ilimitado para core dumps. 
  3. NOTA: “* soft core unlimited” não cobre root, e é por isso que o root precisa ser especificado em sua própria linha.
  4. Para verificar esses valores, efetue logout, faça login novamente e digite “ulimit -a”. "Tamanho do arquivo principal" deve ser definido como ilimitado.
  5. Verifique os arquivos .bashrc (usuário e root, se aplicável) para certificar-se de que ulimit não esteja definido lá. Caso contrário, o valor acima será sobrescrito na inicialização.
  6. Abra o arquivo /etc/sysctl.conf. Digite o seguinte na parte inferior: “kernel.core_pattern = /home//coredumps/%e_%t.dump”. (% e será o nome do processo e% t será a hora do sistema)
  7. Saia e digite “sysctl -p” para carregar a nova configuração Verifique/proc/sys/kernel/core_pattern e verifique se isso corresponde ao que você acabou de digitar.
  8. O despejo do núcleo pode ser testado executando um processo na linha de comando ("&") e, em seguida, matando-o com "kill -11". Se o despejo do core for bem sucedido, você verá “(core dumping)” após a indicação de falha de segmentação.
2
enthusiasticgeek

No Linux/unix/MacOSX use arquivos principais (você pode ativá-los com ulimit ou chamada de sistema compatível ). No Windows, use o relatório de erros da Microsoft (você pode se tornar um parceiro e obter acesso aos dados de falha do seu aplicativo).

1
Kasprzol

Se você ainda quiser ir sozinho como eu fiz, você pode linkar contra bfd e evitar usar addr2line como eu fiz aqui:

https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c

Isso produz a saída:

[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

Eu esqueci sobre a tecnologia GNOME de "apport", mas eu não sei muito sobre como usá-lo. Ele é usado para gerar rastreamentos de pilha e outros diagnósticos para processamento e pode arquivar bugs automaticamente. Certamente vale a pena fazer check-in.

0
Joseph

Se o seu programa falhar, é o próprio sistema operacional que gera informações de despejo de memória. Se você estiver usando um sistema operacional * nix, você simplesmente não deve impedi-lo de fazê-lo (confira as opções 'coredump' do comando ulimit).

0
nsayer

Parece que em uma das últimas versões do c ++ boost apareceu biblioteca para fornecer exatamente o que você quer, provavelmente o código seria multiplataforma. É boost :: stacktrace , que você pode usar como como na amostra de reforço :

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

No Linux você compila o código acima:

g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

Exemplo de backtrace copiado de boost documentation :

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