Pergunta

Estou trabalhando no Linux com o compilador GCC.Quando meu programa C++ travar, 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).

Gostaria que meu programa fosse capaz de gerar um rastreamento de pilha quando travar e, na próxima vez que o usuário o executar, ele perguntará se pode enviar o rastreamento de pilha para mim para que eu possa rastrear o problema.Posso lidar com o envio das informações para mim, mas não sei como gerar a string de rastreamento.Alguma ideia?

Foi útil?

Solução

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

Aqui está um exemplo de programa que instala um SIGSEGV manipulador e imprime um stacktrace para stderr quando ele falha.O baz() A função aqui causa 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 fornece informações sobre símbolos em sua saída, que a glibc pode usar para criar um bom stacktrace:

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

Executar isso fornece 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, o deslocamento e a função de onde veio cada quadro na pilha.Aqui você pode ver o manipulador de sinal no topo da pilha e as funções da libc antes main além de main, foo, bar, e baz.

Outras dicas

Linux

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

As duas primeiras entradas na cadeia de quadros 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 de pilha da última função chamada antes do sinal (que é a localização da falha) ser 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 considero a funcionalidade que descrevi aqui bastante útil na depuração de falhas.

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

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

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

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

  1. Execute dentro do catchsegv:

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

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

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

Em todos os três 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ê poderá 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á mais ou menos assim (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 sangrentos, infelizmente a melhor fonte é a fonte:Ver 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=depurar

Mesmo que um resposta correta foi fornecido que descreve como usar o GNU libc backtrace() função1 e eu forneci minha própria resposta que descreve como garantir que um backtrace de um manipulador de sinal aponte para a localização real da falha2, não vejo nenhuma menção a desmontar Saída de símbolos C++ do backtrace.

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

  • 1 Linux e OS XObserve 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 outra resposta e demonstra como c++filt pode ser usado para desmontar os símbolos.

Código:

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

Saída (./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]

Saída desmontada (./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 é baseado 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 desmontar os símbolos.Este manipulador de sinal produz a mesma saída desmantelada do exemplo acima.

Código:

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

Talvez valha a pena dar uma olhada Google Breakpad, um gerador de despejos de memória multiplataforma e ferramentas para processar os despejos.

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

O GCC também possui dois componentes internos que podem ajudá-lo, mas que podem ou não ser totalmente implementados em sua arquitetura, e esses são __builtin_frame_address e __builtin_return_address.Ambos desejam 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 obter o endereço de retorno do mesmo nível.

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

além disso, se você executar seu programa a partir do gdb, ele interromperá seu programa devido a "violações de segmentação" (SIGSEGV, geralmente quando você acessou um pedaço de memória que não havia alocado) ou pode definir pontos de interrupção.

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

Obrigado ao entusiasta por chamar minha atenção para o utilitário addr2line.

Eu escrevi um script rápido e sujo para processar a saída da resposta fornecida aqui:(muito obrigado a 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 jschmier.

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

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 

É importante observar que depois de gerar um arquivo principal, você precisará usar a ferramenta gdb para visualizá-lo.Para que o gdb entenda 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

Então, você pode definir "ulimit -c ilimitado" para permitir o dump de um núcleo ou apenas executar seu programa dentro do gdb.Eu gosto mais da segunda abordagem:

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

Eu espero que isso ajude.

Estou analisando esse problema há algum tempo.

E enterrado profundamente no README das Ferramentas de Desempenho do Google

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

fala sobre libunwind

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

Adoraria ouvir opiniões sobre esta biblioteca.

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

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

Eu lembro de usar libunwind há muito tempo para obter rastreamentos de pilha, mas pode não ser compatível com sua plataforma.

Esqueça a mudança de suas fontes e faça alguns hacks com a função backtrace() ou macroses - essas são apenas soluções ruins.

Como uma solução que funcione corretamente, eu aconselharia:

  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 ilimitado" - para permitir que o sistema faça grandes despejos de memória.
  3. Quando o seu programa travou, no diretório de trabalho você verá o arquivo "core".
  4. Execute o próximo comando para imprimir backtrace em stdout:gdb -batch -ex "backtrace" ./seu_programa_exe ./core

Isso imprimirá o backtrace legível adequado do seu programa de maneira legível por humanos (com nomes de arquivos de origem e números de linha).Além disso, esta abordagem lhe dará liberdade para automatizar seu sistema:tenha um script curto que verifique se o processo criou um core dump e, em seguida, envie backtraces por e-mail para os desenvolvedores ou registre-o em algum sistema de registro.

ulimit -c unlimited

é uma variável de sistema, que permitirá criar um core dump após a falha do seu aplicativo.Neste caso, um valor ilimitado.Procure um arquivo chamado core no mesmo diretório.Certifique-se de compilar seu código com as informações de depuração habilitadas!

cumprimentos

Você pode usar Manipulador da Morte - pequena classe C++ que faz tudo por você, confiável.

Olhe para a:

homem 3 backtrace

E:

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

Estas são extensões GNU.

Consulte o recurso Stack Trace em ÁS (Ambiente de Comunicação ADAPTATIVO).Já foi escrito para cobrir todas as principais plataformas (e mais).A biblioteca é licenciada no estilo BSD, então você pode até copiar/colar o código se não quiser usar o ACE.

Posso ajudar com a versão Linux:as funções backtrace, backtrace_symbols e backtrace_symbols_fd podem ser usadas.Consulte as páginas de manual correspondentes.

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

ganhar:Verificar esse do msdn.

Você também pode consultar o código do Chrome do Google para ver como ele lida com falhas.Possui um bom mecanismo de tratamento de exceções.

Descobri que a solução @tgamblin não está completa.Ele não pode lidar com stackoverflow.Eu acho que porque, por padrão, o manipulador de sinal é chamado com a mesma pilha e o Sigsegv é jogado 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 está 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;
} 

Eu usaria o código que gera um rastreamento de pilha para memória vazada em Detector visual de vazamento.No entanto, isso só funciona no Win32.

Tenho visto muitas respostas aqui executando um manipulador de sinal e depois saindo.Esse é o caminho a seguir, mas lembre-se de um fato muito importante:Se você deseja obter o core dump do erro gerado, não pode chamar exit(status).Chamar abort() em vez de!

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

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

Pessoalmente eu chamo isso usando esta 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);
}

