web-dev-qa-db-fra.com

Comment obtenir une trace de pile pour C ++ en utilisant gcc avec des informations sur le numéro de ligne?

Nous utilisons des traces de pile dans une macro propriétaire assert comme pour détecter les erreurs du développeur - lorsqu'une erreur est détectée, la trace de pile est imprimée.

Je trouve que les méthodes de paire backtrace()/backtrace_symbols() de gcc sont insuffisantes:

  1. Les noms sont mutilés
  2. Aucune information de ligne

Le 1er problème peut être résolu par abi :: __ cxa_demangle .

Mais le 2ème problème est plus difficile. J'ai trouvé remplacement de backtrace_symbols () . C'est mieux que backtrace_symbols () de gcc, car il peut récupérer les numéros de ligne (s'il est compilé avec -g) et vous n'avez pas besoin de compiler avec -rdynamic.

Hoverer le code est sous licence GNU, donc à mon humble avis, je ne peux pas l'utiliser dans le code commercial.

Une proposition?

P.S.

gdb est capable d'imprimer les arguments passés aux fonctions. C'est probablement déjà trop demander :)

PS 2

Question similaire (merci nobar)

57
dimba

Il n'y a pas si longtemps j'ai répondu à une question similaire . Vous devriez jeter un œil au code source disponible sur la méthode # 4, qui imprime également les numéros de ligne et les noms de fichiers.

  • Méthode n ° 4:

Une petite amélioration que j'ai faite sur la méthode # 3 pour imprimer les numéros de ligne. Cela pourrait être copié pour fonctionner également avec la méthode # 2.

Fondamentalement, il utilise addr2line pour convertir les adresses en noms de fichiers et en numéros de ligne.

Le code source ci-dessous imprime les numéros de ligne pour toutes les fonctions locales. Si une fonction d'une autre bibliothèque est appelée, vous pourriez voir quelques ??:0 au lieu des noms de fichiers.

#include <stdio.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>
#include <execinfo.h>

void bt_sighandler(int sig, struct sigcontext ctx) {

  void *trace[16];
  char **messages = (char **)NULL;
  int i, trace_size = 0;

  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, ctx.cr2, ctx.eip);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *)ctx.eip;
  messages = backtrace_symbols(trace, trace_size);
  /* skip first stack frame (points here) */
  printf("[bt] Execution path:\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] #%d %s\n", i, messages[i]);

    /* find first occurence of '(' or ' ' in message[i] and assume
     * everything before that is the file name. (Don't go beyond 0 though
     * (string terminator)*/
    size_t p = 0;
    while(messages[i][p] != '(' && messages[i][p] != ' '
            && messages[i][p] != 0)
        ++p;

    char syscom[256];
    sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
        //last parameter is the file name of the symbol
    system(syscom);
  }

  exit(0);
}


int func_a(int a, char b) {

  char *p = (char *)0xdeadbeef;

  a = a + b;
  *p = 10;  /* CRASH here!! */

  return 2*a;
}


int func_b() {

  int res, a = 5;

  res = 5 + func_a(a, 't');

  return res;
}


int main() {

  /* Install our signal handler */
  struct sigaction sa;

  sa.sa_handler = (void *)bt_sighandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;

  sigaction(SIGSEGV, &sa, NULL);
  sigaction(SIGUSR1, &sa, NULL);
  /* ... add any other signal here */

  /* Do something */
  printf("%d\n", func_b());
}

Ce code doit être compilé comme: gcc sighandler.c -o sighandler -rdynamic

Le programme produit:

Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0
28
karlphillip

Vous voulez donc une fonction autonome qui imprime une trace de pile avec toutes les fonctionnalités des traces de pile gdb et qui ne met pas fin à votre application. La réponse est d'automatiser le lancement de gdb dans un mode non interactif pour effectuer uniquement les tâches que vous souhaitez.

Cela se fait en exécutant gdb dans un processus enfant, en utilisant fork () et en le scriptant pour afficher une trace de pile pendant que votre application attend qu'elle se termine. Cela peut être effectué sans utiliser de vidage de mémoire et sans abandonner l'application. J'ai appris à le faire en regardant cette question: Comment est-il préférable d'invoquer gdb à partir d'un programme pour imprimer son stacktrace?

