Samstag, 26. März 2011

Exception Handling - Inform your users!

Hello everyone!

This article will show you methods to inform the user that something has happened in the program which caused an exception that was not handled by the program. It will also teach you how to use the values you will get and display some more information the user can then submit to you.

First we should know what happens if an exception is thrown. The operating system now aims to find a handler which tells what to do now. In order to find such a handler the system has a fixed order to search. It will start with the vectored exception handlers (added for example using AddVectoredExceptionHandler). If no handler was found there it will continue and in each frame of the stack it will examine all the frame based exception handlers (try - catch, __try - __except). If no handler suited the exception thrown it will then have a look if a top level exception handler is registered (using SetUnhandledExceptionFilter). If that fails too you will get a window "XY has stopped working" with the commonly known options. There is actually one exception from that behavior: When a debugger is attached. In this case the debugger is automatically installed as the top level exception handler and you cannot change it. Using various settings you can even intercept all of the above mentioned handlers and directly pass every exception to the debugger.

Well, now how could we get informed that an exception happened that was not properly handled? Using AddVectoredExceptionHandler is not a good idea because every exception that happened will be interpreted as if it was not handled. Thats not what we try to achieve. Installing a frame on top of every thread that gets started and handling every type of exception there is possible but to much work. Also threads started by external components maybe dont act like that and exceptions caused by them wont be recognized.

The response is using SetUnhandledExceptionFilter. This function lets us register a function that is called if all other handlers failed. As this is the last resort we can be sure that no one "cared" about that exception and that it will lead to an unwanted program termination if nothing is done now. The usage of that function is very easy. All it wants is a pointer to a function which has a special format. It then returns a pointer to the function that was installed before our call so we could reset it if we dont need to handle the exceptions anymore. So to test that we make a simple program:
#include <Windows.h>

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 MessageBox(0, "An exception occured which wasnt handled!", "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 char* ptr = NULL;
 *ptr = 'a';
}

The return value of our function indicates what the operating system should do next. Returning EXCEPTION_CONTINUE_HANDLER instructs it to execute the handler for that exception. On the top level this actually means that it will directly call ExitProcess. If you run the above code (please make sure you are not attaching any debugger) you will get the messagebox (unless you are in kernel mode) and the application will terminate.

Now lets experiment with that. First we will look if the function also gets called if we surround the bad code with try - catch(...). First maybe think if you can figure out the response yourself.
#include <Windows.h>

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 MessageBox(0, "An exception occured which wasnt handled!", "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 try
 {
  char* ptr = NULL;
  *ptr = 'a';
 }
 catch(...)
 {

 }
}

After running the code we know: It gets called! Maybe that is surprising you. If so have a look at the following code:
#include <Windows.h>

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 MessageBox(0, "An exception occured which wasnt handled!", "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 __try
 {
  char* ptr = NULL;
  *ptr = 'a';
 }
 __except(EXCEPTION_EXECUTE_HANDLER)
 {

 }
}

Wait, now it is not called?! But why? The answer is pretty simple: There are two main types of exceptions. The first type are exceptions that are thrown using the keyword 'throw'. They are called C++-exceptions. These exceptions get caught using the try-catch statement. The other type are exceptions that are thrown by the operating system in response of faults that happened. These are called SEH-exceptions (structed exception handling). To catch such an exception there must be a frame which catches exceptions using __try - __except. We see the EXCEPTION_EXECUTE_HANDLER here again. In this case the executed "handler" is the part in the scope after __except.

As dereferencing the NULL-Pointer and assigning a value to it in user mode is not allowed the operating system indicates a fault and throws a SEH-exception. Those exceptions cannot be handled by try - catch statements. Therefore in the first code the exception is passed to our function. But in the second code the exception is caught and the program can continue so our function is not called.

Well, now our messagebox does not really contain a lot of information. A user which sees that message wont have an idea what to do and if he submits it to you this wont help at all. So providing some more information would be helpful. So we'll use the parameter our exception handler receives to find additional information about the exception which we can show to the user. The most important things one should now are the following two:
What happened?
Where did it happen?

The answer to both questions lies inside the exceptionInfo parameter. And here is how we can access it:
#include <Windows.h>
#include <iostream>

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 char message[255];
 sprintf_s<255>(message, 
  "An exception occured which wasnt handled!\nCode: 0x%08X\nAddress: 0x%08X", 
  exceptionInfo->ExceptionRecord->ExceptionCode,
  exceptionInfo->ExceptionRecord->ExceptionAddress
 );
 MessageBox(0, message, "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 char* ptr = NULL;
 *ptr = 'a';
}

I think this is pretty self-explaining. Though there is one major problem with the exception address. If our binary uses ASLR (Address Space Layout Randomization) its base address will start at a random address at every launch. Therefore the address is completely useless for us as we dont know where the binary starts. To solve that problem we could print the start and end address for every module loaded into the message box which would allow us to exactly determine in which module at which offset the exception happened or we just print the start of the main module and the offset from there. The second version is less informative because if the exception didnt happen in the main module we again have an offset which is not really useful. But anyway, we will use the second approach as we focus on our executable.

#include <Windows.h>
#include <iostream>

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 DWORD codeBase = (DWORD)GetModuleHandle(NULL);
 char message[255];
 sprintf_s<255>(message, 
  "An exception occured which wasnt handled!\nCode: 0x%08X\nOffset: 0x%08X\nCodebase: 0x%08X", 
  exceptionInfo->ExceptionRecord->ExceptionCode,
  (DWORD)exceptionInfo->ExceptionRecord->ExceptionAddress - codeBase,
  codeBase
 );
 MessageBox(0, message, "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 char* ptr = NULL;
 *ptr = 'a';
}

