Comment faire en sorte que backtrace () / backtrace_symbols () imprime les noms des fonctions?

90

Le spécifique Linux backtrace()et backtrace_symbols()vous permet de produire une trace d'appel du programme. Cependant, il n'imprime que les adresses de fonction, pas leurs noms pour mon programme. Comment puis-je leur faire imprimer également les noms des fonctions? J'ai essayé de compiler le programme avec -gainsi que -ggdb. Le cas de test ci-dessous imprime simplement ceci:

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

Je voudrais que les 2 premiers éléments affichent également les noms des fonctions, fooetmain

Code:

#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
la source
btw, la fonction backtrace_symbols_fdeffectue la même opération que backtrace_symbols(), mais les chaînes résultantes sont immédiatement écrites dans le descripteur de fichierfd .
nhnghia
J'ai testé plusieurs méthodes en détail sur: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

63

Les symboles sont tirés de la table des symboles dynamiques; vous avez besoin de l' -rdynamicoption to gcc, qui lui fait passer un drapeau à l'éditeur de liens qui garantit que tous les symboles sont placés dans la table.

(Voir la page Options de lien du manuel GCC et / ou la page Backtraces du manuel de la glibc .)

Matthew Slattery
la source
10
Cela ne fonctionne pas pour les symboles statiques, cependant. libunwindque @Nemo mentionne, fonctionne pour les fonctions statiques.
Nathan Kidd
Dans un projet CMake, cela avait été défini par erreur comme ADD_COMPILE_OPTIONS(-rdynamic). Au lieu de cela, il doit être ADD_LINK_OPTIONS(-rdynamic)ou équivalent pour avoir le comportement souhaité.
Stéphane
30

Utilisez la commande addr2line pour mapper les adresses exécutables sur le nom de fichier du code source + le numéro de ligne. Donner la-f possibilité d'obtenir des noms de fonctions.

Sinon, essayez libunwind .

Nemo
la source
3
La ligne addr2 est agréable car elle inclut le nom du fichier et le numéro de ligne dans la sortie, mais elle échoue dès que les déplacements sont effectués par le chargeur.
Erwan Legrand
... et avec l'ASLR, les délocalisations sont plus courantes que jamais.
Erwan Legrand
@ErwanLegrand: Avant ASLR, je pensais que les délocalisations étaient prévisibles et addr2linefonctionnaient de manière fiable même pour les adresses dans des objets partagés (?) Mais oui, sur les plates-formes modernes, vous auriez besoin de connaître l'adresse de charge réelle de l'objet déplaçable même pour effectuer cette opération en principe .
Nemo
Je ne peux pas dire avec certitude à quel point cela fonctionnait avant l'ASLR. De nos jours, cela ne fonctionnera que pour le code à l'intérieur des exécutables "normaux" et échouera pour le code à l'intérieur des DSO et des PIE (exécutables indépendants de position). Heureusement, libunwind semble fonctionner pour tout cela.
Erwan Legrand
J'avais oublié cette discussion. Libbacktrace, que je ne connaissais pas à l'époque, est une meilleure option. (Voir ma nouvelle réponse.)
Erwan Legrand
11

L'excellent Libbacktrace d'Ian Lance Taylor résout ce problème. Il gère le déroulement de la pile et prend en charge les symboles ELF ordinaires et les symboles de débogage DWARF.

Libbacktrace ne nécessite pas d'exporter tous les symboles, ce qui serait laid, et ASLR ne le casse pas.

Libbacktrace faisait à l'origine partie de la distribution GCC. Maintenant, une version autonome peut être trouvée sur Github:

https://github.com/ianlancetaylor/libbacktrace

Erwan Legrand
la source
2

la réponse en haut a un bogue si ret == -1 et errno est EINTER, vous devriez réessayer, mais ne comptez pas ret comme copié (vous ne ferez pas de compte juste pour cela, si vous n'aimez pas ça difficile)

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
la source
0

Boost backtrace

Très pratique car il imprime à la fois:

  • noms de fonction C ++ démêlés
  • numéros de ligne

automatiquement pour vous.

Résumé de l'utilisation:

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

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

J'ai fourni un exemple minimal exécutable pour cela et de nombreuses autres méthodes à: print pile d'appels en C ou C ++

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
La question est marquée [c], pas [c ++].
Yakov Galka