Trace de stack d’affichage C ++ en cas d’exception

Je souhaite avoir un moyen de signaler la trace de la stack à l’utilisateur si une exception est levée. Quelle est la meilleure façon de procéder? Est-ce qu’il faut beaucoup de code supplémentaire?

Pour répondre aux questions:

Je voudrais que ce soit portable si possible. Je souhaite que les informations apparaissent, afin que l’utilisateur puisse copier la trace de la stack et l’envoyer par courrier électronique en cas d’erreur.

Cela dépend de quelle plateforme.

Sur GCC, c’est assez sortingvial, voyez ce post pour plus de détails.

Sur MSVC, vous pouvez alors utiliser la bibliothèque StackWalker qui gère tous les appels d’API sous-jacents nécessaires pour Windows.

Vous devrez trouver le meilleur moyen d’intégrer cette fonctionnalité dans votre application, mais la quantité de code à écrire doit être minimale.

La réponse d’Andrew Grant n’aide pas à obtenir une trace de stack de la fonction de lancement , du moins pas avec GCC, car une instruction throw ne sauvegarde pas seule la trace de stack actuelle et le gestionnaire de capture n’a pas access à la trace de stack. ce point plus.

La seule façon – en utilisant GCC – de résoudre ce problème est de s’assurer de générer une trace de stack au sharepoint l’instruction jeter et de l’enregistrer avec l’object exception.

Bien entendu, cette méthode nécessite que chaque code qui lance une exception utilise cette classe d’exception particulière.

Mise à jour 11 juillet 2017 : pour un code utile, jetez un coup d’œil à la réponse de cahit beyaz, qui pointe sur http://stacktrace.sourceforge.net – je ne l’ai pas encore utilisée, mais elle semble prometteuse.

Si vous utilisez Boost 1.65 ou supérieur, vous pouvez utiliser boost :: stacktrace :

 #include  // ... somewhere inside the bar(int) function that is called recursively: std::cout << boost::stacktrace::stacktrace(); 

Unix: backtrace

Mac: backtrace

Windows: CaptureBackTrace

AFAIK libunwind est assez portable et jusqu’à présent, je n’ai rien trouvé de plus facile à utiliser.

Je recommande le projet http://stacktrace.sourceforge.net/ . Il supporte Windows, Mac OS et aussi Linux

sur linux avec g ++ consultez cette lib

https://sourceforge.net/projects/libcsdbg

il fait tout le travail pour vous

Sous Windows, consultez BugTrap . Ce n’est plus le lien d’origine, mais il est toujours disponible sur CodeProject.

J’aimerais append une option de bibliothèque standard (c.-à-d. Multi-plateforme) sur la façon de générer des backtraces d’exception, disponibles avec C ++ 11 :

Utilisez std::nested_exception et std::throw_with_nested

Cela ne vous donnera pas une stack de décompression, mais à mon avis, la meilleure chose à faire. Il est décrit ici et ici dans StackOverflow, comment vous pouvez obtenir une trace sur vos exceptions dans votre code sans avoir besoin d’un débogueur ou d’une journalisation fastidieuse, simplement en écrivant un gestionnaire d’exceptions approprié qui va renvoyer les exceptions nestedes.

Comme vous pouvez le faire avec n’importe quelle classe dérivée, vous pouvez append beaucoup d’informations à un tel backtrace! Vous pouvez également jeter un coup d’œil à mon fichier MWE sur GitHub , où un backtrace ressemblerait à ceci:

 Library API: Exception caught in function 'api_function' Backtrace: ~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed ~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt" 

Poppy peut rassembler non seulement la trace de la stack, mais aussi les valeurs des parameters, les variables locales, etc.

J’ai un problème similaire, et bien que j’aime la portabilité, j’ai seulement besoin du support de gcc. Dans gcc, execinfo.h et les appels en retour sont disponibles. Pour démêler les noms de fonctions, M. Bingmann a un joli code. Pour sauvegarder une trace sur une exception, je crée une exception qui imprime la trace dans le constructeur. Si je m’attendais à ce que cela fonctionne avec une exception renvoyée dans une bibliothèque, il peut être nécessaire de reconstruire / associer pour que l’exception de retour en arrière soit utilisée.

 /****************************************** #Makefile with flags for printing backtrace with function names # comstack with symbols for backtrace CXXFLAGS=-g # add symbols to dynamic symbol table for backtrace LDFLAGS=-rdynamic turducken: turducken.cc ******************************************/ #include  #include  #include  #include "stacktrace.h" /* https://panthema.net/2008/0901-stacktrace-demangled/ */ // simple exception that prints backtrace when constructed class btoverflow_error: public std::overflow_error { public: btoverflow_error( const std::ssortingng& arg ) : std::overflow_error( arg ) { print_stacktrace(); }; }; void chicken(void) { throw btoverflow_error( "too big" ); } void duck(void) { chicken(); } void turkey(void) { duck(); } int main( int argc, char *argv[]) { try { turkey(); } catch( btoverflow_error e) { printf( "caught exception: %s\n", e.what() ); } } 

