Выяснить источник исключения в С++ после его заражения?

Я ищу ответ в MS VС++.

При отладке большого приложения на С++, которое, к сожалению, имеет очень широкое использование исключений С++. Иногда я получаю исключение чуть позже, чем я действительно хочу.

Пример в псевдокоде:

FunctionB()
{
    ...
    throw e;
    ...
}

FunctionA()
{
    ...
    FunctionB()
    ...
}

try
{
    Function A()
}
catch(e)
{
    (<--- breakpoint)
    ...
}

Я могу поймать исключение с точкой останова при отладке. Но я не могу отследить, если исключение произошло в FunctionA() или FunctionB(), или какая-либо другая функция. (Предполагая широкое использование исключений и огромную версию приведенного выше примера).

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

Есть ли более простой способ, который требует меньше работы? Без необходимости менять мою большую базу кода?

Есть ли лучшие решения этой проблемы на других языках?

Ответ 1

Если вас просто интересует, откуда исходило исключение, вы можете просто написать простой макрос, например

#define throwException(message) \
    {                           \
        std::ostringstream oss; \
        oss << __FILE __ << " " << __LINE__ << " "  \
           << __FUNC__ << " " << message; \
        throw std::exception(oss.str().c_str()); \
    }

который добавит имя файла, номер строки и имя функции в текст исключения (если компилятор предоставляет соответствующие макросы).

Затем выполните исключения, используя

throwException("An unknown enum value has been passed!");

Ответ 2

Вы указали на точку останова в коде. Поскольку вы находитесь в отладчике, вы можете установить точку останова на конструкторе класса исключений или установить отладчик Visual Studio для разбиения всех исключенных исключений (Debug- > Exceptions Нажмите на исключения С++, выберите брошенные и неперечисленные варианты)

Ответ 3

Там отличная книга, написанная Джоном Роббинсом, которая занимается многими сложными вопросами отладки. Эта книга называется Отладка приложений для Microsoft.NET и Microsoft Windows. Несмотря на название, книга содержит множество информации об отладке собственных приложений на С++.

В этой книге содержится длинный раздел о том, как получить стек вызовов для исключений, которые были выбраны. Если я правильно помню, некоторые его советы включают использование структурированной обработки исключений (SEH) вместо (или в дополнение) исключений С++. Я действительно не могу рекомендовать книгу достаточно высоко.

Ответ 4

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

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

Вы можете использовать функцию StackTrace64 для построения трассировки (я считаю, что есть и другие способы). Проверьте эту статью, например, код.

Ответ 5

Поместите контрольную точку в конструктор объекта исключения. Вы получите свою точку останова до того, как будет выброшено исключение.

Ответ 6

Вот как я это делаю на С++, используя библиотеки GCC:

#include <execinfo.h> // Backtrace
#include <cxxabi.h> // Demangling

vector<Str> backtrace(size_t numskip) {
    vector<Str> result;
    std::vector<void*> bt(100);
    bt.resize(backtrace(&(*bt.begin()), bt.size()));
    char **btsyms = backtrace_symbols(&(*bt.begin()), bt.size());
    if (btsyms) {
        for (size_t i = numskip; i < bt.size(); i++) {
            Aiss in(btsyms[i]);
            int idx = 0; Astr nt, addr, mangled;
            in >> idx >> nt >> addr >> mangled;
            if (mangled == "start") break;
            int status = 0;
            char *demangled = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);

            Str frame = (status==0) ? Str(demangled, demangled+strlen(demangled)) : 
                                      Str(mangled.begin(), mangled.end());
            result.push_back(frame);

            free(demangled);
        }
        free(btsyms);
    }
    return result;
}

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

Ответ 7

Нет стандартного способа сделать это.

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

В VС++ на Win32/Win64 вы можете получить достаточные для использования результаты, записав значение из встроенного _ReturnAddress() компилятора и убедитесь, что ваш конструктор класса исключений - __declspec (noinline). В сочетании с библиотекой символов отладки, я думаю, вы могли бы получить имя функции (и номер строки, если ваш .pdb содержит), который соответствует обратному адресу с помощью SymGetLineFromAddr64.

Ответ 8

В собственном коде вы можете получить снимок при вызове вызова, установив Vectored Exception handler. VС++ реализует исключения С++ поверх исключений SEH, а векторный обработчик исключений получает первый снимок перед любыми обработчиками на основе фреймов. Однако будьте очень осторожны, проблемы, возникающие при обработке векторных исключений, могут быть трудно диагностировать.

Также У Майка Столла есть некоторые предупреждения об использовании его в приложении с управляемым кодом. Наконец, прочитайте статью Matt Pietrek и убедитесь, что вы понимаете SEH и векторную обработку исключений, прежде чем пытаться это сделать. (Ничто не чувствует себя так плохо, как отслеживание критической проблемы, чтобы код, который вы добавили, помог отслеживать критические проблемы.)

Ответ 9

Если вы отлаживаете из среды IDE, перейдите в Debug- > Exceptions, нажмите "Бросить" для исключений на С++.

Ответ 10

Я считаю, что MSDev позволяет устанавливать точки останова при вызове исключения.

Альтернативно поместите точку перерыва в конструктор вашего объекта исключения.

Ответ 11

Другие языки? Ну, на Java вы вызываете e.printStackTrace(); Это не намного проще.

Ответ 12

В случае, если кто-то заинтересован, сотрудник ответил на этот вопрос мне по электронной почте:

Артем писал (а):

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

Кроме того, я думаю, что отключение встроенных функций и оптимизация всей программы помогут довольно много.

На самом деле, существует много типов дампов, возможно, вы можете выбрать один достаточно маленький, но все еще иметь больше информации http://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx

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

Я заметил, что некоторые из этих типов дампов не поддерживаются в dbghelp.dll версии 5.1, которые мы используем. Мы могли бы обновить его до новейшей версии 6.9, хотя я только что проверил EULA для MS Debugging Tools - новый dbghelp.dll по-прежнему подходит для распространения.

Ответ 13

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

throw Exception( "comms::serial::serial( )", "Something failed!" );

Также у меня есть второй формат исключения:

throw Exception( "comms::serial::serial( )", ::GetLastError( ) );

который затем преобразуется из значения DWORD в фактическое сообщение с помощью FormatMessage. Использование формата where/what покажет вам, что произошло и в какой функции.