Estou trabalhando no Linux com o compilador GCC. Quando meu programa C ++ falha, eu gostaria que ele gerasse automaticamente um rastreamento de pilha.
Meu programa está sendo executado por muitos usuários diferentes e também é executado no Linux, Windows e Macintosh (todas as versões são compiladas usando gcc
).
Gostaria que meu programa pudesse gerar um rastreamento de pilha quando ele falhar e, na próxima vez em que o usuário o executar, ele perguntará se está OK para enviar o rastreamento de pilha para mim, para que eu possa rastrear o problema. Eu posso lidar com o envio das informações para mim, mas não sei como gerar a sequência de rastreamento. Alguma ideia?
Respostas:
Para Linux e acredito que o Mac OS X, se você estiver usando gcc ou qualquer compilador que use glibc, poderá usar as funções backtrace ()
execinfo.h
para imprimir um stacktrace e sair normalmente quando houver uma falha de segmentação. A documentação pode ser encontrada no manual libc .Aqui está um exemplo de programa que instala um
SIGSEGV
manipulador e imprime um rastreamento de pilha parastderr
quando ele é segmentado por falha. Abaz()
função aqui causa o segfault que aciona o manipulador:Compilar com
-g -rdynamic
você obtém informações de símbolo em sua saída, que a glibc pode usar para criar um bom rastreamento de pilha:Ao executar isso, você obtém esta saída:
Isso mostra o módulo de carregamento, o deslocamento e a 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
main
, além demain
,foo
,bar
, ebaz
.fonte
sigaction()
da libc. Embora seu backtrace pareça estar correto, às vezes descobri que são necessárias etapas adicionais para garantir que a localização real da falha apareça no backtrace, pois ela pode ser substituídasigaction()
pelo kernel.catchsegv
não é o que o OP precisa, mas é incrível para detectar falhas de segmentação e obter todas as informações.É ainda mais fácil do que "man backtrace", há uma biblioteca pouco documentada (específica para GNU) distribuída com glibc como libSegFault.so, que eu acredito que foi escrita por Ulrich Drepper para dar suporte ao programa catchsegv (consulte "man catchsegv").
Isso nos dá três possibilidades. Em vez de executar o "programa -o hai":
Execute no catchsegv:
Vincule-se ao libSegFault no tempo de execução:
Link com libSegFault em tempo de compilação:
Nos três casos, você obterá retornos 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:
A saída será mais ou menos assim (observe o backtrace na parte inferior):
Se você deseja conhecer os detalhes sangrentos, infelizmente, a melhor fonte é a fonte: Consulte 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
fonte
-Wl,--no-as-needed
aos sinalizadores do compilador. Caso contrário, deld
fato não será vinculadolibSegFault
, porque reconhece que o binário não usa nenhum de seus símbolos.Linux
Embora o uso de backtrace () funcione em execinfo.h para imprimir um stacktrace e sair normalmente quando você receber uma falha de segmentação, já foi sugerido , não vejo menção aos meandros necessários para garantir que o backtrace resultante aponte para o local real de a falha (pelo menos para algumas arquiteturas - x86 e ARM).
As duas primeiras entradas na cadeia de quadros da 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 que o sinal (que é o local da falha) seja perdido.
Código
Resultado
Todos os riscos de chamar as funções backtrace () em um manipulador de sinal ainda existem e não devem ser ignorados, mas acho a funcionalidade que descrevi aqui bastante útil na depuração de falhas.
É importante observar que o exemplo que forneci foi desenvolvido / testado no Linux para x86. Eu também implementei isso com sucesso no ARM usando
uc_mcontext.arm_pc
vez deuc_mcontext.eip
.Aqui está um link para o artigo em que aprendi os detalhes desta implementação: http://www.linuxjournal.com/article/6391
fonte
-rdynamic
para instruir o vinculador a adicionar todos os símbolos, não apenas os usados, à tabela de símbolos dinâmicos. Isso permitebacktrace_symbols()
converter endereços em nomes de funçõesaddr2line
comando de alguma forma para obter a linha exata onde ocorreu a falha?glibc
uc_mcontext
não contém um campo chamadoeip
. Agora existe uma matriz que precisa ser indexada,uc_mcontext.gregs[REG_EIP]
é o equivalente.Embora tenha sido fornecida uma resposta correta que descreva como usar a
backtrace()
função GNU libc 1 e forneça minha própria resposta que descreve como garantir que um retorno de um manipulador de sinal aponte para o local real da falha 2 , não vejo qualquer menção à retirada dos símbolos C ++ do backtrace.Ao obter backtraces de um programa C ++, a saída pode ser executada através de
c++filt
1 para desmembrar os símbolos ou usando 1 diretamente.abi::__cxa_demangle
c++filt
e__cxa_demangle
são específicos ao GCCO exemplo a seguir do C ++ Linux usa o mesmo manipulador de sinal da minha outra resposta e demonstra como
c++filt
pode ser usado para desmembrar os símbolos.Código :
Saída (
./test
):Saída desmontada (
./test 2>&1 | c++filt
):O seguinte baseia-se no manipulador de sinal da minha 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 desmontada que o exemplo acima.Código :
fonte
std::cerr
,free()
eexit()
todos violam as restrições contra chamando chamadas não assíncrona de sinal-seguro em sistemas POSIX. Este código irá impasse se o seu processo falhar em qualquer chamada, comofree()
,malloc()
new
, oudetete
.Pode valer a pena olhar para o Google Breakpad , um gerador de despejo de plataforma e ferramentas para processar os despejos.
fonte
Você não especificou seu sistema operacional, portanto, isso é difícil de responder. Se você estiver usando um sistema baseado no gnu libc, poderá usar a função libc
backtrace()
.O GCC também possui dois recursos 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 querem um nível inteiro imediato (por imediato, quero dizer, não pode ser uma variável). Se__builtin_frame_address
um determinado nível for diferente de zero, deve ser seguro pegar o endereço de retorno do mesmo nível.fonte
Obrigado a entusiasticgeek 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:
Código:
fonte
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 seusulimit
valores comulimit -a
.Além disso, se você executar o programa a partir do gdb, ele interromperá o programa por "violações de segmentação" (
SIGSEGV
geralmente quando você acessa 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 o gdb, o que facilita muito o trabalho com o novato.
fonte
É importante observar que, depois de gerar um arquivo principal, você precisará usar a ferramenta gdb para analisá-lo. Para que o gdb compreenda 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:
Então, você pode definir "ulimit -c unlimited" para deixá-lo despejar um núcleo ou apenas executar seu programa dentro do gdb. Eu gosto mais da segunda abordagem:
Eu espero que isso ajude.
fonte
gdb
diretamente do seu programa de falha. Manipulador de instalação para SIGSEGV, SEGILL, SIGBUS, SIGFPE que chamará gdb. Detalhes: stackoverflow.com/questions/3151779/… A vantagem é que você obtém um backtrace bonito e anotado, comobt full
também pode obter rastreamentos de pilha de todos os threads.Eu estive olhando para este problema por um tempo.
E enterrado 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/
Gostaria de ouvir opiniões desta biblioteca.
O problema com -rdynamic é que ele pode aumentar o tamanho do binário de forma relativamente significativa em alguns casos
fonte
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 a libunwind há muito tempo para obter rastreamentos de pilha, mas pode não ser suportado na sua plataforma.
fonte
Você pode usar DeathHandler - classe C ++ pequena, que faz tudo por você, confiável.
fonte
execlp()
para executar chamadas addr2line ... seria bom permanecer totalmente no próprio programa (o que é possível incluindo o código addr2line de alguma forma)Esqueça de mudar 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 funcionando corretamente, aconselho:
Isso imprimirá o rastreamento 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, essa abordagem dará a você a liberdade de automatizar seu sistema: tenha um script curto que verifique se o processo criou um dump principal e envie backtraces por email aos desenvolvedores, ou faça o logon em algum sistema de log.
fonte
é uma variável do sistema, que permitirá criar um dump principal após a falha do aplicativo. Neste caso, uma quantidade ilimitada. Procure um arquivo chamado core no mesmo diretório. Certifique-se de compilar seu código com as informações de depuração ativadas!
Saudações
fonte
limit coredumpsize unlimited
Olhe para a:
homem 3 backtrace
E:
Estas são extensões GNU.
fonte
Consulte o recurso Rastreamento de pilha no ACE (ADAPTIVE Communication Environment). Já foi escrito para cobrir todas as principais plataformas (e mais). A biblioteca é licenciada no estilo BSD, para que você possa copiar / colar o código se não quiser usar o ACE.
fonte
Eu posso ajudar com a versão Linux: as funções backtrace, backtrace_symbols e backtrace_symbols_fd podem ser usadas. Veja as páginas de manual correspondentes.
fonte
Parece que em uma das últimas versões da biblioteca de c ++ apareceu a biblioteca para fornecer exatamente o que você deseja, provavelmente o código seria multiplataforma. É boost :: stacktrace , que você pode usar como na amostra boost :
No Linux, você compila o código acima:
Exemplo de backtrace copiado da documentação do impulso :
fonte
* nix: você pode interceptar o SIGSEGV (normalmente esse sinal é disparado 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 isso no msdn.
Você também pode olhar o código do Google Chrome para ver como ele lida com falhas. Ele tem um bom mecanismo de manipulação de exceções.
fonte
Descobri que a solução @tgamblin não está completa. Ele não pode lidar com o stackoverflow. Eu acho que porque, por padrão, o manipulador de sinal é chamado com a mesma pilha e o SIGSEGV é acionado 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 certo.
fonte
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
fonte
Eu usaria o código que gera um rastreamento de pilha para vazamento de memória no Visual Leak Detector . Isso funciona apenas no Win32, no entanto.
fonte
Eu já vi muitas respostas aqui realizando um manipulador de sinal e saindo. Esse é o caminho a seguir, mas lembre-se de um fato muito importante: se você deseja obter o dump principal do erro gerado, não pode ligar
exit(status)
. Ligue emabort()
vez disso!fonte
Como uma solução apenas para Windows, você pode obter o equivalente a um rastreamento de pilha (com muito, muito mais informações) usando o Relatório de Erros do Windows . Com apenas algumas entradas do Registro, ele pode ser configurado para coletar despejos no modo de usuário :
Você pode definir as entradas do registro no seu instalador, que possui os privilégios necessários.
A criação de um dump no modo de usuário tem as seguintes vantagens sobre a geração de um rastreamento de pilha no cliente:
Observe que o WER só pode ser acionado por uma falha no aplicativo (ou seja, o sistema encerra um processo devido a uma exceção não tratada).
MiniDumpWriteDump
pode ser chamado a qualquer momento. Isso pode ser útil se você precisar despejar o estado atual para diagnosticar problemas que não sejam uma falha.Leitura obrigatória, se você quiser avaliar a aplicabilidade de mini despejos:
fonte
Além das respostas acima, aqui como você faz o Debian Linux OS gerar dump principal
fonte
Se você ainda quiser seguir sozinho, como eu, pode vincular
bfd
e evitar o usoaddr2line
como fiz aqui:https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c
Isso produz a saída:
fonte
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 aplicativo).
fonte
Eu esqueci a tecnologia GNOME de "apport", mas não sei muito sobre usá-la. É usado para gerar rastreamentos de pilha e outros diagnósticos para processamento e pode arquivar bugs automaticamente. Certamente vale a pena conferir.
fonte