Трассировка стека отображения C ++ при исключении - PullRequest
172 голосов
/ 28 марта 2009

Я хочу иметь способ сообщить трассировку стека пользователю, если возникнет исключение. Каков наилучший способ сделать это? Требуется ли огромное количество дополнительного кода?

Чтобы ответить на вопросы:

Я бы хотел, чтобы он был портативным, если это возможно. Я хочу, чтобы информация всплывала, чтобы пользователь мог скопировать трассировку стека и отправить ее мне по электронной почте в случае возникновения ошибки.

Ответы [ 14 ]

69 голосов
/ 28 марта 2009

Зависит от того, какая платформа.

В GCC это довольно тривиально, см. этот пост для более подробной информации.

В MSVC вы можете использовать библиотеку StackWalker , которая обрабатывает все основные вызовы API, необходимые для Windows.

Вам нужно будет найти лучший способ интеграции этой функции в ваше приложение, но объем кода, который вам нужно написать, должен быть минимальным.

45 голосов
/ 12 ноября 2014

Ответ Эндрю Гранта не помогает получить трассировку стека функции throwing , по крайней мере, не в GCC, поскольку оператор throw не сохраняет текущая трассировка стека сама по себе, и обработчик перехвата больше не будет иметь доступа к трассировке стека.

Единственный способ - с помощью GCC - решить эту проблему - убедиться, что генерируется трассировка стека в точке инструкции throw, и сохраните ее с объектом исключения.

Этот метод требует, конечно, чтобы каждый код, который выдает исключение, использовал этот конкретный класс Exception.

Обновление от 11 июля 2017 года : чтобы получить полезный код, взгляните на ответ cahit beyaz, который указывает на http://stacktrace.sourceforge.net - я еще не использовал его, но выглядит многообещающе.

34 голосов
/ 11 сентября 2017

Если вы используете Boost 1.65 или выше, вы можете использовать boost :: stacktrace :

#include <boost/stacktrace.hpp>

// ... somewhere inside the bar(int) function that is called recursively:
std::cout << boost::stacktrace::stacktrace();
13 голосов
/ 31 августа 2013
4 голосов
/ 03 декабря 2017

Я хотел бы добавить стандартную библиотечную опцию (т.е. кроссплатформенный), как генерировать трассировки исключений, которая стала доступна с 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"
4 голосов
/ 08 марта 2017

Рекомендую http://stacktrace.sourceforge.net/ проект. Он поддерживает Windows, Mac OS, а также Linux

4 голосов
/ 19 декабря 2012

AFAIK libunwind довольно переносим, ​​и до сих пор я не нашел ничего более легкого в использовании.

3 голосов
/ 26 июля 2018

Поскольку стек уже разматывается при входе в блок catch, в моем случае решение было не перехватывать определенные исключения , которые затем приводили к SIGABRT. В обработчике сигналов для SIGABRT я затем выполняю fork () и execl () либо gdb (в сборках отладки), либо стеколом Google breakpads (в ​​сборках выпуска). Также я стараюсь использовать только безопасные функции обработчика сигналов.

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

Редактировать: чтобы это работало для 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 к аварийному процессу (он же отладка "точно в срок") )

3 голосов
/ 23 сентября 2013

в linux с g ++ посмотрите эту библиотеку

https://sourceforge.net/projects/libcsdbg

он сделает всю работу за вас

3 голосов
/ 07 января 2010

В Windows проверьте BugTrap . Его больше нет по исходной ссылке, но он все еще доступен в CodeProject.

...