Como fazer backtrace () / backtrace_symbols () imprimir os nomes das funções?

90

O Linux específico backtrace()e backtrace_symbols()permite que você produza um rastreamento de chamada do programa. No entanto, ele apenas imprime endereços de função, não seus nomes para meu programa. Como posso fazer com que eles também imprimam os nomes das funções? Tentei compilar o programa com -go também -ggdb. O caso de teste abaixo apenas imprime isso:

    BACKTRACE ------------
    ./a.out () [0x8048616]
    ./a.out () [0x8048623]
    /lib/libc.so.6(__libc_start_main+0xf3) [0x4a937413]
    ./a.out () [0x8048421]
    ----------------------
    

Eu gostaria que os 2 primeiros itens também mostrassem os nomes das funções, fooemain

Código:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) && (errno != EINTR))
                        break;

                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

void print_backtrace(void)
{
        static const char start[] = "BACKTRACE ------------\n";
        static const char end[] = "----------------------\n";

        void *bt[1024];
        int bt_size;
        char **bt_syms;
        int i;

        bt_size = backtrace(bt, 1024);
        bt_syms = backtrace_symbols(bt, bt_size);
        full_write(STDERR_FILENO, start, strlen(start));
        for (i = 1; i < bt_size; i++) {
                size_t len = strlen(bt_syms[i]);
                full_write(STDERR_FILENO, bt_syms[i], len);
                full_write(STDERR_FILENO, "\n", 1);
        }
        full_write(STDERR_FILENO, end, strlen(end));
    free(bt_syms);
}
void foo()
{
    print_backtrace();
}

int main()
{
    foo();
    return 0;
}
Lyke
fonte
possível duplicata de How to get backtrace mais detalhado
Nemo
A propósito, a função backtrace_symbols_fdexecuta a mesma operação que backtrace_symbols(), mas as strings resultantes são imediatamente gravadas no descritor de arquivo fd.
nhnghia
Testei vários métodos detalhadamente em: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

63

Os símbolos são retirados da tabela de símbolos dinâmicos; você precisa da -rdynamicopção para gcc, o que o faz passar um sinalizador para o vinculador que garante que todos os símbolos sejam colocados na tabela.

(Consulte a página Opções de link do manual GCC e / ou a página Backtraces do manual glibc .)

Matthew Slattery
fonte
10
Porém, isso não funciona para símbolos estáticos. libunwindque @Nemo menciona, funciona para funções estáticas.
Nathan Kidd
Em um projeto CMake, isso foi definido por engano como ADD_COMPILE_OPTIONS(-rdynamic). Em vez disso, ele precisa ser ADD_LINK_OPTIONS(-rdynamic)ou equivalente para ter o comportamento desejado.
Stéphane
30

Use o comando addr2line para mapear endereços executáveis ​​para o nome do arquivo do código-fonte + número da linha. Dê a -fopção de obter nomes de funções também.

Como alternativa, tente o libunwind .

Nemo
fonte
3
A linha addr2 é boa porque inclui o nome do arquivo e o número da linha na saída, mas falha assim que as realocações são realizadas pelo carregador.
Erwan Legrand
... e com ASLR as realocações são mais comuns do que nunca.
Erwan Legrand
@ErwanLegrand: Antes do ASLR, eu pensava que as realocações eram previsíveis e addr2linefuncionavam de forma confiável até mesmo para endereços em objetos compartilhados (?) Mas sim, em plataformas modernas você precisaria saber o endereço de carga real do objeto realocável até mesmo para fazer esta operação em princípio .
Nemo
Não tenho certeza de como funcionava bem antes do ASLR. Hoje em dia, só servirá para código dentro de executáveis ​​"regulares" e falhará para código dentro de DSOs e PIEs (Executáveis ​​Independentes de Posição). Felizmente, o libunwind parece funcionar para todos eles.
Erwan Legrand
Eu tinha me esquecido dessa discussão. Libbacktrace, que eu não conhecia na época, é uma opção melhor. (Veja minha nova resposta.)
Erwan Legrand
11

O excelente Libbacktrace de Ian Lance Taylor resolve esse problema. Ele lida com o desenrolar da pilha e oferece suporte a símbolos ELF comuns e símbolos de depuração DWARF.

Libbacktrace não exige a exportação de todos os símbolos, o que seria feio, e o ASLR não o quebra.

Libbacktrace era originalmente parte da distribuição GCC. Agora, uma versão autônoma pode ser encontrada no Github:

https://github.com/ianlancetaylor/libbacktrace

Erwan Legrand
fonte
2

a resposta no topo tem um bug se ret == -1 e errno for EINTER você deve tentar novamente, mas não contar ret como copiado (não vou fazer uma conta apenas para isso, se você não gosta de ser difícil)

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) {
                        if (errno != EINTR))
                                break;
                        //else
                        continue;
                }
                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}
Billg
fonte
0

Boost backtrace

Muito conveniente porque imprime ambos:

  • nomes de função C ++ não manipulados
  • números de linha

automaticamente para você.

Resumo de uso:

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

std::cout << boost::stacktrace::stacktrace() << std::endl;

Eu forneci um exemplo mínimo executável para ele e muitos outros métodos em: imprimir pilha de chamadas em C ou C ++

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
A pergunta é marcada como [c], não [c ++].
Yakov Galka