C++ отображение трассировки стека при исключении
Я хочу иметь способ сообщить трассировку стека Пользователю, если возникает исключение. Каков наилучший способ сделать это? Требуется ли огромное количество дополнительного кода?
ответить на вопросы:
Я хотел бы, чтобы он был портативным, если это возможно. Я хочу, чтобы информация всплывала, поэтому пользователь может скопировать трассировку стека и отправить ее мне по электронной почте, если возникнет ошибка.
14 ответов:
Это зависит от того, какая платформа.
на GCC это довольно тривиально, см. этот пост для более подробной информации.
на MSVC, то вы можете использовать StackWalker библиотека, которая обрабатывает все основные вызовы API, необходимые для Windows.
вам нужно будет найти лучший способ интеграции этой функции в ваше приложение, но количество кода, которое вам нужно написать, должно быть минимальным.
ответ Эндрю Гранта делает не помогите получить трассировку стека бросать функция, по крайней мере, не с GCC, потому что оператор throw не сохраняет текущую трассировку стека самостоятельно, и обработчик catch больше не будет иметь доступа к трассировке стека в этот момент.
единственный способ - с помощью GCC-решить эту проблему-это создать трассировку стека в точке инструкции throw и сохранить ее с исключением объект.
этот метод требует, конечно, чтобы каждый код, который вызывает исключение, использовал этот конкретный класс исключений.
Обновление 11 Июля 2017: для некоторого полезного кода взгляните на ответ кахита беяза, который указывает на http://stacktrace.sourceforge.net - я еще не использовал его, но он выглядит многообещающим.
Если вы используете Boost 1.65 или выше, вы можете использовать boost:: stacktrace:
#include <boost/stacktrace.hpp> // ... somewhere inside the bar(int) function that is called recursively: std::cout << boost::stacktrace::stacktrace();
Unix:backtrace
Mac:backtrace
Windows:CaptureBackTrace
Я рекомендую http://stacktrace.sourceforge.net/ проект. Он поддерживает Windows, Mac OS, а также Linux
на linux с g++ проверьте эту lib
https://sourceforge.net/projects/libcsdbg
Он делает всю работу за вас
на Windows, проверьте BugTrap. Его больше нет в исходной ссылке, но он все еще доступен на CodeProject.
Я хотел бы добавить стандартная библиотека вариант (т. е. кросс-платформенный) как генерировать исключение трассировки, который стал доступен с C++11:
использовать
std::nested_exceptionиstd::throw_with_nestedЭто не даст вам стек расслабиться, но на мой взгляд следующая лучшая вещь. Это описано на StackOverflow здесь и здесь, как вы можете получить обратную трассу на вашем исключения внутри вашего кода без необходимости отладчика или громоздкого ведения журнала, просто написав правильный обработчик исключений, который будет повторно создавать вложенные исключения.
поскольку вы можете сделать это с любым производным классом исключений, вы можете добавить много информации к такому обратному следу! Вы также можете взглянуть на мой MWE на GitHub, где след будет выглядеть примерно так:
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"
Мака может собирать не только трассировку стека, но и значения параметров, локальные переменные и т. д. - все, что привело к катастрофе.
у меня есть аналогичная проблема, и хотя мне нравится переносимость, мне нужна только поддержка gcc. В gcc, execinfo.ч и backtrace звонки. Чтобы деманглировать имена функций, г-н Бингман имеет хороший кусок кода. чтобы сбросить backtrace на исключение, я создаю исключение, которое печатает backtrace в конструкторе. Если бы я ожидал, что это будет работать с исключением, вызванным в библиотеке, это может потребовать перестройки / связывания, чтобы отследить используется исключение.
/****************************************** #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() ); } }компиляция и запуск этого с помощью gcc 4.8.4 дает обратную трассировку с красиво незамутненными именами функций C++:
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]
поскольку стек уже размотан при входе в блок catch, решение в моем случае было не поймать некоторые исключения которые затем приводят к SIGABRT. В обработчике сигнала по сигналу sigabrt тогда я вызов fork() и пропускная() либо ГДБ (в отладочных сборках) или тормоза stackwalk Google (в версии). Также я стараюсь использовать только безопасные функции обработчика сигналов.
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-- = ''; 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: чтобы заставить его работать breakpad мне также пришлось добавить это:
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 });источник: как получить трассировку стека для C++ с помощью gcc с информацией о номере строки? и можно ли подключить gdb к аварийному процессу (он же отладка "точно в срок")
Cpp-tool ex_diag - easyweight, multiplatform, минимальный ресурс используя, простой и гибкий на трассировке.
следующий код останавливает выполнение сразу после исключения. Вам нужно установить windows_exception_handler вместе с обработчиком завершения. Я проверил это в 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); ... }Проверьте следующий код для функции windows_exception_handler: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html
Comments