Traccia dello stack di visualizzazione C ++ su eccezione


204

Voglio avere un modo per segnalare all'utente la traccia dello stack se viene generata un'eccezione. Qual è il modo migliore per farlo? Ci vogliono enormi quantità di codice extra?

Per rispondere alle domande:

Mi piacerebbe che fosse portatile, se possibile. Voglio che vengano visualizzate le informazioni, in modo che l'utente possa copiare la traccia dello stack e inviarmele via e-mail in caso di errore.

Risposte:


76

Dipende da quale piattaforma.

Su GCC è piuttosto banale, vedi questo post per maggiori dettagli.

Su MSVC è quindi possibile utilizzare la libreria StackWalker che gestisce tutte le chiamate API sottostanti necessarie per Windows.

Dovrai capire il modo migliore per integrare questa funzionalità nella tua app, ma la quantità di codice che devi scrivere dovrebbe essere minima.


71
il post a cui ti colleghi punta principalmente a generare una traccia da un segfault, ma il richiedente menziona specificamente le eccezioni, che sono una bestia abbastanza diversa.
Shep

8
Sono d'accordo con @Shep - questa risposta non aiuta davvero a ottenere una traccia dello stack del codice di lancio su GCC. Vedi la mia risposta per una possibile soluzione.
Thomas Tempelmann,

1
Questa risposta è fuorviante. Il link punta a una risposta specifica a Linuxno gcc.
fjardon,

Puoi ignorare il meccanismo di lancio di libstdc++(usato da GCC e potenzialmente Clang) come spiegato in questa risposta .
ingomueller.net,

59

La risposta di Andrew Grant non aiuta a ottenere una traccia dello stack della funzione di lancio , almeno non con GCC, perché un'istruzione di lancio non salva da sola la traccia dello stack corrente e il gestore catch non avrà accesso alla traccia dello stack in più quel punto.

L'unico modo - usando GCC - per risolvere questo problema è assicurarsi di generare una traccia dello stack nel punto dell'istruzione di lancio e salvarla con l'oggetto eccezione.

Questo metodo richiede, ovviamente, che ogni codice che genera un'eccezione utilizza quella particolare classe di eccezione.

Aggiornamento dell'11 luglio 2017 : per un codice utile, dai un'occhiata alla risposta di cahit beyaz, che punta a http://stacktrace.sourceforge.net - Non l'ho ancora usato ma sembra promettente.


1
Sfortunatamente il link è morto. Potresti fornirne un altro?
guerriera

2
E neanche Archive.org lo sa. Dannazione. Bene, la procedura dovrebbe essere chiara: lanciare un oggetto di classe personalizzata che registra la traccia dello stack al momento del lancio.
Thomas Tempelmann,

1
Nella home page di StackTrace, vedo throw stack_runtime_error. Sono corretto nel dedurre che questa lib funziona solo per le eccezioni derivate da quella classe e non per std::exceptiono eccezioni da librerie di terze parti?
Thomas,

3
Quindi purtroppo la risposta è "No, non è possibile ottenere una traccia dello stack da un'eccezione C ++", l'unica opzione è lanciare la propria classe che genera una traccia dello stack quando viene costruita. Se sei bloccato usando cose come, diciamo, qualsiasi parte della libreria std :: C ++, sei sfortunato. Mi dispiace, fa schifo essere te.
Abominatore del codice

43

Se si utilizza Boost 1.65 o versione successiva, è possibile utilizzare boost :: stacktrace :

#include <boost/stacktrace.hpp>

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

5
I documenti boost spiegano non solo l'acquisizione di una traccia dello stack, ma come farlo per eccezioni e affermazioni. Roba fantastica.
moodboom

1
Questo stacktrace () stampa i file di origine e i numeri di riga come indicato nella guida GettingStarted?
Gimhani,


11

Vorrei aggiungere un'opzione di libreria standard (ovvero multipiattaforma) su come generare backtrace di eccezioni, che è diventato disponibile con C ++ 11 :

Usa std::nested_exceptionestd::throw_with_nested

Questo non ti darà un po 'di relax, ma secondo me la prossima cosa migliore. È descritto su StackOverflow qui e qui , come è possibile ottenere un backtrace sulle eccezioni all'interno del codice senza la necessità di un debugger o di una registrazione ingombrante, semplicemente scrivendo un gestore di eccezioni adeguato che riproporrà le eccezioni nidificate.

Dato che puoi farlo con qualsiasi classe di eccezione derivata, puoi aggiungere molte informazioni a tale backtrace! Puoi anche dare un'occhiata al mio MWE su GitHub , dove un backtrace sarebbe simile a questo:

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"

Questo è probabilmente molto meglio, se sei disposto a fare il lavoro extra, rispetto alla solita traccia dello stupido stack.
Più chiaro

4

AFAIK libunwind è abbastanza portatile e finora non ho trovato nulla di più facile da usare.


libunwind 1.1 non si basa su os x.
xaxxon,

4

Raccomando il progetto http://stacktrace.sourceforge.net/ . Supporta Windows, Mac OS e anche Linux


4
Nella sua home page, vedo throw stack_runtime_error. Sono corretto nel dedurre che questa lib funziona solo per le eccezioni derivate da quella classe e non per std::exceptiono eccezioni da librerie di terze parti?
Thomas,

4

Se stai usando C ++ e non vuoi / non puoi usare Boost, puoi stampare backtrace con nomi confusi usando il seguente codice [link al sito originale] .

Nota, questa soluzione è specifica per Linux. Usa le funzioni libc di GNU backtrace () / backtrace_symbols () (da execinfo.h) per ottenere i backtrace e quindi usa __cxa_demangle () (da cxxabi.h) per districare i nomi dei simboli backtrace.

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



3

Su Windows, controlla BugTrap . Non è più al collegamento originale, ma è ancora disponibile su CodeProject.


3

Ho un problema simile e anche se mi piace la portabilità, ho solo bisogno del supporto di gcc. In gcc sono disponibili execinfo.h e le chiamate backtrace . Per confondere i nomi delle funzioni, Mr. Bingmann ha un bel codice. Per scaricare una backtrace su un'eccezione, creo un'eccezione che stampa la backtrace nel costruttore. Se mi aspettavo che funzionasse con un'eccezione generata in una libreria, potrebbe essere necessario ricostruire / collegare in modo da utilizzare l'eccezione di backtracing.

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

Compilare ed eseguire questo con gcc 4.8.4 produce un backtrace con nomi di funzioni C ++ non distorti:

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]

3

Dato che lo stack è già srotolato quando si entra nel blocco catch, la soluzione nel mio caso è stata quella di non catturare determinate eccezioni che portano quindi a un SIGABRT. Nel gestore del segnale per SIGABRT, quindi fork () ed execl () o gdb (nelle build di debug) o Google Breakpads stackwalk (nelle build di rilascio). Inoltre cerco di utilizzare solo le funzioni sicure del gestore del segnale.

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

Modifica: per farlo funzionare per il breakpad ho anche dovuto aggiungere questo:

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

Fonte: Come ottenere una traccia dello stack per C ++ usando gcc con informazioni sul numero di riga? ed è possibile collegare gdb a un processo bloccato (alias debug "just-in-time")


2

Poppy può raccogliere non solo la traccia dello stack, ma anche i valori dei parametri, le variabili locali, ecc., Tutto ciò che porta al crash.


2

Il codice seguente interrompe l'esecuzione subito dopo che viene generata un'eccezione. È necessario impostare un windows_exception_handler insieme a un gestore di terminazione. Ho provato questo in MinGW 32 bit.

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

Controllare il seguente codice per la funzione windows_exception_handler: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.