62 votes

Comment il est préférable d'appeler les gdb de programme pour imprimer ses stacktrace?

Maintenant, je suis en utilisant la fonction comme ceci:

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

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "--pid=%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);
    }
}

Et je vois les détails de print_trace dans la sortie.

Quelles sont les autres manières de le faire?

95voto

karlphillip Points 46502

Vous avez mentionné sur mon autre réponse (aujourd'hui supprimée) que vous aussi vous voulez voir les numéros de ligne. Je ne suis pas sûr de la façon de le faire lors de l'invocation de gdb à partir de l'intérieur de votre application.

Mais je vais partager avec vous quelques-unes des façons d'imprimer une simple stacktrace avec les noms de fonction et de leurs numéros de ligne sans l'utilisation de gdb. La plupart d'entre eux sont venus à partir d'un très bel article de Linux Journal:

  • Méthode #1:

La première méthode consiste à diffuser avec d'imprimer et d'enregistrer les messages dans l'ordre pour repérer le chemin d'exécution. Dans un programme complexe, cette option peut devenu lourd et fastidieux, même si, avec l'aide de GCC spécifiques les macros, il peut être simplifié un peu. Considérons, par exemple, une version de débogage de macro tels que:

 #define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                          "() [%s:%d] here I am\n", \
                          __FILE__, __LINE__)

Vous pouvez propager cette macro rapidement tout au long de votre programme par la coupe et le collant. Lorsque vous n'en avez pas besoin de plus, l'éteindre, tout simplement par la définition n'-op.

  • Méthode #2: (Il ne dit rien sur les numéros de ligne, mais je n'sur la méthode 4)

Une jolie manière à obtenir un stack trace, toutefois, pour utiliser certains des soutien spécifique aux fonctions fournies par la glibc. L'une des clés est le backtrace(), qui navigue dans la pile des images à partir de l'appel de point pour le début de le programme et fournit un tableau de des adresses de retour. Ensuite, vous pouvez mapper chaque adresse dans le corps d'un fonction spécifique dans votre code par avoir un coup d'oeil au fichier objet avec le nm de commande. Ou, vous pouvez faire une plus simple d'utilisation backtrace_symbols(). Cette fonction transforme une liste de des adresses de retour, tel que retourné par la backtrace(), dans une liste de chaînes de caractères, chaque contenant le nom de la fonction décalage de la fonction et de la adresse de retour. La liste des chaînes est allouées à partir de votre espace de tas (comme si vous avez appelé la fonction malloc()), de sorte que vous devrait free() dès que vous avez terminé avec c'.

Je vous encourage à le lire depuis la page a le code source des exemples. Afin de convertir une adresse sur un nom de fonction, vous devez compiler votre application avec l' -rdynamic option.

  • Méthode n ° 3: (Une meilleure façon de faire la méthode 2)

Encore plus utiles de l'application pour cette technique est de mettre une pile trace à l'intérieur d'un gestionnaire de signal et avoir le dernier attraper tous les "mauvais" les signaux de votre programme peut recevoir (SIGSEGV, SIGBUS, SIGILL, SIGFPE et la comme). De cette façon, si votre programme malheureusement, les accidents et vous n'étiez pas l'exécution avec un débogueur, vous pouvez obtenir une trace de la pile et de savoir où l' la faute qui s'est passé. Cette technique a également peut être utilisé pour comprendre où votre le programme est une boucle dans le cas où il s'arrête répondre

Une mise en œuvre de cette technique est disponible ici.

  • Méthode n ° 4:

Une petite amélioration, je l'ai fait sur la méthode #3 pour imprimer les numéros de ligne. Cela pourrait être copié à travailler sur la méthode #2 aussi.

En gros, j'ai suivi une astuce qui utilise addr2line à

convertir les adresses en noms de fichiers et de les numéros de ligne.

