Windows Exception Handling Features
Having read the recent topic " Using try - catch for debugging " I decided to still share my experience as an addition.
In this article, I propose to consider
receiving a callstack of the place where an exception was thrown in case of working with
structural exceptions (MS Windows). We will not go into details of the operation of exceptions, because it draws on a separate series of articles (for those who are interested I recommend Richter, MSDN and wasm.ru). Of course, there are many ready-made projects for generating
What to do with the received call stack is up to you. You can watch it with a debugger, you can write to a file and watch it with a third-party program (for this, do not forget to write down the list of loaded modules with their addresses, as well as you will need debugging symbols).
I would like to note right away that getting a call stack in the exception constructor is not the best option. Not all exceptions are yours, and there is also a class of hardware exceptions that are not caught using the structure
We will go to the solution in an iterative manner so that it becomes clear how this works.
In the case of hardware exception, everything is simple. The stack does not spin up, and we can get a call stack in the exception filter. In the case of a program exception, when we get into the block, the
By the way, in this case the block
So, we sketch out what happened (this construction does not compile, because you cannot use different forms of exception handling inside the same function):
Now you can make a convenient wrapper so as not to block such designs every time and to be able to control it all in one place. In my opinion, this kind of wrapper is better than a bunch of preprocessor directives.
Wrap Requirements:
If your method is a class method, and / or that returns something and / or takes arguments, then all these constructions can always be represented as a delegate that does not return anything and does not require arguments.
Wrapper Highlights:
Our Wrapper Interface:
Implementation:
A function
The topmost wrapper that prevents the further spread of hardware exceptions.
The second wrapper - we catch "C ++ - exceptions" and prevent the further distribution of program exceptions.
And the third wrapper that calls the delegate passed in and the function
Use cases on google test framework .
Hardware exception:
Software exception:
I note that exception handling can be a costly operation in terms of performance, but useful for debugging.
In this article, I propose to consider
receiving a callstack of the place where an exception was thrown in case of working with structural exceptions (MS Windows). We will not go into details of the operation of exceptions, because it draws on a separate series of articles (for those who are interested I recommend Richter, MSDN and wasm.ru). Of course, there are many ready-made projects for generating
minidump's (for example CrashRpt or google-breakpad ), so this article is more educational in nature.What to do with the received call stack is up to you. You can watch it with a debugger, you can write to a file and watch it with a third-party program (for this, do not forget to write down the list of loaded modules with their addresses, as well as you will need debugging symbols).
Theoretical part
I would like to note right away that getting a call stack in the exception constructor is not the best option. Not all exceptions are yours, and there is also a class of hardware exceptions that are not caught using the structure
try-catch, but with the help __try-__except. We will go to the solution in an iterative manner so that it becomes clear how this works.
In the case of hardware exception, everything is simple. The stack does not spin up, and we can get a call stack in the exception filter. In the case of a program exception, when we get into the block, the
catchstack is already untwisted, and in the constructor of the exception, we agreed not to receive the stack. But, it turns out that if it try-catchwraps __try-__except, then, even in the case of a program exception, we first go to the filter passed to__except. Here we can get the call stack, but what should the filter return? If the filter returns EXCEPTION_EXECUTE_HANDLER, then we will not get to try-catch. Well, let’s return EXCEPTION_CONTINUE_SEARCH, which will prompt the handler to look for the next filter that will return EXCEPTION_EXECUTE_HANDLER. In this case, with a software exception, we get to try-catch, and in the case of a hardware exception, the exception handling mechanism will go to look for the handler down the stack, skip it try-catchand so on until it encounters __exceptan argument EXCEPTION_EXECUTE_HANDLER. Ok then wrap try-catchin __try-__except(EXCEPTION_EXECUTE_HANDLER). By the way, in this case the block
__except(filter()/*-> EXCEPTION_CONTINUE_SEARCH*/)
{
/*этот блок*/
}will never succeed. So, we sketch out what happened (this construction does not compile, because you cannot use different forms of exception handling inside the same function):
__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)
{
}Useful wrapper
Now you can make a convenient wrapper so as not to block such designs every time and to be able to control it all in one place. In my opinion, this kind of wrapper is better than a bunch of preprocessor directives.
Wrap Requirements:
- In the simplest case, no exception should extend beyond the wrapper.
- Accepts a delegate that returns nothing and does not accept arguments ( )
function - We believe that if an exception occurs, then the wrapper should report this by returning
false.
If your method is a class method, and / or that returns something and / or takes arguments, then all these constructions can always be represented as a delegate that does not return anything and does not require arguments.
Wrapper Highlights:
Our Wrapper Interface:
struct SafeExecutor
{
typedef boost::function TDoDelegate;
SafeExecutor(TDoDelegate doDelegate);
// true - the everything is successful
// false - otherwise
bool Do();
private:
bool DoCPlusPlusExceptionWrapper();
bool DoWorkWrapper();
private:
TDoDelegate m_DoDelegate;
}; Implementation:
A function
Filterin which we should receive a call stack, and which returns EXCEPTION_CONTINUE_SEARCH: LONG Filter( PEXCEPTION_POINTERS pep )
{
// pep->ExceptionRecord->ExceptionCode
// pep->ExceptionRecord->ExceptionAddress
// GetModules();
// GetCallStack();
return EXCEPTION_CONTINUE_SEARCH;
}The topmost wrapper that prevents the further spread of hardware exceptions.
bool SafeExecutor::Do()
{
bool AbnornalTermination = false;
bool IsExecSuccessful = true;
{
__try
{
IsExecSuccessful = DoCPlusPlusExceptionWrapper();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
AbnornalTermination = true;
}
}
return !AbnornalTermination && IsExecSuccessful;
}The second wrapper - we catch "C ++ - exceptions" and prevent the further distribution of program exceptions.
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;
}And the third wrapper that calls the delegate passed in and the function
Filterin which we need to get the call stack.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;
}Use cases on google test framework .
Hardware exception:
int HWUnsafe()
{
int z = 0;
return 1/z;
}
TEST(HWUnsafe, SafeExecutor)
{
SafeExecutorNS::SafeExecutor se(HWUnsafe);
ASSERT_FALSE(se.Do());
}Software exception:
int SWUnsafe1()
{
int z = 1;
throw std::exception();
return 1/z;
}
TEST(SW_std_ex, SafeExecutor)
{
SafeExecutorNS::SafeExecutor se(SWUnsafe1);
ASSERT_FALSE(se.Do());
}I note that exception handling can be a costly operation in terms of performance, but useful for debugging.