Выяснить источник исключения в C ++ после того, как оно поймано? - PullRequest
11 голосов
/ 30 августа 2008

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

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

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

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

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

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

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

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

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

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

Ответы [ 13 ]

12 голосов
/ 30 августа 2008

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

11 голосов
/ 13 сентября 2008

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

#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!");
6 голосов
/ 30 августа 2008

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

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

4 голосов
/ 24 октября 2008

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

4 голосов
/ 30 августа 2008

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

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

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

2 голосов
/ 30 августа 2008

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

1 голос
/ 24 октября 2008

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

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

1 голос
/ 06 сентября 2008

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

1 голос
/ 02 сентября 2008

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

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

1 голос
/ 30 августа 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...