Le code source ci-dessous imprime les numéros de ligne pour toutes les fonctions. Si une fonction à partir d'une autre bibliothèque est appelée, vous pouvez voir un couple de ??:0 au lieu des noms de fichier.

#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]);

    char syscom[256];
    sprintf(syscom,"addr2line %p -e sighandler", trace[i]); //last parameter is the name of this app
    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 des sorties:

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

Mise à jour 2012/04/28 pour les dernières versions du noyau linux, le ci-dessus sigaction signature est obsolète. Aussi j'ai amélioré un peu en saisissant le nom de l'exécutable à partir de cette réponse. Voici une version à jour:

char* exe = 0;

int initialiseExecutableName() 
{
    char link[1024];
    exe = new char[1024];
    snprintf(link,sizeof link,"/proc/%d/exe",getpid());
    if(readlink(link,exe,sizeof link)==-1) {
        fprintf(stderr,"ERRORRRRR\n");
        exit(1);
    }
    printf("Executable name initialised: %s\n",exe);
}

const char* getExecutableName()
{
    if (exe == 0)
        initialiseExecutableName();
    return exe;
}

/* get REG_EIP from ucontext.h */
#define __USE_GNU
#include <ucontext.h>

void bt_sighandler(int sig, siginfo_t *info,
                   void *secret) {

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

  /* Do something useful with siginfo_t */
  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, info->si_addr, 
           uc->uc_mcontext.gregs[REG_EIP]);
  else
    printf("Got signal %d#92;\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *) uc->uc_mcontext.gregs[REG_EIP];

  messages = backtrace_symbols(trace, trace_size);
  /* skip first stack frame (points here) */
  printf("[bt] Execution path:#92;\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] %s#92;\n", messages[i]);
    char syscom[256];
    sprintf(syscom,"addr2line %p -e %s", trace[i] , getExecutableName() ); //last parameter is the name of this app
    system(syscom);

  }
  exit(0);
}

et initialiser comme ceci:

int main() {

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

  sa.sa_sigaction = (void *)bt_sighandler;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_SIGINFO;

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

  /* Do something */
  printf("%d#92;n", func_b());

}

8voto

Jim Blandy Points 947

Si vous utilisez Linux, la bibliothèque standard C comprend une fonction appelée backtrace, qui remplit un tableau avec les cadres de l'adresse de retour, et une autre fonction appelée backtrace_symbols, qui prendra les adresses à partir d' backtrace et de regarder les noms de fonction. Ceux-ci sont documentés dans la Bibliothèque C de GNU manuel.

Ceux qui n'affiche pas les valeurs d'argument, les lignes de source, et de l'autre, et ils ne s'appliquent que pour le thread appelant. Toutefois, ils devraient être beaucoup plus rapide (et peut-être moins feuilletée) que de courir GDB de cette façon, ils ont donc leur place.

7voto

karlphillip Points 46502

J'ai déjà averti nobar qu'il devrait afficher son fantastique réponse ici, mais depuis il n'a pas bouger un doigt, je vais le faire pour vous les gars:

Donc, vous voulez un stand-alone, fonction qui imprime une trace de la pile avec toutes les fonctionnalités que gdb traces de pile et que ne pas mettre fin à votre application. La réponse est d'automatiser le lancement de gdb dans un mode non-interactif pour exécuter les tâches que vous souhaitez.

Ceci est fait par l'exécution de gdb dans un enfant de processus avec fork(), et un script pour afficher un stack-trace pendant que votre application attend pour la mener à bien. Cela peut être effectué sans l'utilisation d'un core dump et sans l'abandon de la demande.

Je crois que c'est ce que vous cherchez, @Vi

1voto

Motti Points 32921

N'est-ce pas abort() plus simple?

De cette façon, si ça se passe dans le champ de la client peut vous envoyer le fichier de base (je ne connais pas beaucoup d'utilisateurs qui sont assez impliqué dans mon application vouloir me forcer à le débogage).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X