GNU/Linux >> Tutoriels Linux >  >> Linux

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

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 mettent 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. Ceci peut être effectué sans l'utilisation d'un core-dump et sans abandonner l'application. J'ai appris à faire cela en regardant cette question :comment est-il préférable d'invoquer gdb à partir du programme pour imprimer son stacktrace ?

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

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/prctl.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;
    prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
    int child_pid = fork();
    if (!child_pid) {
        dup2(2,1); // redirect output to stderr - edit: unnecessary?
        execl("/usr/bin/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é (les variables locales sont incluses dans la sortie). Les pages de manuel de gdb sont plutôt légères, mais une documentation complète est disponible ici.

Comme ceci est basé sur gdb, la sortie inclut des noms démêlés , numéros de ligne , arguments de fonction , et éventuellement même des variables locales . De plus, gdb est sensible aux threads, vous devriez donc pouvoir extraire certaines 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 dans une session gdb (impossible d'appliquer une deuxième instance de "ptrace" à un processus).


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 apportée à la méthode n°3 pour imprimer les numéros de ligne. Cela pourrait être copié pour fonctionner également sur la méthode #2.

Fondamentalement, il utilise addr2line pour convertir les adresses en noms de fichiers et 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 pouvez 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 affiche :

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

Linux
  1. Comment éviter les attaques par écrasement de pile avec GCC

  2. Comment obtenir l'utilisation totale du processeur sous Linux en utilisant C++

  3. Comment écrire une chaîne de plusieurs lignes en utilisant Bash avec des variables ?

  4. Comment compter le nombre d'onglets dans chaque ligne à l'aide d'un script shell ?

  5. Comment imprimer l'heure actuelle (avec millisecondes) en utilisant C++ / C++11

Comment trouver des informations sur le processeur sous Linux à l'aide de la ligne de commande

Comment obtenir des informations sur le matériel sous Linux à l'aide de la commande dmidecode

Comment obtenir les spécifications matérielles de votre système à l'aide de lshw Hardware Lister

Comment utiliser GDB dans Eclipse pour le débogage C/C++ ?

Comment obtenir le nombre de CPU sous Linux en utilisant C ?

Comment remplir un fichier avec FF en utilisant dd ?