Traccia stack di visualizzazione C ++ su eccezione

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

Per rispondere alle domande:

Mi piacerebbe che fosse portatile se ansible. Desidero che le informazioni vengano visualizzate, in modo che l’utente possa copiare la traccia dello stack e inviarcele via email in caso di errore.

Dipende da quale piattaforma.

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

Su MSVC, quindi, è ansible 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.

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

L’unico modo – usando GCC – per risolvere questo è assicurarsi di generare una traccia stack nel punto dell’istruzione throw e salvarla con l’object exception.

Questo metodo richiede, ovviamente, che ogni codice che genera un’eccezione utilizzi quella particolare class Exception.

Aggiornamento 11 luglio 2017 : Per un codice utile, date un’occhiata alla risposta di cahit beyaz, che rimanda a http://stacktrace.sourceforge.net – Non l’ho ancora usato, ma sembra promettente.

Se stai usando Boost 1.65 o superiore, puoi usare 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 è abbastanza portatile e finora non ho trovato nulla di più facile da usare.

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

su Linux con g ++ controlla questa libreria

https://sourceforge.net/projects/libcsdbg

fa tutto il lavoro per te

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

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

Usa std::nested_exception e std::throw_with_nested

Questo non ti darà una pila, ma a mio parere la prossima cosa migliore. È descritto su StackOverflow qui e qui , come è ansible ottenere un backtrace sulle eccezioni all’interno del codice senza necessità di un debugger o di una registrazione ingombrante, semplicemente scrivendo un gestore di eccezioni appropriato che ripulterà le eccezioni nidificate.

Dato che puoi farlo con qualsiasi class di derivazione 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" 

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.

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 demarcare i nomi delle funzioni, Mr. Bingmann ha un bel pezzo di codice. Per scaricare un backtrace su un’eccezione, creo un’eccezione che stampa il backtrace nel costruttore. Se mi aspettassi che questo funzioni con un’eccezione generata in una libreria, potrebbe richiedere la ricostruzione / collegamento in modo che venga utilizzata 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  #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::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() ); } } 

La compilazione e l’esecuzione di questo con gcc 4.8.4 produce un backtrace con nomi di funzioni C ++ piacevolmente invisibili:

 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] 

Dal momento che la pila è già svolta durante l’inserimento del blocco catch, la soluzione nel mio caso è stata quella di non rilevare alcune eccezioni che portano a un SIGABRT. Nel gestore di segnale per SIGABRT I quindi fork () ed execl () gdb (nelle build di debug) o stackwalks di Google breakpads (nelle build di rilascio). Inoltre, cerco di utilizzare solo le funzioni di sicurezza del gestore di 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(*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 dovuto aggiungere anche 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 di stack per C ++ usando gcc con le informazioni sul numero di riga? ed è ansible colbind gdb a un processo in crash (anche noto come debug “just-in-time”)

Cpp-tool ex_diag – easyweight, multipiattaforma, utilizzo minimo di risorse, semplice e flessibile a tracciare.

Il seguente codice interrompe l’esecuzione subito dopo l’emissione di un’eccezione. È necessario impostare un windows_exception_handler con un gestore di terminazione. Ho provato questo in 32G MinGW.

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

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