web-dev-qa-db-fra.com

C ++ affiche la trace de pile à l'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 procéder? Est-ce que cela prend d'énormes quantités de code supplémentaire?

Pour répondre aux questions:

J'aimerais que ce soit portable si possible. Je souhaite que les informations s'affichent afin que l'utilisateur puisse copier la trace de la pile et l'envoyer par courrier électronique en cas d'erreur.

176
rlbond

Cela dépend de quelle plate-forme.

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

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

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

69
Andrew Grant

La réponse d'Andrew Grant aide pas à obtenir une trace de pile de la fonction projection, du moins pas avec GCC, car une instruction throw n'enregistre pas seule la trace de pile actuelle et que le gestionnaire catch n'aura plus accès à la trace de pile à ce stade.

Le seul moyen - en utilisant GCC - de résoudre ce problème est de s'assurer de générer une trace de pile au moment de l'instruction de projection et de l'enregistrer avec l'objet exception.

Bien entendu, cette méthode nécessite que chaque code qui lève une exception utilise cette classe Exception particulière.

Mise à jour du 11 juillet 2017: Pour un code utile, jetez un coup d'œil à la réponse de cahit beyaz, qui pointe vers http://stacktrace.sourceforge.net - Je n'ai pas utilisé encore mais cela semble prometteur.

48
Thomas Tempelmann

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

#include <boost/stacktrace.hpp>

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

Unix: trace

Mac: trace

Windows: CaptureBackTrace

13
bobobobo

Je voudrais ajouter une option de la bibliothèque standard (c'est-à-dire multiplate-forme) pour générer des retraits d'exception, qui sont devenus disponibles avec C++ 11 :

Utilisez std::nested_exception et std::throw_with_nested

Cela ne vous donnera pas le feu vert, mais à mon avis la meilleure chose à faire. Il est décrit dans StackOverflow ici et ici , comment vous pouvez obtenir une trace de vos exceptions dans votre code sans avoir besoin d’un débogueur ou d’une journalisation lourde, en écrivant simplement un gestionnaire d’exceptions approprié qui va redistribuer les exceptions imbriquées.

Comme vous pouvez le faire avec n'importe quelle classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à un tel backtrace! Vous pouvez également jeter un oeil à mon MWE sur GitHub , où une trace aurait l'apparence suivante:

_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"
_
5
GPMueller

Autant que je sache, libunwind est assez portable et, jusqu'à présent, je n'ai rien trouvé de plus facile à utiliser.

4
Nico Brailovsky

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

4
cahit beyaz

Comme la pile est déjà déroulée lors de l'entrée dans le bloc catch, la solution dans mon cas était de ne pas capturer certaines exceptions qui conduisaient ensuite à un SIGABRT. Dans le gestionnaire de signaux pour SIGABRT, j'ai alors fork () et execl (), soit gdb (dans les versions debug), soit dans Google breakpads (dans les versions finales). De plus, j'essaie d'utiliser uniquement les fonctions sécurisées 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-- = '\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], 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 que cela fonctionne avec le breakpad, je devais aussi 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 à un processus bloqué (également appelé débogage "juste à temps")

3
Bl00dh0und

sur linux avec g ++ consultez cette lib

https://sourceforge.net/projects/libcsdbg

il fait tout le travail pour vous

3
Tasos Parisinos

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

3
hplbsh

J'ai un problème similaire et bien que j'aime la portabilité, je n'ai besoin que du support de gcc. Dans gcc, execinfo.h et les appels backtrace sont disponibles. Pour démêler les noms de fonction, M. Bingmann a n joli morceau de code. Pour vider 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 levée dans une bibliothèque, cela pourrait nécessiter une reconstruction/un lien afin que l'exception de traçage soit 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() );
    }
}

En compilant et en exécutant ceci avec gcc 4.8.4, on obtient une trace avec des noms de fonctions C++ joliment démodé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
mattfarley

Poppy peut rassembler non seulement la trace de la pile, mais également les valeurs de paramètre, les variables locales, etc. - tout ce qui a provoqué le crash.

2
Orlin Georgiev

Le code suivant arrête l'exécution juste après la levée d'une exception. Vous devez définir un gestionnaire windows_exception_handler avec un gestionnaire de terminaison. J'ai testé cela 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

1
Marcos Fuentes

Si vous utilisez C++ et que vous ne voulez pas/ne pouvez pas utiliser Boost, vous pouvez imprimer une trace de retour avec des noms démêlés en utilisant le code suivant: [lien vers le site d'origine] .

Notez que cette solution est spécifique à Linux. Il utilise les fonctions libc de GNU, backtrace ()/backtrace_symbols () (de execinfo.h) pour obtenir les traces, puis utilise __cxa_demangle () (de cxxabi.h) pour démêler les noms de symbole de trace.

// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
// published under the WTFPL v2.0

#ifndef _STACKTRACE_H_
#define _STACKTRACE_H_

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>

/** Print a demangled stack backtrace of the caller function to FILE* out. */
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
{
    fprintf(out, "stack trace:\n");

    // storage array for stack trace address data
    void* addrlist[max_frames+1];

    // retrieve current stack addresses
    int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));

    if (addrlen == 0) {
    fprintf(out, "  <empty, possibly corrupt>\n");
    return;
    }

    // resolve addresses into strings containing "filename(function+address)",
    // this array must be free()-ed
    char** symbollist = backtrace_symbols(addrlist, addrlen);

    // allocate string which will be filled with the demangled function name
    size_t funcnamesize = 256;
    char* funcname = (char*)malloc(funcnamesize);

    // iterate over the returned symbol lines. skip the first, it is the
    // address of this function.
    for (int i = 1; i < addrlen; i++)
    {
    char *begin_name = 0, *begin_offset = 0, *end_offset = 0;

    // find parentheses and +address offset surrounding the mangled name:
    // ./module(function+0x15c) [0x8048a6d]
    for (char *p = symbollist[i]; *p; ++p)
    {
        if (*p == '(')
        begin_name = p;
        else if (*p == '+')
        begin_offset = p;
        else if (*p == ')' && begin_offset) {
        end_offset = p;
        break;
        }
    }

    if (begin_name && begin_offset && end_offset
        && begin_name < begin_offset)
    {
        *begin_name++ = '\0';
        *begin_offset++ = '\0';
        *end_offset = '\0';

        // mangled name is now in [begin_name, begin_offset) and caller
        // offset in [begin_offset, end_offset). now apply
        // __cxa_demangle():

        int status;
        char* ret = abi::__cxa_demangle(begin_name,
                        funcname, &funcnamesize, &status);
        if (status == 0) {
        funcname = ret; // use possibly realloc()-ed string
        fprintf(out, "  %s : %s+%s\n",
            symbollist[i], funcname, begin_offset);
        }
        else {
        // demangling failed. Output function name as a C function with
        // no arguments.
        fprintf(out, "  %s : %s()+%s\n",
            symbollist[i], begin_name, begin_offset);
        }
    }
    else
    {
        // couldn't parse the line? print the whole line.
        fprintf(out, "  %s\n", symbollist[i]);
    }
    }

    free(funcname);
    free(symbollist);
}

#endif // _STACKTRACE_H_

HTH!

1
sundeep singh

Cpp-tool ex_diag - easyweight, multiplatform, ressources minimales, simple et flexible à la trace.

1
Boris