В этой статье предлагаю рассмотреть получение callstack’а места, где было брошено исключение в случае работы со
структурными исключениями (MS Windows). В детали работы исключений вдаваться не будем, т.к. это тянет на отдельный цикл статей (для интересующихся рекомендую Рихтера, MSDN и wasm.ru). Конечно, есть много уже готовых проектов для генерации minidump
’ов (например CrashRpt или google-breakpad), так что эта статья носит больше образовательный характер.
Что делать с полученным стеком вызовов — решать вам. Можно смотреть отладчиком, можно записать в файл и смотреть сторонней программой (для этого не забудьте записать список загруженных модулей с их адресами, а так же вам понадобятся отладочные символы).
Теоретическая часть
Сразу же хотелось бы заметить, что получать стек вызовов в конструкторе исключения — не самый лучший вариант. Не все исключения ваши, и есть еще класс аппаратных исключений, которые отлавливаются не с помощью конструкции try-catch
, а с помощью __try-__except
.
Будем идти к решению в итеративном порядке, чтобы стало понятно, как это работает.
В случае с аппаратным исключением все просто. Стек не раскручивается, и мы можем в фильтре исключения получить стек вызовов. В случае с программным исключением, когда мы попадаем в блок catch
стек уже раскручен, а в конструкторе исключения мы договорились стек не получать. Но, оказывается, что если try-catch
оборачивает __try-__except
, то, даже в случае программного исключения, мы сначала заходим в фильтр, переданный в __except
. Тут мы можем получить стек вызовов, но что должен возвращать фильтр? Если фильтр вернет EXCEPTION_EXECUTE_HANDLER
, тогда мы не дойдем до try-catch
. Что же, вернем EXCEPTION_CONTINUE_SEARCH
, который побудит обработчик искать следующий фильтр, который вернет EXCEPTION_EXECUTE_HANDLER
. В этом случае с программным исключением мы дойдем до try-catch
, а в случае с аппаратным исключением механизм обработки исключений пойдет искать обработчик дальше по стеку, пропустит try-catch
и так до тех пор, пока не встретит __except
с аргументом EXCEPTION_EXECUTE_HANDLER
. Хорошо, тогда обернем try-catch
в __try-__except(EXCEPTION_EXECUTE_HANDLER)
.
Кстати, в этом случае блок
__except(filter()/*-> EXCEPTION_CONTINUE_SEARCH*/) { /*этот блок*/ }
никогда не выполнится.
Итак, изобразим схематично, что получилось (это конструкция не компилируется, потому что внутри одной функции нельзя использовать разные формы обработки исключений):
__try { try { __try { useful_unsafe_function(); } __except(filter()/*-> EXCEPTION_CONTINUE_SEARCH*/) { // this block will be never executed } } catch(const your_lib::Exception& ex) { } catch(const std::exception& ex) { } catch(...) { } } __except(EXCEPTION_EXECUTE_HANDLER) { }
Полезная обертка
Теперь можно сделать удобную обертку, чтобы не городить такие конструкции каждый раз и чтобы иметь возможность контролировать это все в одном месте. На мой взгляд такого рода обертка лучше, чем нагромождение из директив препроцессора.
Требования к обертке:
- В простейшем случае за пределы обертки ни одно исключение не должно распространится.
- Принимает делегат, который ничего не возвращает и не принимает аргументов (
function<void()>
) - Считаем, что если произошло исключение, то обертка должна об этом сообщить, вернув
false
.
Если ваш метод — это метод класса, и/или который что-то возвращает и/или принимает аргументы, то все эти конструкции всегда можно представить в виде делегата, который ничего не возвращает и не требует аргументы.
Основные моменты обертки:
Интерфейс нашей обертки:
struct SafeExecutor { typedef boost::function<void()> TDoDelegate; SafeExecutor(TDoDelegate doDelegate); // true - the everything is successful // false - otherwise bool Do(); private: bool DoCPlusPlusExceptionWrapper(); bool DoWorkWrapper(); private: TDoDelegate m_DoDelegate; };
Реализация:
Функция Filter
, в которой мы должны получить стек вызовов, и которая возвращает EXCEPTION_CONTINUE_SEARCH
:
LONG Filter( PEXCEPTION_POINTERS pep ) { // pep->ExceptionRecord->ExceptionCode // pep->ExceptionRecord->ExceptionAddress // GetModules(); // GetCallStack(); return EXCEPTION_CONTINUE_SEARCH; }
Самая верхняя обертка, которая предотвращает дальнейшее распространение аппаратных исключений.
bool SafeExecutor::Do() { bool AbnornalTermination = false; bool IsExecSuccessful = true; { __try { IsExecSuccessful = DoCPlusPlusExceptionWrapper(); } __except(EXCEPTION_EXECUTE_HANDLER) { AbnornalTermination = true; } } return !AbnornalTermination && IsExecSuccessful; }
Вторая обертка — отлавливаем «C++-исключения» и предотвращаем дальнейшее распространение программных исключений.
bool SafeExecutor::DoCPlusPlusExceptionWrapper() { bool res = true; try { res = DoWorkWrapper(); } catch(std::exception& /*ex*/) { // smth like log(ex.what()); //assert(false); res = false; } catch(...) { // smth like log("unknown sw-exception); //assert(false); res = false; } return res; }
И третья обертка, которая вызывает переданный делегат и функцию Filter
, в которой мы должны получить стек вызовов.
bool SafeExecutor::DoWorkWrapper() { bool res = false; if (!m_DoDelegate.empty()) { __try { m_DoDelegate(); res = true; } __except(Filter(GetExceptionInformation())) // we must dump callstack inside this Filter { // never be executed because Filter always returns `CONTINUE_SEARCH` } } return res; }
Примеры использования на google test framework.
Аппаратное исключение:
int HWUnsafe() { int z = 0; return 1/z; } TEST(HWUnsafe, SafeExecutor) { SafeExecutorNS::SafeExecutor se(HWUnsafe); ASSERT_FALSE(se.Do()); }
Программное исключение:
int SWUnsafe1() { int z = 1; throw std::exception(); return 1/z; } TEST(SW_std_ex, SafeExecutor) { SafeExecutorNS::SafeExecutor se(SWUnsafe1); ASSERT_FALSE(se.Do()); }
Замечу, что обработка исключений может быть дорогостоящей операцией в плане производительности, но полезной при отладке.
ссылка на оригинал статьи http://habrahabr.ru/post/163745/
Добавить комментарий