C++ affichage de la trace de la pile d'exception

je veux avoir un moyen de signaler la trace de la pile à l'utilisateur si une exception est levée. Quelle est la meilleure façon de le faire? Est-ce qu'il faut beaucoup de code supplémentaire?

pour répondre aux questions:

j'aimerais qu'il soit portable si possible. Je veux que l'information apparaisse, pour que l'utilisateur puisse copier la trace de la pile et me l'envoyer par courriel si une erreur se produit.

157
demandé sur Peter Mortensen 2009-03-28 01:42:46

14 réponses

cela dépend quelle plate-forme.

Sur GCC c'est assez trivial, voir ce post pour plus de détails.

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

Vous aurez à trouver le meilleur moyen d'intégrer cette fonctionnalité dans votre application, mais la quantité de code à écrire devrait être minime.

68
répondu Andrew Grant 2017-05-23 12:26:32

la réponse D'Andrew Grant fait pas aide pour obtenir une trace de pile de la fonction lancer , du moins pas avec GCC, parce qu'une déclaration de jet ne sauve pas la trace de pile actuelle sur son propre, et le gestionnaire de capture n'aura plus accès à la trace de pile à ce point.

la seule façon - en utilisant GCC - de résoudre ceci est de s'assurer de générer une trace de pile au point du jet l'instruction et de l'enregistrer avec l'objet de l'exception.

cette méthode exige, bien sûr, que chaque code qui jette une exception utilise cette classe D'Exception particulière.

mise à jour du 11 juillet 2017 : pour un code utile, jetez un oeil à la réponse de cahit beyaz, qui pointe vers http://stacktrace.sourceforge.net - Je ne l'ai pas encore utilisé mais il semble prometteur.

41
répondu Thomas Tempelmann 2017-07-11 19:06:47

si vous utilisez Boost 1.65 ou plus, vous pouvez utiliser boost:: stacktrace :

#include <boost/stacktrace.hpp>

// ... somewhere inside the bar(int) function that is called recursively:
std::cout << boost::stacktrace::stacktrace();
24
répondu vasek 2017-09-11 11:08:48
12
répondu bobobobo 2013-08-30 22:26:13

AFAIK libunwind est très portable et jusqu'à présent je n'ai rien trouvé de plus facile à utiliser.

4
répondu Nico Brailovsky 2012-12-19 17:26:22

je recommande http://stacktrace.sourceforge.net projet . Il prend en charge Windows, Mac OS et aussi Linux

4
répondu cahit beyaz 2017-03-08 06:43:06

sur linux avec g++ vérifiez ce lib

https://sourceforge.net/projects/libcsdbg

elle fait tout le travail pour vous

3
répondu Tasos Parisinos 2014-01-25 05:37:51

sur les fenêtres, Vérifiez BugTrap . Ce n'est plus le lien original, mais il est toujours disponible sur CodeProject.

3
répondu hplbsh 2014-05-07 01:58:55

je voudrais ajouter une option de bibliothèque standard (c.-à-d. multi-plateforme) comment générer des backtraces d'exception, qui est devenu disponible avec C++11 :

utiliser std::nested_exception et std::throw_with_nested

cela ne vous donnera pas une dépoussiérage pile, mais à mon avis la meilleure chose suivante. Il est décrit sur StackOverflow ici et ici , comment vous pouvez obtenir un backtrace sur vos exceptions à l'intérieur de votre code sans avoir besoin d'un débogueur ou d'une journalisation encombrante, en écrivant simplement un gestionnaire d'exceptions approprié qui repensera les exceptions imbriquées.

puisque vous pouvez le faire avec n'importe quelle classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à une telle bactrace! Vous pouvez également jeter un oeil à mon MWE sur GitHub , où une ressembler à quelque chose comme 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"
3
répondu GPMueller 2017-12-03 13:29:50

Poppy peut rassembler non seulement la trace de la pile, mais aussi les valeurs des paramètres, les variables locales, etc. - tout ce qui a précédé le crash.

2
répondu Orlin Georgiev 2014-10-23 13:09:18

j'ai un problème similaire, et bien que j'aime la portabilité, Je n'ai besoin que du support gcc. Dans gcc, execinfo.h et les appels backtrace sont disponibles. Pour démêler les noms de fonction, M. Bingmann a un beau morceau de code. pour Dumper une rétrotrace sur une exception, je crée une exception qui imprime la rétrotrace dans le constructeur. Si je m'attendais à ce que cela fonctionne avec une exception jetée dans une bibliothèque, il pourrait nécessiter la reconstruction / lien ainsi que l'exception de rétrogradation est utilisée.

/******************************************
#Makefile with flags for printing backtrace with function names
# compile with symbols for backtrace
CXXFLAGS=-g
# add symbols to dynamic symbol table for backtrace
LDFLAGS=-rdynamic
turducken: turducken.cc
******************************************/

#include <cstdio>
#include <stdexcept>
#include <execinfo.h>
#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::string& 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() );
    }
}

compiler et exécuter ceci avec gcc 4.8.4 fournit une rétrotrace avec des noms de fonctions C++ bien non changés:

stack trace:
 ./turducken : btoverflow_error::btoverflow_error(std::string 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]
2
répondu mattfarley 2017-11-15 07:22:19

puisque la pile est déjà déboulée en entrant le bloc catch, la solution dans mon cas était de ne pas attraper certaines exceptions qui conduisent alors à un SIGABRT. Dans le gestionnaire de signal pour SIGABRT I puis fork() et execl () soit gdb (dans les builds de debug) ou Google breakpads stackwalk (dans les builds de release). De plus, j'essaie 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 *ltrim(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<bool>(*s))
        {
            *d++ = *s++;
        }
        *d-- = '"151900920"';
        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], ltrim(&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 le faire fonctionner pour breakpad j'ai aussi dû ajouter ceci:

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

Source: Comment obtenir une trace de pile pour C++ en utilisant gcc avec des informations de numéro de ligne? et Est-il possible de joindre gdb à l'écrasement d'un processus (un.k.le "juste-à-temps" débogage)

2
répondu Bl00dh0und 2018-08-27 14:45:00

Rpc-outil ex_diag - easyweight, multiplateforme, un minimum de ressources à l'aide de, simple et flexible à l'état de trace.

1
répondu Boris 2013-06-16 18:56:38

le code suivant arrête l'exécution juste après qu'une exception ait été lancée. Vous devez définir un windows_exception_handler avec un gestionnaire de résiliation. J'ai testé ça en 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érifier le code suivant pour la fonction windows_exception_handler: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html

1
répondu Marcos Fuentes 2015-06-11 20:01:40