c++ - processus - trace de la pile




Comment générer automatiquement une pile quand mon programme gcc C++ se bloque (19)

Linux

Alors que l'utilisation de la fonction backtrace () dans execinfo.h pour imprimer une pile et quitter gracieusement lorsque vous obtenez une erreur de segmentation a déjà été suggérée , je ne vois aucune mention des subtilités nécessaires pour assurer que les traces de retour résultantes à l'emplacement réel de la faute (au moins pour certaines architectures - x86 & ARM).

Les deux premières entrées de la chaîne de cadres de pile lorsque vous entrez dans le gestionnaire de signal contiennent une adresse de retour dans le gestionnaire de signal et une dans sigaction () dans libc. Le cadre de pile de la dernière fonction appelée avant le signal (qui est l'emplacement de la panne) est perdu.

Code

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 struct ucontext   *uc_link;
 stack_t           uc_stack;
 struct sigcontext uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)\n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

Sortie

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

Tous les dangers d'appeler les fonctions backtrace () dans un gestionnaire de signal existent toujours et ne devraient pas être négligés, mais je trouve la fonctionnalité que j'ai décrite ici très utile dans le débogage des plantages.

Il est important de noter que l'exemple que j'ai fourni est développé / testé sous Linux pour x86. Je l'ai également implémenté avec succès sur ARM en utilisant uc_mcontext.arm_pc au lieu de uc_mcontext.eip .

Voici un lien vers l'article où j'ai appris les détails de cette implémentation: http://www.linuxjournal.com/article/6391

Quand mon programme C ++ se bloque, je voudrais qu'il génère automatiquement une pile.

Mon programme est exécuté par de nombreux utilisateurs différents et fonctionne également sous Linux, Windows et Macintosh (toutes les versions sont compilées avec gcc ).

Je voudrais que mon programme soit capable de générer une trace de pile quand il tombe en panne et la prochaine fois que l'utilisateur l'exécute, il leur demandera s'il est correct de m'envoyer la trace de pile pour que je puisse localiser le problème. Je peux gérer l'envoi de l'information à moi mais je ne sais pas comment générer la chaîne de trace. Des idées?


* nix: vous pouvez intercepter SIGSEGV (habituellement ce signal est déclenché avant de s'écraser) et conserver l'information dans un fichier. (à part le fichier core que vous pouvez utiliser pour déboguer en utilisant gdb par exemple).

win: Cochez this case à partir de msdn.

Vous pouvez également regarder le code chrome de Google pour voir comment il gère les plantages. Il a un bon mécanisme de gestion des exceptions.



Il est important de noter qu'une fois que vous générez un fichier core, vous devrez utiliser l'outil gdb pour le voir. Pour que gdb donne un sens à votre fichier core, vous devez indiquer à gcc d'instrumenter le binaire avec les symboles de débogage: pour ce faire, vous compilez avec l'option -g:

$ g++ -g prog.cpp -o prog

Ensuite, vous pouvez définir "ulimit -c unlimited" pour le laisser vider un core, ou simplement lancer votre programme dans gdb. J'aime la deuxième approche plus:

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

J'espère que ça aide.


J'ai trouvé que la solution @tgamblin n'est pas complète. Il ne peut pas gérer avec . Je pense que par défaut le gestionnaire de signal est appelé avec la même pile et SIGSEGV est lancé deux fois. Pour protéger, vous devez enregistrer une pile indépendante pour le gestionnaire de signal.

Vous pouvez vérifier cela avec le code ci-dessous. Par défaut, le gestionnaire échoue. Avec la macro définie STACK_OVERFLOW, tout va bien.

#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <cassert>

using namespace std;

//#define STACK_OVERFLOW

#ifdef STACK_OVERFLOW
static char stack_body[64*1024];
static stack_t sigseg_stack;
#endif

static struct sigaction sigseg_handler;

void handler(int sig) {
  cerr << "sig seg fault handler" << endl;
  const int asize = 10;
  void *array[asize];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, asize);

  // print out all the frames to stderr
  cerr << "stack trace: " << endl;
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  cerr << "resend SIGSEGV to get core dump" << endl;
  signal(sig, SIG_DFL);
  kill(getpid(), sig);
}

void foo() {
  foo();
}

int main(int argc, char **argv) {
#ifdef STACK_OVERFLOW
  sigseg_stack.ss_sp = stack_body;
  sigseg_stack.ss_flags = SS_ONSTACK;
  sigseg_stack.ss_size = sizeof(stack_body);
  assert(!sigaltstack(&sigseg_stack, nullptr));
  sigseg_handler.sa_flags = SA_ONSTACK;
#else
  sigseg_handler.sa_flags = SA_RESTART;  
#endif
  sigseg_handler.sa_handler = &handler;
  assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
  cout << "sig action set" << endl;
  foo();
  return 0;
} 

