Как я нахожу, где исключение было выдано в C++?

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

Там какой-либо путь состоит в том, чтобы сказать, куда мои исключения прибывают из за исключением установки 'броска выгоды' в gdb и вызове следа для каждой вызванной исключительной ситуации?

85
задан Alex 14 March 2010 в 18:05
поделиться

5 ответов

Вот некоторая информация, которая может быть полезной при отладке вашей проблемы

Если исключение не перехвачено, специальная библиотечная функция std :: terminate () вызывается автоматически. Terminate на самом деле является указателем на функцию, а значением по умолчанию является функция стандартной библиотеки C std :: abort () .Если для неперехваченного исключения не происходит очистки, это может действительно помочь в отладке этой проблемы, поскольку деструкторы не вызываются.
† Это определяется реализацией, разворачивается ли стек перед вызовом std :: terminate () .


Вызов abort () часто бывает полезен при создании дампа памяти, который можно проанализировать для определения причины исключения. Убедитесь, что вы включили дампы ядра с помощью ulimit -c unlimited (Linux).


Вы можете установить свою собственную функцию terminate () , используя std :: set_terminate () . У вас должна быть возможность установить точку останова для функции завершения в gdb. Вы можете иметь возможность сгенерировать трассировку стека из вашей функции terminate () , и эта трассировка может помочь в определении местоположения исключения.

Краткое обсуждение неперехваченных исключений в Брюс Эккель «Мышление на C ++», 2-е изд. также может быть полезным.


Поскольку terminate () по умолчанию вызывает abort () (что по умолчанию вызывает сигнал SIGABRT ), вы можете иметь возможность установить обработчик SIGABRT , а затем распечатать трассировку стека из обработчика сигнала . Эта обратная трассировка может помочь в определении местоположения исключения.


Примечание: Я говорю, что может , потому что C ++ поддерживает нелокальную обработку ошибок за счет использования языковых конструкций для отделения обработки ошибок и кода сообщения от обычного кода. Блок catch может быть и часто находится в другой функции / методе, чем точка выброса. В комментариях мне также было указано (спасибо Dan ), что это зависит от реализации, будет ли развернут стек перед вызовом terminate () .

Обновление: Я собрал тестовую программу Linux под названием, которая генерирует обратную трассировку в функции terminate () , установленной с помощью set_terminate () и другой в обработчике сигналов для SIGABRT . Обе трассировки правильно показывают местоположение необработанного исключения.

Обновление 2: Благодаря сообщению в блоге на Перехват неперехваченных исключений в terminate , я узнал несколько новых уловок; включая повторную генерацию неперехваченного исключения в обработчике завершения. Важно отметить, что пустой оператор throw в пользовательском обработчике завершения работает с GCC и не является переносимым решением.

Код:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <string.h>

#include <iostream>
#include <cstdlib>
#include <stdexcept>

void my_terminate(void);

namespace {
    // invoke set_terminate as part of global constant initialization
    static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}

// This structure mirrors the one found in /usr/include/asm/ucontext.h
typedef struct _sig_ucontext {
   unsigned long     uc_flags;
   struct ucontext   *uc_link;
   stack_t           uc_stack;
   struct sigcontext uc_mcontext;
   sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) {
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    // Get the address at the time the signal was raised from the EIP (x86)
    void * caller_address = (void *) uc->uc_mcontext.eip;

    std::cerr << "signal " << sig_num 
              << " (" << strsignal(sig_num) << "), address is " 
              << info->si_addr << " from " 
              << caller_address << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    std::cerr << __FUNCTION__ << " backtrace returned " 
              << size << " frames\n\n";

    // overwrite sigaction with caller's address
    array[1] = caller_address;

    char ** messages = backtrace_symbols(array, size);

    // skip first stack frame (points here)
    for (int i = 1; i < size && messages != NULL; ++i) {
        std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}

void my_terminate() {
    static bool tried_throw = false;

    try {
        // try once to re-throw currently active exception
        if (!tried_throw++) throw;
    }
    catch (const std::exception &e) {
        std::cerr << __FUNCTION__ << " caught unhandled exception. what(): "
                  << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." 
                  << std::endl;
    }

    void * array[50];
    int size = backtrace(array, 50);    

    std::cerr << __FUNCTION__ << " backtrace returned " 
              << size << " frames\n\n";

    char ** messages = backtrace_symbols(array, size);

    for (int i = 0; i < size && messages != NULL; ++i) {
        std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
    }
    std::cerr << std::endl;

    free(messages);

    abort();
}

int throw_exception() {
    // throw an unhandled runtime error
    throw std::runtime_error("RUNTIME ERROR!");
    return 0;
}

int foo2() {
    throw_exception();
    return 0;
}

int foo1() {
    foo2();
    return 0;
}

int main(int argc, char ** argv) {
    struct sigaction sigact;

    sigact.sa_sigaction = crit_err_hdlr;
    sigact.sa_flags = SA_RESTART | SA_SIGINFO;

    if (sigaction(SIGABRT, &sigact, (struct sigaction *)NULL) != 0) {
        std::cerr << "error setting handler for signal " << SIGABRT 
                  << " (" << strsignal(SIGABRT) << ")\n";
        exit(EXIT_FAILURE);
    }

    foo1();

    exit(EXIT_SUCCESS);
}

Вывод:

my_terminate caught unhanded exception. what(): RUNTIME ERROR!
my_terminate backtrace returned 10 frames

[bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52]
[bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (5) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (6) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (7) ./test(main+0xc1) [0x8049121]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21]

signal 6 (Aborted), address is 0x1239 from 0x42029331
crit_err_hdlr backtrace returned 13 frames

[bt]: (1) ./test(kill+0x11) [0x42029331]
[bt]: (2) ./test(abort+0x16e) [0x4202a8c2]
[bt]: (3) ./test [0x8048f9f]
[bt]: (4) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (6) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (8) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (9) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (10) ./test(main+0xc1) [0x8049121]
[bt]: (11) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (12) ./test(__eh_alloc+0x3d) [0x8048b21]

70
ответ дан 24 November 2019 в 08:19
поделиться

У меня есть код для этого в Windows / Visual Studio, дайте мне знать, если вам нужна схема. Не знаю, как это сделать для кода dwarf2, быстрый гугл подсказывает, что в libgcc есть функция _Unwind_Backtrace, которая, вероятно, является частью того, что вам нужно.

1
ответ дан 24 November 2019 в 08:19
поделиться

Проверьте эту ветку, возможно, это поможет:

Обнаружение всех необработанных исключений C ++?

Я получил хороший опыт работы с этим программным обеспечением:

http : //www.codeproject.com/KB/applications/blackbox.aspx

Он может распечатать трассировку стека в файл для любого необработанного исключения.

1
ответ дан 24 November 2019 в 08:19
поделиться

Вы можете создать макрос, например:

#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )

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

16
ответ дан 24 November 2019 в 08:19
поделиться

Вы не передали информацию о том, какую ОС / компилятор вы используете.

В Visual Studio C ++ исключения могут быть инструментированы.

См. «Visual C ++ Exception-Handling Instrumentation» на ddj.com

Моя статья «Посмертная отладка» , также на ddj.com, включает код использовать структурированную обработку исключений Win32 (используемую инструментарием) для ведения журнала и т. д.

5
ответ дан 24 November 2019 в 08:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: