C ++ Display Stack Trace bei Ausnahme

204

Ich möchte eine Möglichkeit haben, den Stack-Trace dem Benutzer zu melden, wenn eine Ausnahme ausgelöst wird. Was ist der beste Weg, dies zu tun? Benötigt es große Mengen an zusätzlichem Code?

Fragen beantworten:

Ich möchte, dass es wenn möglich tragbar ist. Ich möchte, dass Informationen angezeigt werden, damit der Benutzer den Stack-Trace kopieren und per E-Mail an mich senden kann, wenn ein Fehler auftritt.

rlbond
quelle

Antworten:

76

Es kommt darauf an, welche Plattform.

Auf GCC ist es ziemlich trivial, siehe diesen Beitrag für weitere Details.

Unter MSVC können Sie dann die StackWalker- Bibliothek verwenden, die alle zugrunde liegenden API-Aufrufe verarbeitet, die für Windows erforderlich sind.

Sie müssen herausfinden, wie Sie diese Funktionalität am besten in Ihre App integrieren können, aber die Menge an Code, die Sie schreiben müssen, sollte minimal sein.

Andrew Grant
quelle
71
Der Beitrag, auf den Sie verlinken, verweist hauptsächlich auf das Generieren einer Spur aus einem Segfault, aber der Fragesteller erwähnt ausdrücklich Ausnahmen, die ein ganz anderes Tier sind.
Shep
8
Ich stimme @Shep zu - diese Antwort hilft nicht wirklich dabei, eine Stapelverfolgung des Wurfcodes auf GCC zu erhalten. Siehe meine Antwort für eine mögliche Lösung.
Thomas Tempelmann
1
Diese Antwort ist irreführend. Der Link verweist auf eine Antwort, die spezifisch für Linuxnicht ist gcc.
Fjardon
Sie können den Wurfmechanismus von libstdc++(von GCC und möglicherweise Clang verwendet) überschreiben, wie in dieser Antwort erläutert .
ingomueller.net
59

Die Antwort von Andrew Grant hilft nicht , eine Stapelverfolgung der Wurffunktion zu erhalten , zumindest nicht mit GCC, da eine throw-Anweisung die aktuelle Stapelverfolgung nicht alleine speichert und der catch-Handler keinen Zugriff auf die Stapelverfolgung bei hat dieser Punkt nicht mehr.

Die einzige Möglichkeit, dies mithilfe von GCC zu lösen, besteht darin, am Punkt der Wurfanweisung eine Stapelverfolgung zu generieren und diese mit dem Ausnahmeobjekt zu speichern.

Diese Methode erfordert natürlich, dass jeder Code, der eine Ausnahme auslöst, diese bestimmte Ausnahmeklasse verwendet.

Update 11. Juli 2017 : Einen hilfreichen Code finden Sie in der Antwort von cahit beyaz, die auf http://stacktrace.sourceforge.net verweist. Ich habe ihn noch nicht verwendet, aber er sieht vielversprechend aus.

Thomas Tempelmann
quelle
1
Leider ist der Link tot. Könnten Sie noch etwas anbieten?
Warran
2
Und archive.org weiß es auch nicht. Verdammt. Nun, die Prozedur sollte klar sein: Wirf ein benutzerdefiniertes Klassenobjekt, das die Stapelverfolgung zum Zeitpunkt des Wurfs aufzeichnet.
Thomas Tempelmann
1
Auf der Homepage von StackTrace sehe ich throw stack_runtime_error. Kann ich zu Recht feststellen, dass diese std::exceptionBibliothek nur für Ausnahmen funktioniert, die von dieser Klasse abgeleitet sind, und nicht für oder Ausnahmen von Bibliotheken von Drittanbietern?
Thomas
3
Leider lautet die Antwort "Nein, Sie können keinen Stack-Trace von einer C ++ - Ausnahme abrufen". Die einzige Option besteht darin, eine eigene Klasse zu werfen, die beim Erstellen einen Stack-Trace generiert. Wenn Sie beispielsweise mit einem Teil der C ++ std :: -Bibliothek nicht weiterkommen, haben Sie kein Glück. Sorry, scheiße, du zu sein.
Code Abominator
43

Wenn Sie Boost 1.65 oder höher verwenden, können Sie boost :: stacktrace verwenden :

#include <boost/stacktrace.hpp>

// ... somewhere inside the bar(int) function that is called recursively:
std::cout << boost::stacktrace::stacktrace();
vasek
quelle
5
In den Boost-Dokumenten wird nicht nur die Erfassung eines Stack-Trace erläutert, sondern auch die Vorgehensweise für Ausnahmen und Asserts. Tolles Zeug.
Moodboom
1
Gibt diese stacktrace () Quelldatei und Zeilennummern aus, wie im GettingStarted-Handbuch angegeben?
Gimhani
11

Ich möchte eine Standardbibliotheksoption (dh plattformübergreifend) zum Generieren von Ausnahme-Backtraces hinzufügen , die mit C ++ 11 verfügbar geworden ist :

Verwenden Sie std::nested_exceptionundstd::throw_with_nested