L'exemple publié avec cette question n'a pas fonctionné pour moi exactement comme écrit, alors voici ma version "fixe" (je l'ai exécutée sur Ubuntu 9.04).

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    int child_pid = fork();
    if (!child_pid) {           
        dup2(2,1); // redirect output to stderr
        fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
        execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
        abort(); /* If gdb failed to start */
    } else {
        waitpid(child_pid,NULL,0);
    }
}

Comme indiqué dans la question référencée, gdb fournit des options supplémentaires que vous pouvez utiliser. Par exemple, l'utilisation de "bt full" au lieu de "bt" produit un rapport encore plus détaillé (des variables locales sont incluses dans la sortie). Les pages de manuel de gdb sont assez claires, mais une documentation complète est disponible ici .

Comme cela est basé sur gdb, la sortie comprend les noms démangés , les numéros de ligne , arguments de la fonction , et éventuellement même variables locales . De plus, gdb est compatible avec les threads, vous devriez donc être en mesure d'extraire des métadonnées spécifiques aux threads.

Voici un exemple du type de traces de pile que je vois avec cette méthode.

0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0  0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1  0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2  0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3  0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4  0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5  0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70

Remarque: J'ai trouvé cela incompatible avec l'utilisation de valgrind (probablement en raison de l'utilisation par Valgrind d'une machine virtuelle). Cela ne fonctionne pas non plus lorsque vous exécutez le programme à l'intérieur d'une session gdb (vous ne pouvez pas appliquer une seconde instance de "ptrace" à un processus).

37
nobar

Il y a une discussion robuste sur essentiellement la même question à: Comment générer un stacktrace lorsque mon application gcc C++ plante . De nombreuses suggestions sont fournies, y compris de nombreuses discussions sur la façon de générer des traces de pile au moment de l'exécution.

Ma réponse préférée personnelle à partir de ce fil était d'activer core dumps qui vous permet de voir l'état complet de l'application au moment du crash (y compris les arguments de fonction, les numéros de ligne et les noms démêlés). Un avantage supplémentaire de cette approche est qu'elle fonctionne non seulement pour affirme, mais aussi pour défauts de segmentation et exceptions non gérées .

Différents shells Linux utilisent différentes commandes pour activer les vidages de mémoire, mais vous pouvez le faire à partir de votre code d'application avec quelque chose comme ça ...

#include <sys/resource.h>
...
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds

Après un plantage, exécutez votre débogueur préféré pour examiner l'état du programme.

$ kdbg executable core

Voici quelques exemples de sortie ...

alt text

Il est également possible d'extraire la trace de pile à partir d'un vidage de mémoire sur la ligne de commande.

$ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core )
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 22857]
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#1  0x00007f4189be7bc3 in abort () from /lib/libc.so.6
#2  0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6
#3  0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18
#4  0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19
#5  0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19
#6  0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19
#7  0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19
#8  0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19
#9  0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26
11
nobar

Utilisez la bibliothèque Google Glog pour cela. Il a une nouvelle licence BSD.

Il contient une fonction GetStackTrace dans le fichier stacktrace.h.

[~ # ~] modifier [~ # ~]

J'ai trouvé ici http://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/ qu'il existe un utilitaire appelé addr2line qui traduit les adresses des programmes en noms de fichiers et numéros de ligne.

http://linuxcommand.org/man_pages/addr2line1.html

Étant donné que le code sous licence GPL est destiné à vous aider pendant le développement, vous ne pouvez tout simplement pas l'inclure dans le produit final. La GPL vous empêche de distribuer du code de licences GPL lié à un code non compatible GPL. Tant que vous n'utilisez que le code GPL en interne, ça devrait aller.

6
KeithB

Voici une approche alternative. Une macro debug_assert () définit par programme un point d'arrêt conditionnel . Si vous utilisez un débogueur, vous atteindrez un point d'arrêt lorsque l'expression d'assertion est fausse - et vous pouvez analyser la pile en direct (le programme ne se termine pas). Si vous n'êtes pas en cours d'exécution dans un débogueur, un debug_assert () échoué provoque l'abandon du programme et vous obtenez un vidage de mémoire à partir duquel vous pouvez analyser la pile (voir ma réponse précédente).

L'avantage de cette approche, par rapport aux assertions normales, est que vous pouvez continuer à exécuter le programme après le déclenchement de debug_assert (lors de l'exécution dans un débogueur). En d'autres termes, debug_assert () est légèrement plus flexible que assert ().

   #include <iostream>
   #include <cassert>
   #include <sys/resource.h> 