J'ai vu beaucoup de réponses ici en train d'exécuter un gestionnaire de signal puis de sortir. C'est la voie à suivre, mais rappelez-vous un fait très important: Si vous voulez obtenir le vidage du noyau pour l'erreur générée, vous ne pouvez pas appeler exit(status) . Appelez abort() place!


Je peux aider avec la version Linux: la fonction backtrace, backtrace_symbols et backtrace_symbols_fd peuvent être utilisées. Voir les pages de manuel correspondantes.


Le nouveau roi en ville est arrivé https://github.com/bombela/backward-cpp

1 en-tête à placer dans votre code et 1 bibliothèque à installer.

Personnellement, je l'appelle en utilisant cette fonction

#include "backward.hpp"
void stacker() {

using namespace backward;
StackTrace st;


st.load_here(99); //Limit the number of trace depth to 99
st.skip_n_firsts(3);//This will skip some backward internal function from the trace

Printer p;
p.snippet = true;
p.object = true;
p.color = true;
p.address = true;
p.print(st, stderr);
}

Merci à enthousiasticgeek d'avoir attiré mon attention sur l'utilitaire addr2line.

J'ai écrit un script rapide et sale pour traiter la sortie de la réponse fournie here : (merci beaucoup à jschmier!) En utilisant l'utilitaire addr2line.

Le script accepte un seul argument: Le nom du fichier contenant la sortie de l'utilitaire jschmier.

La sortie devrait imprimer quelque chose comme le suivant pour chaque niveau de la trace:

BACKTRACE:  testExe 0x8A5db6b
FILE:       pathToFile/testExe.C:110
FUNCTION:   testFunction(int) 
   107  
   108           
   109           int* i = 0x0;
  *110           *i = 5;
   111      
   112        }
   113        return i;

Code:

#!/bin/bash

LOGFILE=$1

NUM_SRC_CONTEXT_LINES=3

old_IFS=$IFS  # save the field separator           
IFS=$'\n'     # new field separator, the end of line           