Além das respostas acima, aqui como você faz o sistema operacional Debian Linux 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 ilimitado” e “root soft core ilimitado” se estiver habilitando core dumps para root, para permitir espaço ilimitado para core dumps.
  3. OBSERVAÇÃO:“*soft core ilimitado” não cobre root, e é por isso que root deve ser especificado em sua própria linha.
  4. Para verificar esses valores, efetue logout, 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 ter certeza de que ulimit não está definido lá.Caso contrário, o valor acima será substituído na inicialização.
  6. Abra /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 check/proc/sys/kernel/core_pattern e verifique se isso corresponde ao que você acabou de digitar.
  8. O core dumping pode ser testado executando um processo na linha de comando (“&”) e, em seguida, eliminando-o com “kill -11”.Se o core dump for bem-sucedido, você verá “(core dumped)” após a indicação de falha de segmentação.

Como uma solução somente para Windows, você pode obter o equivalente a um rastreamento de pilha (com muito, muito mais informações) usando Relatório de erros do Windows.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 dumps completos do modo de usuário sejam coletados e armazenados localmente após uma falha do aplicativo no modo de usuário.[...]

Este recurso não está habilitado por padrão.A ativação do recurso requer privilégios de administrador.Para habilitar e configurar o recurso, use os seguintes valores de registro na seção HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows elatório de erros do Windows\LocalDumps chave.

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

A criação de um dump no modo de usuário tem as seguintes vantagens em relação à geração de um rastreamento de pilha no cliente:

  • Já está implementado no sistema.Você pode usar o WER conforme descrito acima ou ligar MiniDumpWriteDump você mesmo, se precisar de um controle mais refinado sobre a quantidade de informações a serem despejadas.(Certifique-se de chamá-lo de um processo diferente.)
  • Caminho mais completo que um rastreamento de pilha.Entre outros, pode conter variáveis ​​locais, argumentos de funções, pilhas para outras 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 da sua implantação e também dificulta a engenharia reversa do seu aplicativo.
  • Em grande parte independente do compilador que você usa.Usar o WER nem requer nenhum código.De qualquer forma, ter uma maneira de obter um banco de dados de símbolos (PDB) é muito útil para análise offline.Acredito que o GCC pode gerar PDBs ou existem ferramentas para converter o banco de dados de símbolos para o formato PDB.

Observe que o WER só pode ser acionado por uma falha no aplicativo (ou seja,o sistema encerrando 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 outros problemas além de uma falha.

Leitura obrigatória, caso queira avaliar a aplicabilidade dos mini dumps:

No Linux/unix/MacOSX use arquivos principais (você pode habilitá-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 falhas do seu aplicativo).

Esqueci a tecnologia GNOME de "apport", mas não sei muito sobre como usá-la.Ele é usado para gerar rastreamentos de pilha e outros diagnósticos para processamento e pode registrar bugs automaticamente.Certamente vale a pena conferir.

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

#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 impulsionar documentação:

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

Se você ainda quiser fazer tudo sozinho como eu fiz, você pode vincular bfd e evite usar addr2line como 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]
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top