Je travaille sur Linux avec le compilateur GCC. Lorsque mon programme C++ se bloque, j'aimerais qu'il génère automatiquement un stacktrace.
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 à l'aide de gcc
).
J'aimerais que mon programme puisse générer une trace de pile lorsqu'il se bloque et que l'utilisateur le exécutera à la prochaine exécution. Il leur demandera s'il est correct de m'envoyer la trace de pile afin que je puisse localiser le problème. Je peux gérer l'envoi des informations mais je ne sais pas comment générer la chaîne de trace. Des idées?
Sous Linux et Mac OS X, si vous utilisez gcc, ou tout compilateur utilisant glibc, vous pouvez utiliser les fonctions backtrace () de execinfo.h
pour imprimer un stacktrace et se terminer 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
quand il segfafaults. 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.
}
En compilant avec -g -rdynamic
, vous obtiendrez des informations sur les symboles dans votre sortie, que glibc peut utiliser pour créer un stacktrace Nice:
$ gcc -g -rdynamic ./test.c -o test
En exécutant ceci, vous obtenez 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]
Ceci montre le module de chargement, le décalage et la fonction dont chaque cadre de la pile est issu. Ici, vous pouvez voir le gestionnaire de signaux en haut de la pile et les fonctions libc avant main
en plus de main
, foo
, bar
et baz
.
Bien que l’utilisation des fonctions backtrace () dans execinfo.h pour imprimer une trace de pile et sortir normalement lorsque vous obtenez une erreur de segmentation a déjà été suggéré , je ne vois aucune mention des subtilités nécessaires pour garantir que les traces de trace résultantes sont: l'emplacement réel du défaut (au moins pour certaines architectures - x86 et ARM).
Les deux premières entrées de la chaîne de trames de la pile, lorsque vous entrez dans le gestionnaire de signaux, contiennent une adresse de retour dans le gestionnaire de signaux et une autre dans sigaction () dans libc. La pile de la dernière fonction appelée avant le signal (qui est l'emplacement du défaut) est perdue.
#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);
}
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 risques d'appeler les fonctions backtrace () dans un gestionnaire de signaux existent toujours et ne doivent pas être négligés, mais je trouve les fonctionnalités que je viens de décrire ici très utiles pour le débogage des plantages.
Il est important de noter que l'exemple que j'ai fourni est développé/testé sur Linux pour x86. J'ai également implémenté cela 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
C'est encore plus facile que "man backtrace", il existe une bibliothèque peu documentée (spécifique à GNU) distribuée avec glibc sous le nom libSegFault.so, qui a été écrite par Ulrich Drepper pour soutenir le programme catchsegv (voir "man catchsegv").
Cela nous donne 3 possibilités. Au lieu de lancer "programme -o hai":
Exécuter dans catchsegv:
$ catchsegv program -o hai
Lien avec libSegFault au moment de l'exécution:
$ LD_PRELOAD=/lib/libSegFault.so program -o hai
Lien avec libSegFault lors de la compilation:
$ gcc -g1 -lSegFault -o program program.cc
$ program -o hai
Dans les 3 cas, vous obtiendrez des traces plus claires avec moins d'optimisation (gcc -O0 ou -O1) et des symboles de débogage (gcc -g). Sinon, vous risquez de vous retrouver avec une pile d'adresses mémoire.
Vous pouvez également capturer plus de signaux pour les traces de pile avec quelque chose comme:
$ export SEGFAULT_SIGNALS="all" # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt" # SIGBUS and SIGABRT
La sortie ressemblera à ceci (notez la trace en bas):
*** Segmentation fault Register dump:
EAX: 0000000c EBX: 00000080 ECX:
00000000 EDX: 0000000c ESI:
bfdbf080 EDI: 080497e0 EBP:
bfdbee38 ESP: bfdbee20
EIP: 0805640f EFLAGS: 00010282
CS: 0073 DS: 007b ES: 007b FS:
0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000004
OldMask: 00000000 ESP/signal:
bfdbee20 CR2: 00000024
FPUCW: ffff037f FPUSW: ffff0000
TAG: ffffffff IPOFF: 00000000
CSSEL: 0000 DATAOFF: 00000000
DATASEL: 0000
ST(0) 0000 0000000000000000 ST(1)
0000 0000000000000000 ST(2) 0000
0000000000000000 ST(3) 0000
0000000000000000 ST(4) 0000
0000000000000000 ST(5) 0000
0000000000000000 ST(6) 0000
0000000000000000 ST(7) 0000
0000000000000000
Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]
Si vous voulez connaître les détails sanglants, la meilleure source est malheureusement la source: Voir http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c et son répertoire parent http://sourceware.org/git/?p=glibc.git;a=tree;f=debug
Même si un réponse correcte a été fourni qui décrit comment utiliser la fonction GNU libc backtrace()
1 et j'ai fourni ma propre réponse qui décrit comment assurer la traçabilité d'un pointeur de traitement du signal sur l'emplacement réel du défaut2, Je ne vois aucune mention de démêlage sortie des symboles C++ à partir de la trace.
Lors de l'obtention de traces d'un programme C++, la sortie peut être exécutée via c++filt
1 démêler les symboles ou en utilisant abi::__cxa_demangle
1 directement.
c++filt
et __cxa_demangle
sont spécifiques à GCC.L'exemple C++ Linux suivant utilise le même gestionnaire de signal que mon autre réponse et montre comment c++filt
peut être utilisé pour démêler les symboles.
Code:
class foo
{
public:
foo() { foo1(); }
private:
void foo1() { foo2(); }
void foo2() { foo3(); }
void foo3() { foo4(); }
void foo4() { crash(); }
void crash() { char * p = NULL; *p = 0; }
};
int main(int argc, char ** argv)
{
// Setup signal handler for SIGSEGV
...
foo * f = new foo();
return 0;
}
Sortie (./test
):
signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]
Sortie démêlée (./test 2>&1 | c++filt
):
signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]
Les constructions suivantes sur le gestionnaire de signaux à partir de mon réponse d'origine et peuvent remplacer le gestionnaire de signaux dans l'exemple ci-dessus pour montrer comment/ abi::__cxa_demangle
peut être utilisé pour démêler les symboles. Ce gestionnaire de signal produit la même sortie démêlée que l'exemple ci-dessus.
Code:
void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;
void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific
std::cerr << "signal " << sig_num
<< " (" << strsignal(sig_num) << "), address is "
<< info->si_addr << " from " << caller_address
<< std::endl << std::endl;
void * array[50];
int size = backtrace(array, 50);
array[1] = caller_address;
char ** messages = backtrace_symbols(array, size);
// skip first stack frame (points here)
for (int i = 1; i < size && messages != NULL; ++i)
{
char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
// find parantheses and +address offset surrounding mangled name
for (char *p = messages[i]; *p; ++p)
{
if (*p == '(')
{
mangled_name = p;
}
else if (*p == '+')
{
offset_begin = p;
}
else if (*p == ')')
{
offset_end = p;
break;
}
}
// if the line could be processed, attempt to demangle the symbol
if (mangled_name && offset_begin && offset_end &&
mangled_name < offset_begin)
{
*mangled_name++ = '\0';
*offset_begin++ = '\0';
*offset_end++ = '\0';
int status;
char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
// if demangling is successful, output the demangled function name
if (status == 0)
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << " : "
<< real_name << "+" << offset_begin << offset_end
<< std::endl;
}
// otherwise, output the mangled function name
else
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << " : "
<< mangled_name << "+" << offset_begin << offset_end
<< std::endl;
}
free(real_name);
}
// otherwise, print the whole line
else
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
}
std::cerr << std::endl;
free(messages);
exit(EXIT_FAILURE);
}
Cela vaut peut-être la peine de regarder Google Breakpad , un générateur de vidage sur incident multi-plateforme et des outils permettant de traiter les vidages.
Vous n'avez pas spécifié votre système d'exploitation, il est donc difficile de répondre à cette question. Si vous utilisez un système basé sur gnu libc, vous pourrez peut-être utiliser la fonction libc backtrace()
.
GCC a également deux éléments intégrés qui peuvent vous aider, mais qui peuvent ou non être entièrement implémentés dans votre architecture, à savoir __builtin_frame_address
et __builtin_return_address
. Les deux qui veulent un niveau entier immédiat (par immédiat, je veux dire que ça ne peut pas être une variable). Si __builtin_frame_address
pour un niveau donné est différent de zéro, il devrait être prudent de saisir l'adresse de retour du même niveau.
ulimit -c <value>
définit la taille limite du fichier de base sous unix. Par défaut, la taille maximale du fichier principal est 0. Vous pouvez voir vos valeurs ulimit
avec ulimit -a
.
de plus, si vous exécutez votre programme à partir de gdb, il sera arrêté sur des "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 interfaces pour gdb qui facilitent grandement son travail pour les novices.
J'ai examiné ce problème pendant un moment.
Et enfoui au plus profond du fichier README de Google Performance Tools
http://code.google.com/p/google-perftools/source/browse/trunk/README
parle de libunwind
http://www.nongnu.org/libunwind/
J'adorerais entendre les opinions de cette bibliothèque.
Le problème avec -rdynamic est qu’il peut augmenter la taille du binaire de manière relativement significative dans certains cas.
Merci à enthousiastegeek 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 ici : (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 doit afficher quelque chose comme ceci pour chaque niveau de 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
Certaines versions de libc contiennent des fonctions traitant des traces de pile; vous pourrez peut-être les utiliser:
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
Je me souviens d’avoir utilisé libunwind il ya longtemps pour obtenir des traces de pile, mais il se peut que cela ne soit pas pris en charge sur votre plate-forme.
Il est important de noter qu'une fois que vous avez généré un fichier core, vous devez utiliser l'outil gdb pour l'examiner. Pour que gdb donne un sens à votre fichier core, vous devez dire à gcc d’instrumenter le binaire avec les symboles de débogage: pour ce faire, vous compilez avec le drapeau -g:
$ g++ -g prog.cpp -o prog
Ensuite, vous pouvez soit définir "ulimit -c unlimited" pour le laisser vider un noyau, ou simplement exécuter votre programme dans gdb. J'aime plus la deuxième approche:
$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...
J'espère que ça aide.
ulimit -c unlimited
est une variable système, ce qui permettra de créer un vidage mémoire après le blocage de votre application. Dans ce cas, un montant illimité. Recherchez un fichier appelé core dans le même répertoire. Assurez-vous d'avoir compilé votre code avec les informations de débogage activées!
cordialement
Oubliez de changer vos sources et faites quelques piratages avec la fonction backtrace () ou les macros - ce ne sont que des solutions médiocres.
Comme solution efficace, je vous conseillerais:
Ceci imprimera une trace lisible de votre programme de manière lisible par l’homme (avec les noms de fichier source et les numéros de ligne) . dump du noyau, puis envoyez les traces par e-mail aux développeurs ou connectez-le à un système de journalisation.
Vous pouvez utiliser DeathHandler - petite classe C++ qui fait tout pour vous, fiable.
gagner: que diriez-vous de StackWalk64 http://msdn.Microsoft.com/en-us/library/ms680650.aspx
Regarder:
homme 3 backtrace
Et:
#include <exeinfo.h>
int backtrace(void **buffer, int size);
Ce sont des extensions GNU.
Voir la fonction de trace de pile dans ACE (environnement de communication ADAPTIVE). Il est déjà écrit pour couvrir toutes les principales plates-formes (et plus). La bibliothèque est sous licence BSD. Vous pouvez même copier/coller le code si vous ne voulez pas utiliser ACE.
Je peux aider avec la version Linux: les fonctions backtrace, backtrace_symbols et backtrace_symbols_fd peuvent être utilisées. Voir les pages de manuel correspondantes.
* nix: vous pouvez intercepter SIGSEGV (habituellement ce signal est émis avant le crash) et conserver les informations dans un fichier (outre le fichier core que vous pouvez utiliser pour déboguer en utilisant gdb par exemple).
win: Vérifiez this de msdn.
Vous pouvez également consulter le code chrome de Google pour savoir comment il gère les plantages. Il a un mécanisme de gestion des exceptions de Nice.
J'ai trouvé que la solution @tgamblin n'est pas complète . Elle ne peut pas gérer stackoverflow . Je pense que, par défaut, le gestionnaire de signaux est appelé avec la même pile et que SIGSEGV est renvoyé deux fois. Pour vous protéger, vous devez enregistrer une pile indépendante pour le gestionnaire de signaux.
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 exécutant un gestionnaire de signaux, puis en quittant . C'est la voie à suivre, mais rappelez-vous un fait très important: si vous voulez obtenir le vidage mémoire de l'erreur générée, vous ne pouvez pas appeler exit(status)
. Appelez abort()
à la place!
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);
}
J'utiliserais le code qui génère une trace de pile pour la mémoire perdue dans Détecteur de fuite visuel . Cela ne fonctionne que sur Win32, cependant.
En tant que solution exclusivement Windows, vous pouvez obtenir l'équivalent d'une trace de pile (avec beaucoup plus d'informations) à l'aide de Rapport d'erreurs Windows . Avec seulement quelques entrées de registre, il peut être configuré pour collecter des vidages en mode utilisateur :
À partir de Windows Server 2008 et de Windows Vista avec Service Pack 1 (SP1), le rapport d'erreurs Windows (WER) peut être configuré pour que les vidages complets en mode utilisateur soient collectés et stockés localement après le blocage d'une application en mode utilisateur. [...]
Cette fonctionnalité n'est pas activée par défaut. L'activation de la fonctionnalité nécessite des privilèges d'administrateur. Pour activer et configurer cette fonctionnalité, utilisez les valeurs de registre suivantes sous la clé HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Rapport d'erreurs\LocalDumps.
Vous pouvez définir les entrées de registre à partir de votre programme d'installation, qui dispose des privilèges requis.
La création d'un vidage en mode utilisateur présente les avantages suivants par rapport à la génération d'une trace de pile sur le client:
Notez que WER ne peut être déclenché que par un blocage d'application (c'est-à-dire que le système met fin à un processus en raison d'une exception non gérée). MiniDumpWriteDump
peut être appelé à tout moment. Cela peut être utile si vous devez vider l’état actuel pour diagnostiquer des problèmes autres qu’un crash.
Lecture obligatoire, si vous souhaitez évaluer l'applicabilité des mini-dumps:
En plus des réponses ci-dessus, voici comment vous aidez Debian Linux à générer un core dump.
Sous Linux/unix/MacOSX, utilisez des fichiers core (vous pouvez les activer avec ulimit ou appel système compatible ). Sous Windows, utilisez le rapport d'erreurs Microsoft (vous pouvez devenir un partenaire et accéder aux données de blocage de votre application).
Si vous voulez toujours y aller seul comme je l'ai fait, vous pouvez créer un lien vers bfd
et éviter d'utiliser addr2line
comme je l'ai fait ici:
https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c
Ceci produit la sortie:
[E] crash.linux.c:170 | crit_err_hdlr | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E] crash.linux.c:171 | crit_err_hdlr | signal 11 (Segmentation fault), address is (nil)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E] crash.linux.c:199 | crit_err_hdlr | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E] crash.linux.c:199 | crit_err_hdlr | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]
J'ai oublié la technologie GNOME de "apport", mais je ne connais pas grand chose à son utilisation. Il est utilisé pour générer des stacktraces et d’autres diagnostics à des fins de traitement et peut automatiquement archiver des bogues. Cela vaut certainement la peine de vérifier.
Si votre programme se bloque, c'est le système d'exploitation lui-même qui génère les informations de vidage sur incident. Si vous utilisez un système d'exploitation * nix, vous devez simplement ne pas l'empêcher de le faire (consultez les options 'coredump' de la commande ulimit).
Il semble que dans l’une des dernières librairies c ++ boostes, la bibliothèque contienne exactement ce que vous voulez, probablement du code multiplatforme . C’est boost :: stacktrace , que vous pouvez utiliser comme as dans boost échantillon :
#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h> // ::signal, ::raise
#include <boost/stacktrace.hpp>
const char* backtraceFileName = "./backtraceFile.dump";
void signalHandler(int)
{
::signal(SIGSEGV, SIG_DFL);
::signal(SIGABRT, SIG_DFL);
boost::stacktrace::safe_dump_to(backtraceFileName);
::raise(SIGABRT);
}
void sendReport()
{
if (std::filesystem::exists(backtraceFileName))
{
std::ifstream file(backtraceFileName);
auto st = boost::stacktrace::stacktrace::from_dump(file);
std::ostringstream backtraceStream;
backtraceStream << st << std::endl;
// sending the code from st
file.close();
std::filesystem::remove(backtraceFileName);
}
}
int main()
{
::signal(SIGSEGV, signalHandler);
::signal(SIGABRT, signalHandler);
sendReport();
// ... rest of code
}
Sous Linux, vous compilez le code ci-dessus:
g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace
Exemple de trace copiée à partir de la documentation boost :
0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start