Now we have an offset which can be used to determine which instruction caused the exception. If you start google and search for "Exception code 0xC0000005" you will find that this stands for an access violation. Wouldnt it be nice to show EXCEPTION_ACCESS_VIOLATION instead of 0xC0000005? There is a simple way doing that using a little macro that converts the predefined codes into a friendlier form:
#include <Windows.h>
#include <iostream>

#define EXCEPTION_CASE(code) \
 case code: \
  exceptionString = #code; \
  break

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 const char* exceptionString = NULL;
 switch(exceptionInfo->ExceptionRecord->ExceptionCode)
 {
 EXCEPTION_CASE(EXCEPTION_ACCESS_VIOLATION);
 EXCEPTION_CASE(EXCEPTION_DATATYPE_MISALIGNMENT);
 EXCEPTION_CASE(EXCEPTION_BREAKPOINT);
 EXCEPTION_CASE(EXCEPTION_SINGLE_STEP);
 EXCEPTION_CASE(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
 EXCEPTION_CASE(EXCEPTION_FLT_DENORMAL_OPERAND);
 // add more cases...

 default:
  exceptionString = "Unknown exception";
  break;
 }

 DWORD codeBase = (DWORD)GetModuleHandle(NULL);
 char message[255];
 sprintf_s<255>(message, 
  "An exception occured which wasnt handled!\nCode: %s (0x%08X)\nOffset: 0x%08X\nCodebase: 0x%08X", 
  exceptionString,
  exceptionInfo->ExceptionRecord->ExceptionCode,
  (DWORD)exceptionInfo->ExceptionRecord->ExceptionAddress - codeBase,
  codeBase
 );
 MessageBox(0, message, "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 char* ptr = NULL;
 *ptr = 'a';
}

Now the user sees a better readable name for the exception code. Have a look at this article on MSDN to get a list with all defined exception codes that commonly occur: Article

Now lets change the main function to the following:
int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 throw "An error!";
}

Even if you defined all cases from the MSDN-article here you will get "Unknown exception (weird hex-number)". What does that number stand for? Maybe its a pointer to the string we have thrown? And beside that, why do we get such a huge offset? Our executable isn't that big! To answer the second question first: throw will actually be replaced by the compiler to a internal function call to CxxThrowException which then again calls RaiseException. The offset you get is the offset to RaiseException as this is considered the source of the exception and RaiseException lies inside the ntdll.dll which is loaded in high memory regions. See the following extract from IDA Pro:
.text:7DE80000 ; File Name   : C:\Windows\System32\ntdll.dll
.text:7DE80000 ; Format      : Portable executable for 80386 (PE)
.text:7DE80000 ; Imagebase   : 7DE70000

To answer the first question try throwing different things. Throw an integer, throw nothing (just throw;), throw another string, ... . You will notice that the hex-number will always be the same (0xE06D7363). So obviously it has nothing to do with the content thrown. No, it denotes that the thrown exception was a C++ exception (remember, these are exceptions thrown by the programmer using throw). So every time you use throw you will get the code mentioned above. So we can extend our switch with that static code:
#include <Windows.h>
#include <iostream>

#define EXCEPTION_CASE(code) \
 case code: \
  exceptionString = #code; \
  break

LONG WINAPI UnhandledException(LPEXCEPTION_POINTERS exceptionInfo)
{
 const char* exceptionString = NULL;
 switch(exceptionInfo->ExceptionRecord->ExceptionCode)
 {
 EXCEPTION_CASE(EXCEPTION_ACCESS_VIOLATION);
 EXCEPTION_CASE(EXCEPTION_DATATYPE_MISALIGNMENT);
 EXCEPTION_CASE(EXCEPTION_BREAKPOINT);
 EXCEPTION_CASE(EXCEPTION_SINGLE_STEP);
 EXCEPTION_CASE(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
 EXCEPTION_CASE(EXCEPTION_FLT_DENORMAL_OPERAND);
 // add more cases...

 case 0xE06D7363:
  exceptionString = "C++ exception (using throw)";
  break;

 default:
  exceptionString = "Unknown exception";
  break;
 }

 DWORD codeBase = (DWORD)GetModuleHandle(NULL);
 char message[255];
 sprintf_s<255>(message, 
  "An exception occured which wasnt handled!\nCode: %s (0x%08X)\nOffset: 0x%08X\nCodebase: 0x%08X", 
  exceptionString,
  exceptionInfo->ExceptionRecord->ExceptionCode,
  (DWORD)exceptionInfo->ExceptionRecord->ExceptionAddress - codeBase,
  codeBase
 );
 MessageBox(0, message, "Error!", MB_OK);
 return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
 SetUnhandledExceptionFilter(UnhandledException);

 throw "An error!";
}

So far those are the most important things you should now about displaying generic information about every type of exception. In upcoming articles we will have a look on how we can give more information which will require us to perform individual actions for each type of exception.

Thanks for reading and happy commenting
Yanick

11 Kommentare:

  1. Thanks a lot for this really useful information.

    AntwortenLöschen
  2. ayo bergabung dengan bolavita khusus new member lgsg di berikan 10%
    tanpa ribet dan masih banyak bonus2 lain nya
    semua di berikan tanpa ribet pelayanan terbaik 24 jam
    depo wd secepat kilat ^^
    sabung ayam online

    info lbh lanjut :
    WA: +628122222995

    AntwortenLöschen