Трассировка стека на С++ при исключении

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

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

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

Ответ 1

Это зависит от какой платформы.

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

#include <boost/stacktrace.hpp>

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

Ответ 5

Я хотел бы добавить стандартную библиотечную опцию standard library option (то есть кроссплатформенную), как генерировать трассировки исключений, которая стала доступна с С++ 11:

Используйте std::nested_exception и std::throw_with_nested

Это не даст вам раскрутить стек, но, на мой взгляд, следующая лучшая вещь. В Qaru здесь и здесь описывается, как вы можете отследить ваши исключения внутри вашего кода без необходимости в отладчике или громоздкой регистрации, просто написание правильного обработчика исключений, который будет перебрасывать вложенные исключения.

Поскольку вы можете сделать это с любым производным классом исключений, вы можете добавить много информации к такой обратной трассировке! Вы также можете взглянуть на мой 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"

Ответ 6

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

Ответ 8

В Windows проверьте BugTrap. Он не длиннее в исходной ссылке, но по-прежнему доступен в CodeProject.

Ответ 10

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

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

Изменение: Чтобы заставить его работать для перемычки, мне также пришлось добавить следующее:

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 к аварийному процессу (например, отладить "точно в срок")

Ответ 11

Poppy может собирать не только трассировку стека, но и значения параметров, локальные переменные и т.д. - все, что приводит к сбою.

Ответ 12

У меня есть аналогичная проблема, и хотя мне нравится переносимость, мне нужна только поддержка gcc. В gcc доступны функции execinfo.h и 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 дает обратную трассировку с красиво неперепутанными именами функций С++:

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]

Ответ 13

Cpp-tool ex_diag - легкий, многоплатформенный, минимальный ресурс, простой и гибкий в трассировке.

Ответ 14

Следующий код останавливает выполнение сразу после генерирования исключения. Вам нужно установить команду 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

Ответ 15

Если вы используете C++ и не хотите/не можете использовать Boost, вы можете распечатать обратную трассировку с вычеркнутыми именами, используя следующий код [ссылка на оригинальный сайт].

Обратите внимание, что это решение специфично для Linux. Он использует функции GNU libc backtrace()/backtrace_symbols() (из execinfo.h), чтобы получить обратные трассы, а затем использует __cxa_demangle() (из cxxabi.h) для разборки имен символов обратной трассировки.

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

НТН!