Existe alguma maneira de despejar a pilha de chamadas em um processo em execução em C ou C ++ toda vez que uma determinada função é chamada? O que tenho em mente é algo assim:
void foo()
{
print_stack_trace();
// foo's body
return
}
Onde print_stack_trace
funciona de forma semelhante ao caller
Perl.
Ou algo parecido com isto:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
onde register_stack_trace_function
coloca algum tipo de ponto de interrupção interno que fará com que um rastreamento de pilha seja impresso sempre que foo
for chamado.
Algo assim existe em alguma biblioteca C padrão?
Estou trabalhando no Linux, usando GCC.
fundo
Eu tenho um teste executado que se comporta de maneira diferente com base em algumas opções de linha de comando que não devem afetar esse comportamento. Meu código tem um gerador de números pseudo-aleatórios que suponho que seja chamado de maneira diferente com base nessas opções. Quero ser capaz de executar o teste com cada conjunto de opções e ver se o gerador de números aleatórios é chamado de maneira diferente para cada um.
s/easier/either/
como diabos isso aconteceu?s/either/easier
. O que eu precisaria fazer com o gdb é escrever um script que interrompa essa função e imprima o rastreamento da pilha e, em seguida, continue. Agora que pensei sobre isso, talvez seja a hora de aprender sobre scripts gdb.Respostas:
Para uma solução somente Linux, você pode usar backtrace (3) que simplesmente retorna um array de
void *
(na verdade, cada um deles aponta para o endereço de retorno do frame de pilha correspondente). Para traduzir isso em algo útil, há backtrace_symbols (3) .Preste atenção à seção de notas em backtrace (3) :
fonte
glibc
, infelizmente, asbacktrace_symbols
funções não fornecem o nome da função, o nome do arquivo de origem e o número da linha.-rdynamic
, verifique também se o seu sistema de compilação não adiciona-fvisibility=hidden
opções! (pois descartará completamente o efeito de-rdynamic
)Boost stacktrace
Documentado em: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
Esta é a opção mais conveniente que vi até agora, porque:
pode realmente imprimir os números das linhas.
No entanto , ele apenas faz ligações para
addr2line
, o que é feio e pode ser lento se você estiver registrando muitos rastros.demangles por padrão
Boost é apenas o cabeçalho, então não há necessidade de modificar seu sistema de compilação provavelmente
boost_stacktrace.cpp
Infelizmente, parece ser uma adição mais recente, e o pacote
libboost-stacktrace-dev
não está presente no Ubuntu 16.04, apenas 18.04:Temos que adicionar
-ldl
no final ou então a compilação falhará.Resultado:
A saída é explicada com mais detalhes na seção "glibc backtrace" abaixo, que é análoga.
Observe como
my_func_1(int)
emy_func_1(float)
, que estão mutilados devido à sobrecarga de função , foram bem demangled para nós.Observe que a primeira
int
chamada está desativada por uma linha (28 em vez de 27 e a segunda está desativada por duas linhas (27 em vez de 29). Foi sugerido nos comentários que isso ocorre porque o seguinte endereço de instrução está sendo considerado, o que faz com que 27 se tornem 28, e 29 pulem do loop e se tornem 27.Em seguida, observamos que com
-O3
, a saída é completamente mutilada:Backtraces são em geral irreparavelmente mutilados por otimizações. A otimização da chamada final é um exemplo notável disso: O que é a otimização da chamada final?
Teste de referência executado em
-O3
:Resultado:
Como esperado, vemos que esse método é extremamente lento para chamadas externas
addr2line
, e só será viável se um número limitado de chamadas estiver sendo feito.Cada impressão de backtrace parece levar centenas de milissegundos, portanto, esteja avisado de que se um backtrace acontecer com muita frequência, o desempenho do programa sofrerá significativamente.
Testado no Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.
glibc
backtrace
Documentado em: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
main.c
Compilar:
-rdynamic
é a opção chave necessária.Corre:
Saídas:
Assim, vemos imediatamente que uma otimização inlining aconteceu e algumas funções foram perdidas no rastreamento.
Se tentarmos obter os endereços:
nós obtemos:
que está completamente desligado.
Se fizermos o mesmo com
-O0
,./main.out
fornece o rastreamento completo correto:e depois:
dá:
então as linhas estão erradas por apenas uma, TODO por quê? Mas ainda pode ser usado.
Conclusão: backtraces só podem ser mostrados perfeitamente com
-O0
. Com otimizações, o backtrace original é fundamentalmente modificado no código compilado.Não consegui encontrar uma maneira simples de remover automaticamente os símbolos C ++ com isso, no entanto, aqui estão alguns truques:
Testado no Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace_symbols_fd
Este auxiliar é um pouco mais conveniente
backtrace_symbols
e produz uma saída basicamente idêntica:Testado no Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace
com C ++ demangling hack 1:-export-dynamic
+dladdr
Adaptado de: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Este é um "hack" porque requer a troca do ELF com
-export-dynamic
.glibc_ldl.cpp
Compile e execute:
resultado:
Testado no Ubuntu 18.04.
glibc
backtrace
com C ++ demangling hack 2: parse backtrace outputExibido em: https://panthema.net/2008/0901-stacktrace-demangled/
Este é um hack porque requer análise.
TODO pegue para compilar e mostre aqui.
Libunwind
TODO isso tem alguma vantagem sobre o backtrace glibc? Uma saída muito semelhante também requer a modificação do comando build, mas não faz parte da glibc, portanto, requer uma instalação de pacote extra.
Código adaptado de: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
main.c
Compile e execute:
Ou
#define _XOPEN_SOURCE 700
deve estar no topo, ou devemos usar-std=gnu99
:Corre:
Resultado:
e:
dá:
Com
-O0
:e:
dá:
Testado no Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind com eliminação de nomes C ++
Código adaptado de: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
descontrair.cpp
Compile e execute:
Resultado:
e então podemos encontrar as linhas de
my_func_2
emy_func_1(int)
com:que dá:
TODO: por que as linhas estão desligadas por um?
Testado no Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
Automação GDB
Também podemos fazer isso com GDB sem recompilar usando: Como fazer uma ação específica quando um determinado ponto de interrupção é atingido no GDB?
Embora se você for imprimir muito o backtrace, provavelmente será menos rápido do que as outras opções, mas talvez possamos alcançar velocidades nativas com
compile code
, mas estou com preguiça de testá-lo agora: Como chamar assembly no gdb?main.cpp
main.gdb
Compile e execute:
Resultado:
TODO Eu queria fazer isso apenas
-ex
na linha de comando para não precisar criar,main.gdb
mas não consegui fazer ocommands
funcionar lá.Testado no Ubuntu 19.04, GDB 8.2.
Kernel Linux
Como imprimir o rastreamento de pilha de thread atual dentro do kernel Linux?
libdwfl
Isso foi mencionado originalmente em: https://stackoverflow.com/a/60713161/895245 e pode ser o melhor método, mas eu tenho que fazer um benchmark um pouco mais, mas vá votar a favor dessa resposta.
TODO: Eu tentei minimizar o código dessa resposta, que estava funcionando, para uma única função, mas é segfaulting, me diga se alguém descobrir o porquê.
dwfl.cpp
Compile e execute:
Resultado:
Teste de referência:
Resultado:
Portanto, vemos que esse método é 10 vezes mais rápido do que o rastreamento de pilha do Boost e, portanto, pode ser aplicável a mais casos de uso.
Testado no Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.
Veja também
fonte
Não existe uma maneira padronizada de fazer isso. Para Windows, a funcionalidade é fornecida na biblioteca DbgHelp
fonte
Você pode usar uma função macro em vez da instrução de retorno na função específica.
Por exemplo, em vez de usar return,
Você pode usar uma função macro.
Sempre que ocorrer um erro em uma função, você verá a pilha de chamadas no estilo Java, conforme mostrado abaixo.
O código-fonte completo está disponível aqui.
C-callstack em https://github.com/Nanolat
fonte
Outra resposta a um velho tópico.
Quando preciso fazer isso, normalmente uso
system()
epstack
Então, algo assim:
Isso produz
Isso deve funcionar no Linux, FreeBSD e Solaris. Não acho que o macOS tenha pstack ou um equivalente simples, mas este tópico parece ter uma alternativa .
Se estiver usando
C
, você precisará usarC
funções de string.Eu usei 7 para o número máximo de dígitos no PID, com base neste post .
fonte
Específico para Linux, TLDR:
backtrace
inglibc
produz rastreamentos de pilha precisos apenas quando-lunwind
está vinculado (recurso específico da plataforma não documentado).#include <elfutils/libdwfl.h>
(esta biblioteca está documentada apenas em seu arquivo de cabeçalho).backtrace_symbols
ebacktrace_symbolsd_fd
são menos informativos.No Linux moderno, você pode obter os endereços de rastreamento de pilha usando a função
backtrace
. A maneira não documentada debacktrace
produzir endereços mais precisos em plataformas populares é vincular com-lunwind
(libunwind-dev
no Ubuntu 18.04) (veja o exemplo de saída abaixo).backtrace
usa a função_Unwind_Backtrace
e, por padrão, a última vemlibgcc_s.so.1
e essa implementação é mais portátil. Quando-lunwind
está vinculado, ele fornece uma versão mais precisa do,_Unwind_Backtrace
mas esta biblioteca é menos portátil (consulte as arquiteturas suportadas emlibunwind/src
).Infelizmente, o companheiro
backtrace_symbolsd
ebacktrace_symbols_fd
funções não foram capazes de resolver os endereços de rastreamento de pilha para nomes de função com nome de arquivo de origem e número de linha por provavelmente uma década (veja o exemplo de saída abaixo).No entanto, existe outro método para resolver endereços para símbolos e produz os traços mais úteis com o nome da função , arquivo de origem e número da linha . O método é para
#include <elfutils/libdwfl.h>
e link com-ldw
(libdw-dev
no Ubuntu 18.04).Exemplo funcional de C ++ (
test.cc
):Compilado no Ubuntu 18.04.4 LTS com gcc-8.3:
Saídas:
Quando nenhum
-lunwind
está vinculado, ele produz um rastreamento de pilha menos preciso:Para comparação, a
backtrace_symbols_fd
saída para o mesmo rastreamento de pilha é menos informativa:Em uma versão de produção (bem como a versão em linguagem C), você pode querer tornar este código mais robusto, substituindo
boost::core::demangle
,std::string
estd::cout
com as suas chamadas subjacentes.Você também pode substituir
__cxa_throw
para capturar o rastreamento de pilha quando uma exceção é lançada e imprimi-lo quando a exceção é detectada. No momento em que ele entra nocatch
bloco, a pilha foi desfeita, então é tarde demais para chamarbacktrace
, e é por isso que a pilha deve ser capturadathrow
e implementada por função__cxa_throw
. Observe que em um programa multi-threaded__cxa_throw
pode ser chamado simultaneamente por vários threads, de modo que se ele captura o stacktrace em um array global que deve serthread_local
.fonte
-lunwind
problema foi descoberto ao fazer este post, eu usei anteriormentelibunwind
diretamente para obter o stacktrace e ia postá-lo, masbacktrace
faz isso para mim quando-lunwind
está vinculado.gcc
não expõe a API, certo?Você mesmo pode implementar a funcionalidade:
Use uma pilha global (string) e no início de cada função coloque o nome da função e outros valores (por exemplo, parâmetros) nesta pilha; ao sair da função, abra-o novamente.
Escreva uma função que imprimirá o conteúdo da pilha quando for chamada e use-a na função onde deseja ver a pilha de chamadas.
Isso pode parecer muito trabalhoso, mas é bastante útil.
fonte
call_registror MY_SUPERSECRETNAME(__FUNCTION__);
que empurra o argumento em seu construtor e ativa seu destruidor FUNCTION sempre representa o nome da função atual.Claro que a próxima pergunta é: isso será suficiente?
A principal desvantagem dos rastreamentos de pilha é que porque você tem a função precisa sendo chamada, você não tem mais nada, como o valor de seus argumentos, que é muito útil para depuração.
Se você tiver acesso a gcc e gdb, sugiro usar
assert
para verificar uma condição específica e produzir um despejo de memória se não for atendido. Claro, isso significa que o processo será interrompido, mas você terá um relatório completo em vez de um mero rastreamento de pilha.Se desejar uma maneira menos intrusiva, você sempre pode usar o registro. Existem instalações de extração de madeira muito eficientes, como a Pantheios, por exemplo. O que, mais uma vez, pode dar uma imagem muito mais precisa do que está acontecendo.
fonte
Você pode usar Poppy para isso. Normalmente é usado para coletar o rastreamento de pilha durante uma falha, mas também pode gerá-lo para um programa em execução.
Agora, aqui está a parte boa: ele pode gerar os valores reais dos parâmetros para cada função na pilha e até mesmo variáveis locais, contadores de loop, etc.
fonte
Eu sei que este tópico é antigo, mas acho que pode ser útil para outras pessoas. Se estiver usando o gcc, você pode usar os recursos do instrumento (opção -finstrument-functions) para registrar qualquer chamada de função (entrada e saída). Dê uma olhada nisso para obter mais informações: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
Você pode, assim, por exemplo, empurrar e colocar todas as chamadas em uma pilha e, quando quiser imprimi-lo, basta olhar o que tem em sua pilha.
Eu testei, funciona perfeitamente e é muito útil
ATUALIZAÇÃO: você também pode encontrar informações sobre a opção de compilação -finstrument-functions no documento GCC sobre as opções de Instrumentação: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
fonte
Você pode usar as bibliotecas Boost para imprimir a pilha de chamadas atual.
Homem aqui: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
fonte
cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dll
no Win10.Você pode usar o GNU Profiler. Também mostra o gráfico de chamadas! o comando é
gprof
e você precisa compilar seu código com alguma opção.fonte
Não, não há, embora possam existir soluções dependentes de plataforma.
fonte