Выяснить источник исключения в C ++ после того, как оно поймано?

StackOverflow https://stackoverflow.com/questions/36077

Вопрос

Я ищу ответ в MS VC ++.

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

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

#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!");

Другие советы

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

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

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

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

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

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

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

Вот как я это делаю в C ++ с использованием библиотек 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 , потому что мне нравится вырезать конструктор исключения из моих трассировок стека.

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

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

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

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

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

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

Или же установите точку останова на конструкторе объекта исключения.

Если вы выполняете отладку в среде IDE, перейдите в раздел «Отладка -> Исключения» и нажмите «Сгенерировано для исключений C ++».

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

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

Артем написал:

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top