Come fare in modo che backtrace () / backtrace_symbols () stampi i nomi delle funzioni?


90

Lo specifico per Linux backtrace()e backtrace_symbols()consente di produrre una traccia delle chiamate del programma. Tuttavia, stampa solo gli indirizzi delle funzioni, non i loro nomi per il mio programma. Come posso fare in modo che stampino anche i nomi delle funzioni? Ho provato a compilare il programma con -gcosì come -ggdb. Il test case seguente stampa solo questo:

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

Vorrei che i primi 2 elementi mostrassero anche i nomi delle funzioni fooemain

Codice:

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


btw, la funzione backtrace_symbols_fdesegue la stessa operazione di backtrace_symbols(), ma le stringhe risultanti vengono immediatamente scritte nel descrittore di file fd.
nhnghia

Risposte:


63

I simboli sono presi dalla tabella dinamica dei simboli; hai bisogno -rdynamicdell'opzione per gcc, che gli fa passare un flag al linker che assicura che tutti i simboli siano posizionati nella tabella.

(Vedere la pagina Opzioni di collegamento del manuale di GCC e / o la pagina Backtraces del manuale di glibc .)


10
Tuttavia, questo non funziona per i simboli statici. libunwindche @Nemo menziona, funziona per funzioni statiche.
Nathan Kidd

In un progetto CMake, questo era stato erroneamente impostato come ADD_COMPILE_OPTIONS(-rdynamic). Invece, deve essere ADD_LINK_OPTIONS(-rdynamic)o equivalente per avere il comportamento desiderato.
Stéphane

30

Utilizzare il comando addr2line per mappare gli indirizzi eseguibili al codice sorgente nomefile + numero di riga. Dare la -fpossibilità di ottenere anche i nomi delle funzioni.

In alternativa, prova libunwind .


3
La riga addr2 è utile perché include il nome del file e il numero di riga nell'output, ma fallisce non appena i riposizionamenti vengono eseguiti dal caricatore.
Erwan Legrand

... e con ASLR i trasferimenti sono più comuni che mai.
Erwan Legrand

@ErwanLegrand: prima di ASLR, pensavo che i trasferimenti fossero prevedibili e addr2linefunzionassero in modo affidabile anche per gli indirizzi in oggetti condivisi (?) Ma sì, sulle piattaforme moderne avresti bisogno di conoscere l'effettivo indirizzo di carico dell'oggetto rilocabile anche per fare questa operazione in linea di principio .
Nemo

Non posso dire con certezza come funzionasse bene prima dell'ASLR. Al giorno d'oggi, funzionerà solo per il codice all'interno di eseguibili "regolari" e fallirà per il codice all'interno di DSO e PIE (Position Independent Executables). Fortunatamente, libunwind sembra funzionare per tutti questi.
Erwan Legrand

Mi ero dimenticato di questa discussione. Libbacktrace, che all'epoca non conoscevo, è un'opzione migliore. (Vedi la mia nuova risposta.)
Erwan Legrand

11

L'eccellente Libbacktrace di Ian Lance Taylor risolve questo problema. Gestisce lo svolgimento dello stack e supporta sia i simboli ELF ordinari che i simboli di debug DWARF.

Libbacktrace non richiede l'esportazione di tutti i simboli, il che sarebbe brutto, e ASLR non lo rompe.

Libbacktrace faceva originariamente parte della distribuzione GCC. Ora, una versione standalone può essere trovata su Github:

https://github.com/ianlancetaylor/libbacktrace


2

la risposta in alto ha un bug se ret == -1 e errno è EINTER dovresti riprovare, ma non contare ret come copiato (non creerai un account solo per questo, se non ti piace duro)

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

0

Aumenta il backtrace

Molto comodo perché stampa sia:

  • nomi di funzioni C ++ non confusi
  • numeri di riga

automaticamente per te.

Riepilogo utilizzo:

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

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

Ho fornito un esempio minimo eseguibile per questo e molti altri metodi su: print stack di chiamate in C o C ++


La domanda è contrassegnata con [c], non [c ++].
Yakov Galka
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.