// note: The assert expression should show up in
// stack trace as parameter to this function
void debug_breakpoint( char const * expression )
   {
   asm("int3"); // x86 specific
   }

#ifdef NDEBUG
   #define debug_assert( expression )
#else
// creates a conditional breakpoint
   #define debug_assert( expression ) \
      do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0)
#endif

void recursive( int i=0 )
   {
   debug_assert( i < 5 );
   if ( i < 10 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
   setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
   recursive();
   }

Remarque: Parfois, la configuration des "points d'arrêt conditionnels" dans les débogueurs peut être lente. En établissant le point d'arrêt par programme, les performances de cette méthode doivent être équivalentes à celles d'un assert normal ().

Remarque: Tel qu'écrit, cela est spécifique à l'architecture Intel x86 - d'autres processeurs peuvent avoir des instructions différentes pour générer un point d'arrêt.

5
nobar

Un peu tard, mais vous pouvez utiliser libbfb pour récupérer le nom de fichier et le numéro de lin comme refdbg le fait dans symsnarf.c . libbfb est utilisé en interne par addr2line et gdb

4
rve

L'une des solutions consiste à démarrer une gdb avec le script "bt" dans le gestionnaire d'assertions défaillant. Il n'est pas très facile d'intégrer un tel démarrage gdb, mais cela vous donnera à la fois des noms de trace et d'args et de démêlage (ou vous pouvez passer la sortie gdb via le programme c ++ filt).

Les deux programmes (gdb et c ++ filt) ne seront pas liés à votre application, donc GPL ne vous obligera pas à ouvrir une application complète.

La même approche (exécutez un programme GPL) que vous pouvez utiliser avec des symboles de trace. Il suffit de générer la liste ascii de% eip et la carte du fichier exec (/ proc/self/maps) et de la transmettre à un fichier binaire séparé.

2
osgx

Vous pouvez utiliser DeathHandler - petite classe C++ qui fait tout pour vous, fiable.

2
markhor

Je suppose que les numéros de ligne sont liés à la valeur eip actuelle, non?

SOLUTION 1:
Ensuite, vous pouvez utiliser quelque chose comme GetThreadContext () , sauf que vous travaillez sur Linux. J'ai cherché un peu sur Google et j'ai trouvé quelque chose de similaire, ptrace () :

L'appel système ptrace () fournit un moyen par lequel un processus parent peut observer et contrôler l'exécution d'un autre processus, et examiner et modifier son image principale et ses registres. [...] Le parent peut initier une trace en appelant fork (2) et en faisant faire à l'enfant résultant un PTRACE_TRACEME, suivi (typiquement) par un exec (3). Alternativement, le parent peut commencer la trace d'un processus existant à l'aide de PTRACE_ATTACH.

Maintenant, je pensais, vous pouvez faire un programme "principal" qui vérifie les signaux envoyés à son enfant, le vrai programme sur lequel vous travaillez. après fork() il appelle waitid () :

Tous ces appels système sont utilisés pour attendre les changements d'état d'un enfant du processus appelant et obtenir des informations sur l'enfant dont l'état a changé.

et si un SIGSEGV (ou quelque chose de similaire) est intercepté, appelez ptrace() pour obtenir la valeur de eip.

PS: Je n'ai jamais utilisé ces appels système (enfin, en fait, je ne les ai jamais vus auparavant;) donc je ne sais pas si c'est possible et je ne peux pas vous aider. J'espère au moins que ces liens sont utiles. ;)

SOLUTION 2: La première solution est assez compliquée, non? J'en ai trouvé une bien plus simple: utiliser signal () capturer les signaux qui vous intéressent et appeler une fonction simple qui lit la valeur eip stockée dans la pile:

...
signal(SIGSEGV, sig_handler);
...

void sig_handler(int signum)
{
    int eip_value;

    asm {
        Push eax;
        mov eax, [ebp - 4]
        mov eip_value, eax
        pop eax
    }

    // now you have the address of the
    // **next** instruction after the
    // SIGSEGV was received
}

Cette syntaxe asm est celle de Borland, il suffit de l'adapter à GAS. ;)

1
BlackBear

Voici ma troisième réponse - essayant toujours de profiter des vidages de mémoire.

La question de savoir si les macros "assert-like" étaient censées mettre fin à l'application (comme le fait assert) n'était pas tout à fait claire ou si elles devaient continuer à s'exécuter après avoir généré leur trace de pile.

Dans cette réponse, j'aborde le cas où vous souhaitez afficher une trace de pile et continuer à exécuter. J'ai écrit la fonction coredump () ci-dessous pour générer un core dump, extraire automatiquement le stack-trace, puis continuer à exécuter le programme.

L'utilisation est la même que celle de assert (). La différence, bien sûr, est que assert () termine le programme mais coredump_assert () ne le fait pas.

   #include <iostream>
   #include <sys/resource.h> 
   #include <cstdio>
   #include <cstdlib>
   #include <boost/lexical_cast.hpp>
   #include <string>
   #include <sys/wait.h>
   #include <unistd.h>

   std::string exename;

// expression argument is for diagnostic purposes (shows up in call-stack)
void coredump( char const * expression )
   {

   pid_t childpid = fork();

   if ( childpid == 0 ) // child process generates core dump
      {
      rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
      setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
      abort(); // terminate child process and generate core dump
      }

// give each core-file a unique name
   if ( childpid > 0 ) waitpid( childpid, 0, 0 );
   static int count=0;
   using std::string;
   string pid = boost::lexical_cast<string>(getpid());
   string newcorename = "core-"+boost::lexical_cast<string>(count++)+"."+pid;
   string rawcorename = "core."+boost::lexical_cast<string>(childpid);
   int rename_rval = rename(rawcorename.c_str(),newcorename.c_str()); // try with core.PID
   if ( rename_rval == -1 ) rename_rval = rename("core",newcorename.c_str()); // try with just core
   if ( rename_rval == -1 ) std::cerr<<"failed to capture core file\n";

  #if 1 // optional: dump stack trace and delete core file
   string cmd = "( CMDFILE=$(mktemp); echo 'bt' >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} "+exename+" "+newcorename+" ; unlink ${CMDFILE} )";
   int system_rval = system( ("bash -c '"+cmd+"'").c_str() );
   if ( system_rval == -1 ) std::cerr.flush(), perror("system() failed during stack trace"), fflush(stderr);
   unlink( newcorename.c_str() );
  #endif

   }

#ifdef NDEBUG
   #define coredump_assert( expression ) ((void)(expression))
#else
   #define coredump_assert( expression ) do { if ( !(expression) ) { coredump( #expression ); } } while (0)
#endif

void recursive( int i=0 )
   {
   coredump_assert( i < 2 );
   if ( i < 4 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   exename = argv[0]; // this is used to generate the stack trace
   recursive();
   }

Lorsque j'exécute le programme, il affiche trois traces de pile ...

Core was generated by `./temp.exe'.                                         
Program terminated with signal 6, Aborted.
[New process 24251]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=2) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#6  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24259]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=3) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#6  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#7  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24267]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=4) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=3) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#6  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#7  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#8  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
1
nobar

AFAICS toutes les solutions fournies jusqu'à présent n'imprimeront pas les noms des fonctions et les numéros de ligne des bibliothèques partagées. C'est ce dont j'avais besoin, j'ai donc modifié la solution de karlphillip (et une autre réponse d'une question similaire) pour résoudre les adresses de bibliothèque partagée en utilisant/proc/id/maps.

#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <execinfo.h>
#include <stdbool.h>

struct Region { // one mapped file, for example a shared library
    uintptr_t start;
    uintptr_t end;
    char* path;
};

static struct Region* getRegions(int* size) { 
// parse /proc/self/maps and get list of mapped files 
    FILE* file;
    int allocated = 10;
    *size = 0;
    struct Region* res;
    uintptr_t regionStart = 0x00000000;
    uintptr_t regionEnd = 0x00000000;
    char* regionPath = "";
    uintmax_t matchedStart;
    uintmax_t matchedEnd;
    char* matchedPath;

    res = (struct Region*)malloc(sizeof(struct Region) * allocated);
    file = fopen("/proc/self/maps", "r");
    while (!feof(file)) {
        fscanf(file, "%jx-%jx %*s %*s %*s %*s%*[ ]%m[^\n]\n",  &matchedStart, &matchedEnd, &matchedPath);
        bool bothNull = matchedPath == 0x0 && regionPath == 0x0;
        bool similar = matchedPath && regionPath && !strcmp(matchedPath, regionPath);
        if(bothNull || similar) {
            free(matchedPath);
            regionEnd = matchedEnd;
        } else {
            if(*size == allocated) {
                allocated *= 2;
                res = (struct Region*)realloc(res, sizeof(struct Region) * allocated);
            }

            res[*size].start = regionStart;
            res[*size].end = regionEnd;
            res[*size].path = regionPath;
            (*size)++;
            regionStart = matchedStart;
            regionEnd = matchedEnd;
            regionPath = matchedPath;
        }
    }
    return res;
}

struct SemiResolvedAddress {
    char* path;
    uintptr_t offset;
};
static struct SemiResolvedAddress semiResolve(struct Region* regions, int regionsNum, uintptr_t address) {
// convert address from our address space to
// address suitable fo addr2line 
    struct Region* region;
    struct SemiResolvedAddress res = {"", address};
    for(region = regions; region < regions+regionsNum; region++) {
        if(address >= region->start && address < region->end) {
            res.path = region->path;
            res.offset = address - region->start;
        }
    }
    return res;
}

void printStacktraceWithLines(unsigned int max_frames)
{
    int regionsNum;
    fprintf(stderr, "stack trace:\n");

    // storage array for stack trace address data
    void* addrlist[max_frames+1];

    // retrieve current stack addresses
    int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
    if (addrlen == 0) {
        fprintf(stderr, "  <empty, possibly corrupt>\n");
        return;
    }
    struct Region* regions = getRegions(&regionsNum); 
    for (int i = 1; i < addrlen; i++)
    {
        struct SemiResolvedAddress hres =
                semiResolve(regions, regionsNum, (uintptr_t)(addrlist[i]));
        char syscom[256];
        sprintf(syscom, "addr2line -C -f -p -a -e %s 0x%jx", hres.path, (intmax_t)(hres.offset));
        system(syscom);
    }
    free(regions);
}
0
Nikita

voici ma solution:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <zconf.h>
#include "regex"

std::string getexepath() {
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
}

std::string sh(std::string cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
    if (!pipe) throw std::runtime_error("popen() failed!");
    while (!feof(pipe.get())) {
        if (fgets(buffer.data(), 128, pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    return result;
}


void print_backtrace(void) {
    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;

    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    std::regex re("\\[(.+)\\]");
    auto exec_path = getexepath();
    for (i = 1; i < bt_size; i++) {
        std::string sym = bt_syms[i];
        std::smatch ms;
        if (std::regex_search(sym, ms, re)) {
            std::string addr = ms[1];
            std::string cmd = "addr2line -e " + exec_path + " -f -C " + addr;
            auto r = sh(cmd);
            std::regex re2("\\n$");
            auto r2 = std::regex_replace(r, re2, "");
            std::cout << r2 << std::endl;
        }
    }
    free(bt_syms);
}

void test_m() {
    print_backtrace();
}

int main() {
    test_m();
    return 0;
}

sortie:

/home/roroco/Dropbox/c/ro-c/cmake-build-debug/ex/test_backtrace_with_line_number
test_m()
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:57
main
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:61
??
??:0

"??" et "??: 0" puisque cette trace est dans libc, pas dans ma source

0
asullaherc