C ++ слід стека відображення за винятком


204

Я хочу мати спосіб повідомляти про стежку стека користувачеві, якщо буде викинуто виняток. Який найкращий спосіб зробити це? Це займає величезні кількості додаткового коду?

Щоб відповісти на запитання:

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

Відповіді:


76

Це залежить, яка платформа.

Про GCC це досить тривіально, дивіться цей пост для більш детальної інформації.

У MSVC ви можете використовувати бібліотеку StackWalker, яка обробляє всі основні виклики API, необхідні для Windows.

Вам доведеться з’ясувати найкращий спосіб інтегрувати цю функціональність у свій додаток, але кількість коду, який потрібно написати, має бути мінімальним.


71
публікація, на яку ви посилаєтесь, в основному вказує на генерування сліду від segfault, але запитувач конкретно згадує винятки, які є зовсім іншим звіром.
Шеп

8
Я погоджуюся з @Shep - ця відповідь не дуже допомагає отримати стек стеження коду викидання на GCC. Дивіться мою відповідь щодо можливого рішення.
Томас Темпельман

1
Ця відповідь вводить в оману. Посилання вказує на відповідь, яка Linuxне відповідає gcc.
fjardon

Ви можете змінити механізм викидання libstdc++(використовується GCC та потенційно Clang), як пояснено у цій відповіді .
ingomueller.net

59

Відповідь Ендрю Гранта не допомагає отримати слід стека функції метання , принаймні, не з GCC, оскільки операція кидання не зберігає поточний слід стека самостійно, і обробник лову не матиме доступу до сліду стека на це вже більше.

Єдиний спосіб - за допомогою GCC - вирішити це - переконатися, що генерується стек стека в точці інструкції кидати, і зберегти його за допомогою об’єкта винятку.

Цей метод вимагає, звичайно, щоб кожен код, який видає виняток, використовував саме цей клас винятку.

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


1
На жаль, посилання мертва. Не могли б ви надати якусь іншу?
Warran

2
І архів.org теж цього не знає. Блін. Ну, процедура повинна бути зрозумілою: кинути об'єкт користувальницького класу, який записує слід стека на момент кидка.
Томас Темпельман

1
На домашній сторінці StackTrace я бачу throw stack_runtime_error. Чи правильно я вважаю, що ця lib працює лише за винятками, отриманими з цього класу, а не для std::exceptionвиключень із сторонніх бібліотек?
Томас

3
Отже, на жаль, відповідь "Ні, ви не можете отримати слід стека за винятком C ++", єдиний варіант - кинути свій власний клас, який генерує слід стека при його побудові. Якщо ви застрягли, використовуючи такі речі, як, скажімо, будь-яка частина бібліотеки C ++ std ::: вам не пощастило. Вибачте, смокче бути тобою.
Кодовий мерзотник

43

Якщо ви використовуєте 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
Документи підсилення пояснюють не лише фіксування сліду стека, але і як це зробити за винятками та твердженнями. Чудові речі.
настрій настрою

1
Чи друкує цей стектрейс () друкує вихідний файл та номери рядків, як зазначено в посібнику GettingStarted?
Гімхані


11

Я хотів би додати стандартний варіант бібліотеки (тобто крос-платформний), як генерувати зворотні звороти, які стали доступними для 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

Я рекомендую проект http://stacktrace.sourceforge.net/ . Він підтримує Windows, Mac OS, а також Linux


4
На його домашній сторінці я бачу throw stack_runtime_error. Чи правильно я вважаю, що ця lib працює лише за винятками, отриманими з цього класу, а не для std::exceptionвиключень із сторонніх бібліотек?
Томас

4

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

Зауважте, це рішення є специфічним для Linux. Він використовує функції libc GNU backtrace () / backtrace_symbols () (від execinfo.h) для отримання зворотних треків, а потім використовує __cxa_demangle () (від cxxabi.h) для демонтажу імен символів 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

У Windows перевірте BugTrap . Її вже не на вихідному посиланні, але вона все ще доступна в CodeProject.


3

У мене є подібна проблема, і хоча мені подобається портативність, мені потрібна лише підтримка 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, ви отримуєте зворотній слід із непогашеними назви функцій 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]

3

Оскільки стек вже розкручений при введенні блоку лову, у моєму випадку рішенням було не ловити певні винятки які потім призводять до SIGABRT. У обробнику сигналу для SIGABRT я потім fork () та execl () або gdb (у налагодженнях налагодження), або 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-- = '\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 до збійного процесу (він же "на час" налагодження)


2

Мак може зібрати не тільки слід стека, але і значення параметрів, локальні змінні тощо - все, що призводить до збоїв.


2

Наступний код зупиняє виконання відразу після скидання виключення. Потрібно встановити windows_exception_handler разом з обробником припинення. Я перевірив це на 32-бітних 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);
...
}

Перевірте наступний код функції Windows_exception_handler: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html


1

Cpp-інструмент ex_diag - легкий, багатоплатформенний, з мінімальним використанням ресурсів, простий і гнучкий у сліді.


Я перевірив цей проект у 2017.12.24, джерело та завантажую обидва, не доступні.
zhaorufei

1
Я щойно перевірив code.google.com/archive/p/exception-diagnostic/source/default/… і джерело можна завантажити. Можна спробувати ще раз?
Борис
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.