Comstackr et exécuter ceci avec gcc 4.8.4 donne une trace avec des noms de fonctions C ++ bien agencés:

 stack trace: ./turducken : btoverflow_error::btoverflow_error(std::ssortingng const&)+0x43 ./turducken : chicken()+0x48 ./turducken : duck()+0x9 ./turducken : turkey()+0x9 ./turducken : main()+0x15 /lib/x86_64-linux-gnu/libc.so.6 : __libc_start_main()+0xf5 ./turducken() [0x401629] 

Étant donné que la stack est déjà déroulée lors de l’entrée dans le bloc catch, la solution dans mon cas était de ne pas intercepter certaines exceptions qui aboutissaient alors à un SIGABRT. Dans le gestionnaire de signal pour SIGABRT, alors fork () et execl () sont soit gdb (dans les versions de débogage), soit les stackwalk de Google breakpads (dans les versions release). J’essaie également d’utiliser uniquement les fonctions de sécurité du gestionnaire de signaux.

GDB:

 static const char BACKTRACE_START[] = "<2>--- backtrace of entire stack ---\n"; static const char BACKTRACE_STOP[] = "<2>--- backtrace finished ---\n"; static char *lsortingm(char *s) { while (' ' == *s) { s++; } return s; } void Backtracer::print() { int child_pid = ::fork(); if (child_pid == 0) { // redirect stdout to stderr ::dup2(2, 1); // create buffer for parent pid (2+16+1 spaces to allow up to a 64 bit hex parent pid) char pid_buf[32]; const char* stem = " "; const char* s = stem; char* d = &pid_buf[0]; while (static_cast(*s)) { *d++ = *s++; } *d-- = '\0'; char* hexppid = d; // write parent pid to buffer and prefix with 0x int ppid = getppid(); while (ppid != 0) { *hexppid = ((ppid & 0xF) + '0'); if(*hexppid > '9') { *hexppid += 'a' - '0' - 10; } --hexppid; ppid >>= 4; } *hexppid-- = 'x'; *hexppid = '0'; // invoke GDB char name_buf[512]; name_buf[::readlink("/proc/self/exe", &name_buf[0], 511)] = 0; ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_START[0], sizeof(BACKTRACE_START)); (void)r; ::execl("/usr/bin/gdb", "/usr/bin/gdb", "--batch", "-n", "-ex", "thread apply all bt full", "-ex", "quit", &name_buf[0], lsortingm(&pid_buf[0]), nullptr); ::exit(1); // if GDB failed to start } else if (child_pid == -1) { ::exit(1); // if forking failed } else { // make it work for non root users if (0 != getuid()) { ::prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); } ::waitpid(child_pid, nullptr, 0); ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_STOP[0], sizeof(BACKTRACE_STOP)); (void)r; } } 

minidump_stackwalk:

 static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { int child_pid = ::fork(); if (child_pid == 0) { ::dup2(open("/dev/null", O_WRONLY), 2); // ignore verbose output on stderr ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_START[0], sizeof(MINIDUMP_STACKWALK_START)); (void)r; ::execl("/usr/bin/minidump_stackwalk", "/usr/bin/minidump_stackwalk", descriptor.path(), "/usr/share/breakpad-syms", nullptr); ::exit(1); // if minidump_stackwalk failed to start } else if (child_pid == -1) { ::exit(1); // if forking failed } else { ::waitpid(child_pid, nullptr, 0); ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_STOP[0], sizeof(MINIDUMP_STACKWALK_STOP)); (void)r; } ::remove(descriptor.path()); // this is not signal safe anymore but should still work return succeeded; } 

Edit: Pour que cela fonctionne pour breakpad, j’ai également dû append ceci:

 std::set_terminate([]() { ssize_t r = ::write(STDERR_FILENO, EXCEPTION, sizeof(EXCEPTION)); (void)r; google_breakpad::ExceptionHandler::WriteMinidump(std::ssortingng("/tmp"), dumpCallback, NULL); exit(1); // avoid creating a second dump by not calling std::abort }); 

Source: Comment obtenir une trace de stack pour C ++ en utilisant gcc avec des informations de numéro de ligne? Et est-il possible d’attacher gdb à un processus en panne (aka “juste à temps” de débogage)

Cpp-tool ex_diag – utilisation simple, multiplateforme, ressources minimales, simple et flexible à la trace.

Le code suivant arrête l’exécution juste après qu’une exception est levée. Vous devez définir un gestionnaire windows_exception_handler avec un gestionnaire de terminaison. Je l’ai testé dans MinGW 32bits.

 void beforeCrash(void); static const bool SET_TERMINATE = std::set_terminate(beforeCrash); void beforeCrash() { __asm("int3"); } int main(int argc, char *argv[]) { SetUnhandledExceptionFilter(windows_exception_handler); ... } 

Vérifiez le code suivant pour la fonction windows_exception_handler: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html