for bt in `cat $LOGFILE | grep '\[bt\]'`; do
   IFS=$old_IFS     # restore default field separator 
   printf '\n'
   EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`  
   ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
   echo "BACKTRACE:  $EXEC $ADDR"
   A2L=`addr2line -a $ADDR -e $EXEC -pfC`
   #echo "A2L:        $A2L"

   FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
   FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
   echo "FILE:       $FILE_AND_LINE"
   echo "FUNCTION:   $FUNCTION"

   # print offending source code
   SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
   LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
   if ([ -f $SRCFILE ]); then
      cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
   else
      echo "File not found: $SRCFILE"
   fi
   IFS=$'\n'     # new field separator, the end of line           
done

IFS=$old_IFS     # restore default field separator 

Oubliez de changer vos sources et faites des hacks avec des fonctions backtrace () ou des macroses - ce ne sont que des solutions médiocres.

En tant que solution fonctionnant correctement, je conseillerais:

  1. Compilez votre programme avec l'option "-g" pour incorporer les symboles de débogage dans binary (ne vous inquiétez pas, cela n'affectera pas vos performances).
  2. Sur linux, exécutez la commande suivante: "ulimit -c unlimited" - pour permettre au système de faire de gros vidages sur incident.
  3. Lorsque votre programme plante, dans le répertoire de travail, vous verrez le fichier "core".
  4. Exécutez la commande suivante pour imprimer backtrace sur stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Cela imprimera une trace lisible de votre programme de manière lisible par l'utilisateur (avec les noms de fichier source et les numéros de ligne). De plus, cette approche vous donnera la liberté d'automatiser votre système: ayez un court script qui vérifie si le processus a créé un core dump, puis envoyez les backtraces par email aux développeurs, ou connectez-le dans un système de journalisation.


Pour Linux et Mac OS X, si vous utilisez gcc, ou tout compilateur qui utilise glibc, vous pouvez utiliser les fonctions backtrace () dans execinfo.h pour imprimer une pile et quitter normalement lorsque vous obtenez une erreur de segmentation. La documentation peut être trouvée dans le manuel de libc .

Voici un exemple de programme qui installe un gestionnaire SIGSEGV et imprime un stacktrace sur stderr lorsqu'il segmente. La fonction baz() provoque ici le segfault qui déclenche le gestionnaire:

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


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

Compiler avec -g -rdynamic vous donne des informations sur les symboles dans votre sortie, que glibc peut utiliser pour créer une belle pile:

$ gcc -g -rdynamic ./test.c -o test

L'exécution de ceci vous obtient cette sortie:

$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]

Cela montre le module de chargement, le décalage et la fonction dont provient chaque image de la pile. Ici vous pouvez voir le gestionnaire de signal sur le dessus de la pile, et les fonctions de libc avant main en plus de main , foo , bar et baz .


Regarder:

homme 3 backtrace

Et:

#include <exeinfo.h>
int backtrace(void **buffer, int size);

Ce sont des extensions GNU.


Vous n'avez pas spécifié votre système d'exploitation, il est donc difficile de répondre. Si vous utilisez un système basé sur gnu libc, vous pourrez peut-être utiliser la fonction libc backtrace() .

GCC a également deux builtins qui peuvent vous aider, mais qui peuvent être implémentés ou non sur votre architecture, et ceux-ci sont __builtin_frame_address et __builtin_return_address . Les deux qui veulent un niveau entier immédiat (par immédiat, je veux dire qu'il ne peut pas être une variable). Si __builtin_frame_address de __builtin_frame_address pour un niveau donné, il convient de saisir l'adresse de retour du même niveau en toute sécurité.


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


ulimit -c <value> définit la limite de taille de fichier core sur unix. Par défaut, la limite de taille de fichier de base est 0. Vous pouvez voir vos valeurs ulimit -a avec ulimit -a .

De même, si vous exécutez votre programme depuis gdb, il arrêtera votre programme sur les "violations de segmentation" ( SIGSEGV , généralement lorsque vous avez accédé à une partie de la mémoire que vous n'aviez pas allouée) ou vous pouvez définir des points d'arrêt.

ddd et nemiver sont des frontaux pour gdb ce qui rend le travail beaucoup plus facile pour le novice.


As a Windows-only solution, you can get the equivalent of a stack trace (with much, much more information) using WER . With just a few registry entries, it can be set up to collect user-mode dumps :

Starting with Windows Server 2008 and Windows Vista with Service Pack 1 (SP1), Windows Error Reporting (WER) can be configured so that full user-mode dumps are collected and stored locally after a user-mode application crashes. [...]

This feature is not enabled by default. Enabling the feature requires administrator privileges. To enable and configure the feature, use the following registry values under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps key.

You can set the registry entries from your installer, which has the required privileges.

Creating a user-mode dump has the following advantages over generating a stack trace on the client:

  • It's already implemented in the system. You can either use WER as outlined above, or call MiniDumpWriteDump yourself, if you need more fine-grained control over the amount of information to dump. (Make sure to call it from a different process.)
  • Way more complete than a stack trace. Among others it can contain local variables, function arguments, stacks for other threads, loaded modules, and so on. The amount of data (and consequently size) is highly customizable.
  • No need to ship debug symbols. This both drastically decreases the size of your deployment, as well as makes it harder to reverse-engineer your application.
  • Largely independent of the compiler you use. Using WER does not even require any code. Either way, having a way to get a symbol database (PDB) is very useful for offline analysis. I believe GCC can either generate PDB's, or there are tools to convert the symbol database to the PDB format.

Notez que WER ne peut être déclenché que par un crash de l'application (c'est-à-dire que le système termine un processus en raison d'une exception non gérée). MiniDumpWriteDumppeut être appelé à tout moment. Cela peut être utile si vous devez vider l'état actuel pour diagnostiquer des problèmes autres qu'un plantage.

Lecture obligatoire, si vous souhaitez évaluer l'applicabilité des mini-dumps:


If your program crashes, it's the operating system itself that generates crash dump information. If you're using a *nix OS, you simply need to not prevent it from doing so (check out the ulimit command's 'coredump' options).


In addition to above answers, here how you make Debian Linux OS generate core dump

  1. Create a “coredumps” folder in the user's home folder
  2. Go to /etc/security/limits.conf. Below the ' ' line, type “ soft core unlimited”, and “root soft core unlimited” if enabling core dumps for root, to allow unlimited space for core dumps.
  3. NOTE: “* soft core unlimited” does not cover root, which is why root has to be specified in its own line.
  4. To check these values, log out, log back in, and type “ulimit -a”. “Core file size” should be set to unlimited.
  5. Check the .bashrc files (user, and root if applicable) to make sure that ulimit is not set there. Otherwise, the value above will be overwritten on startup.
  6. Open /etc/sysctl.conf. Enter the following at the bottom: “kernel.core_pattern = /home//coredumps/%e_%t.dump”. (%e will be the process name, and %t will be the system time)
  7. Exit and type “sysctl -p” to load the new configuration Check /proc/sys/kernel/core_pattern and verify that this matches what you just typed in.
  8. Core dumping can be tested by running a process on the command line (“ &”), and then killing it with “kill -11 ”. If core dumping is successful, you will see “(core dumped)” after the segmentation fault indication.

ulimit -c unlimited

est une variable système, qui va permettre de créer un core dump après le crash de votre application. Dans ce cas, un montant illimité. Recherchez un fichier appelé core dans le même répertoire. Assurez-vous de compiler votre code avec les informations de débogage activées!

Cordialement





assert