Dies gibt Ihnen keinen Stapel zum Entspannen, aber meiner Meinung nach das nächstbeste. In StackOverflow wird hier und hier beschrieben , wie Sie eine Rückverfolgung Ihrer Ausnahmen in Ihrem Code erhalten können, ohne dass ein Debugger oder eine umständliche Protokollierung erforderlich ist, indem Sie einfach einen geeigneten Ausnahmebehandler schreiben, der verschachtelte Ausnahmen erneut auslöst.

Da Sie dies mit jeder abgeleiteten Ausnahmeklasse tun können, können Sie einer solchen Rückverfolgung viele Informationen hinzufügen! Sie können sich auch mein MWE auf GitHub ansehen, wo ein Backtrace ungefähr so ​​aussehen würde:

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"
GPMueller
quelle
Dies ist wahrscheinlich viel besser, wenn Sie bereit sind, die zusätzliche Arbeit zu erledigen, als die übliche dumme Stapelverfolgung.
Klarer
4

AFAIK libunwind ist ziemlich portabel und ich habe bisher nichts einfacher zu bedienen gefunden.

Nico Brailovsky
quelle
libunwind 1.1 baut nicht auf os x auf.
Xaxxon
4

Ich empfehle das Projekt http://stacktrace.sourceforge.net/ . Es unterstützt Windows, Mac OS und auch Linux

cahit beyaz
quelle
4
Auf seiner Homepage sehe ich throw stack_runtime_error. Kann ich zu Recht feststellen, dass diese std::exceptionBibliothek nur für Ausnahmen funktioniert, die von dieser Klasse abgeleitet sind, und nicht für oder Ausnahmen von Bibliotheken von Drittanbietern?
Thomas
4

Wenn Sie C ++ verwenden und Boost nicht verwenden möchten / können, können Sie Backtrace mit entwirrten Namen mit dem folgenden Code drucken [Link zur ursprünglichen Site] .

Beachten Sie, dass diese Lösung spezifisch für Linux ist. Es verwendet die libc-Funktionen von GNU backtrace () / backtrace_symbols () (von execinfo.h), um die Backtraces abzurufen, und verwendet dann __cxa_demangle () (von cxxabi.h), um die Namen der Backtrace-Symbole zu entwirren.

// 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!

sundeep singh
quelle
3

Schauen Sie sich unter Windows BugTrap an . Es befindet sich nicht mehr unter dem ursprünglichen Link, ist aber weiterhin auf CodeProject verfügbar.

jww
quelle
3

Ich habe ein ähnliches Problem und obwohl ich Portabilität mag, brauche ich nur gcc-Unterstützung. In gcc sind execinfo.h und die Backtrace- Aufrufe verfügbar. Um die Funktionsnamen zu entwirren, hat Herr Bingmann einen schönen Code. Um eine Rückverfolgung für eine Ausnahme zu sichern, erstelle ich eine Ausnahme, die die Rückverfolgung im Konstruktor druckt. Wenn ich damit gerechnet habe, dass dies mit einer in einer Bibliothek ausgelösten Ausnahme funktioniert, muss möglicherweise neu erstellt / verknüpft werden, damit die Backtracing-Ausnahme verwendet wird.

/******************************************
#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() );
    }
}

Das Kompilieren und Ausführen mit gcc 4.8.4 ergibt eine Rückverfolgung mit gut entwirrten C ++ - Funktionsnamen:

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]
Thomas
quelle
3

Da der Stapel bereits beim Betreten des Catch-Blocks abgewickelt wird, bestand die Lösung in meinem Fall darin , bestimmte Ausnahmen nicht zu fangen, die dann zu einem SIGABRT führen. Im Signalhandler für SIGABRT habe ich dann fork () und execl () entweder gdb (in Debug-Builds) oder Google Breakpads Stackwalk (in Release-Builds). Außerdem versuche ich, nur Signalhandler-sichere Funktionen zu verwenden.

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;
}

Bearbeiten: Damit es für das Breakpad funktioniert, musste ich auch Folgendes hinzufügen:

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
});

Quelle: Wie erhalte ich einen Stack-Trace für C ++ mit gcc mit Zeilennummerninformationen? und Ist es möglich, GDB an einen abgestürzten Prozess anzuhängen (auch bekannt als "Just-in-Time" -Debugging)?

Bl00dh0und
quelle
2

Poppy kann nicht nur den Stack-Trace erfassen, sondern auch Parameterwerte, lokale Variablen usw. - alles, was zum Absturz führt.

Orlin Georgiev
quelle
2

Der folgende Code stoppt die Ausführung direkt nach dem Auslösen einer Ausnahme. Sie müssen einen windows_exception_handler zusammen mit einem Terminierungshandler festlegen. Ich habe dies in MinGW 32bit getestet.

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);
...
}

Überprüfen Sie den folgenden Code für die Funktion windows_exception_handler: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html

Marcos Fuentes
quelle
1

Cpp-Tool ex_diag - einfach, plattformübergreifend, minimal ressourcenschonend, einfach und flexibel bei der Ablaufverfolgung.

Boris
quelle
Ich habe dieses Projekt am 24.12.2017 überprüft, Quelle und Download sind beide nicht zugänglich.
Zhaorufei
1
Ich habe gerade code.google.com/archive/p/exception-diagnostic/source/default/… überprüft und die Quelle kann heruntergeladen werden. Kannst du es noch einmal versuchen?
Boris