Monday, May 17, 2010

Windows Structured Exception Handling

In this post I will explain the basics of Windows
Structured Exception Handling.

Many Windows functions like for example WriteFile give us their status via return value. If its execution succeeds the return value is TRUE, otherwise it is FALSE. In the latter case, we can get additional error information by calling GetLastError function. In these cases we get error information synchronously and these errors should be handled immediately after called function returns. However, there are many cases when errors represent asynchronous events, and therefore we don't know exactly when they can occur. In these cases we use Structured Exception Handling.

First of all we will have to take a look at try-except statement which has the following form:

__try

{

    // code possibly causing exception

}

__except (filter-expression)

{

    // exception handling code

}

As you can notice, __try and __except are key words, but they are not part of C or C++ standard. On the other hand, try-catch statement is a part of C++ standard. It is important to note that try-except statement can be used in both C and C++, which is not true for try-catch statement which is C++ concept.

Probably the most interesting part of try-except statement is filter-expression. The value of filter-expression specifies how thrown exception is treated. In most cases filter-expression represents a function that returns one of the values listed below.

  • EXCEPTION_EXECUTE_HANDLER: in case of this value, __except block will be executed immediately after exception is thrown and the rest of instructions in __try block will not be executed.
  • EXCEPTION_CONTINUE_SEARCH: in case of this value, the rest of instructions in __try block will not be executed, but the same applies to the __except block too. Exception will continue its "flight" outside this try-except block and will be handled by the first outer try-except block with filter expression equal to EXCEPTION_EXECUTE_HANDLER.
  • EXCEPTION_CONTINUE_EXECUTION: in case of this value, exception will basically be ignored. The rest of instructions in __try block will be executed, and __except block will not be executed. It is important to understand that some exception types do not allow us to continue execution after they are thrown. If we try to continue execution after some of these exceptions, a new exception of type EXCEPTION_NONCONTINUABLE_EXCEPTION will be thrown.

In most cases, filter function has a DWORD argument that represents exception code. Some of most well-known exception codes are EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_ACCESS_VIOLATION, etc. We can get exception code by calling GetExceptionCode function. This function can't be called from filter function. It must be called immediately after exception is thrown, so we call it inside the filter expression.

Let's take a look at a simple filter function:

DWORD SimpleFilter(DWORD dwExceptionCode)

{

    switch (dwExceptionCode)

    {

        case EXCEPTION_INT_DIVIDE_BY_ZERO:

        case EXCEPTION_NONCONTINUABLE_EXCEPTION:

            _tprintf(_T("* Filter returns EXCEPTION_EXECUTE_HANDLER\n"));

            return EXCEPTION_EXECUTE_HANDLER;

        case EXCEPTION_ACCESS_VIOLATION:

            _tprintf(_T("* Filter returns EXCEPTION_CONTINUE_SEARCH\n"));

            return EXCEPTION_CONTINUE_SEARCH;

        case 0xe1234567:

            _tprintf(_T("* Filter returns EXCEPTION_CONTINUE_EXECUTION\n"));

            return EXCEPTION_CONTINUE_EXECUTION;

    }

    return EXCEPTION_EXECUTE_HANDLER;

}

We can see that function returns different values depending on exception code. Additionally, it prints the value returned.

Now, we will walk through several common "exception handling scenarios".

      
 

EXCEPTION_EXECUTE_HANDLER Scenario

The code bellow throws an EXCEPTION_INT_DIVIDE_BY_ZERO exception. For this exception type, our SimpleFilter function will return EXCEPTION_EXECUTE_HANDLER value. This means that __except block will be executed.

_tprintf(_T("EXECUTE HANDLER scenario demonstration\n"));

__try

{

    _tprintf(_T("* Throwing EXCEPTION_INT_DIVIDE_BY_ZERO\n"));

    int x = 5, y = 0, z = x / y;

    _tprintf(_T("* This will never happen!\n"));

}

__except (SimpleFilter(GetExceptionCode()))

{

    _tprintf(_T("* __except block executed as expected\n"));

}

_tprintf(_T("\n"));

Output:

EXECUTE HANDLER scenario demonstration

* Throwing EXCEPTION_INT_DIVIDE_BY_ZERO

* Filter returns EXCEPTION_EXECUTE_HANDLER

* __except block executed as expected

      
 

EXCEPTION_CONTINUE_SEARCH Scenario

In this example we have two try-except statements, one nested inside the other. Nested __try block causes an EXCEPTION_ACCESS_VIOLATION exception. For this exception our SimpleFilter function returns EXCEPTION_CONTINUE_SEARCH value. Therefor exception will "fly" until it is handled by __except block of outer try-except statement. This is because its filter expression equals to EXCEPTION_EXECUTE_HANDLER.

_tprintf(_T("CONTINUE SEARCH scenario demonstration\n"));

__try

{

    __try

    {

        PDWORD pInvalid = reinterpret_cast<PDWORD>(0);

        _tprintf(_T("* Throwing EXCEPTION_ACCESS_VIOLATION\n"));

        pInvalid[12345] = 54321;

        _tprintf(_T("* This will never happen!\n"));

    }

    __except (SimpleFilter(GetExceptionCode()))

    {

        _tprintf(_T("* This will never happen!\n"));

    }

    _tprintf(_T("* This will never happen!\n"));

}

__except (EXCEPTION_EXECUTE_HANDLER)

{

    _tprintf(_T("* Outer __except block executed as expected\n"));

}

_tprintf(_T("\n"));

Output:

CONTINUE SEARCH scenario demonstration

* Throwing EXCEPTION_ACCESS_VIOLATION

* Filter returns EXCEPTION_CONTINUE_SEARCH

* Outer __except block executed as expected

      
 

EXCEPTION_CONTINUE_EXECUTION Scenario 1

In this example, we throw an exception with user defined exception code. The most significant 4 bits of the formed exception code (0xe = 1110) specify that this is an error (11) and that it is user defined exception (1). The last of these for bits (0) is always clear and is reserved for internal Windows use. The rest of exception code (28 bits) specifies error code (in our case 0x1234567).

To throw the exception we use RaiseException function. The first argument of this function is exception code, second argument specifies if it is possible to continue execution after this exception. In this case we will use 0, which means that execution can be continued after this exception.

Since thrown exception has code 0xe1234567, SimpleFilter function returns EXCEPTION_CONTINUE_EXCEPTION. This means that execution continues immediately after RaiseException call, and __except block is not executed.

_tprintf(_T("CONTINUE EXECUTION scenario demonstration #1\n"));

__try

{

    DWORD dwExceptionCode = (0xe << 28) | 0x1234567;

    _tprintf(_T("* Throwing user defined exception (0x%x)\n"), dwExceptionCode);

    RaiseException(

        dwExceptionCode,

        0,

        0,

        NULL

    );

    _tprintf(_T("* Execution continued, __except block will not be executed\n"));

}

__except (SimpleFilter(GetExceptionCode()))

{

    _tprintf(_T("* This will never happen!\n"));

}

_tprintf(_T("\n"));

Output:

CONTINUE EXECUTION scenario demonstration #1

* Throwing user defined exception (0xe1234567)

* Filter returns EXCEPTION_CONTINUE_EXECUTION

* Execution continued, __except block will not be executed

      
 

EXCEPTION_CONTINUE_EXECUTION Scenario 2

This example is very similar to the previous, with one difference. The second argument of RaiseException function is EXCEPTION_NONCONTINUABLE, which means that execution can be continued after this exception. In this case SimpleFilter function returns EXCEPTION_CONTINUE_EXECUTION, so execution should be continued immediately after RaiseException call, but this is not possible since the exception is noncontinuable. This is why EXCEPTION_NON_CONTINUABLE EXCEPTION is thrown, for which SimpleFilter function returns EXCEPTION_EXECUTE_HANDLER, and therefore __except block will be executed.

_tprintf(_T("CONTINUE EXECUTION scenario demonstration #2\n"));

__try

{

    DWORD dwExceptionCode = (0xe << 28) | 1234567;

    _tprintf(_T("* Throwing user defined exception (0x%x)\n"), dwExceptionCode);

    RaiseException(

        dwExceptionCode,

        EXCEPTION_NONCONTINUABLE,

        0,

        NULL

    );

    _tprintf(_T("* This will never happen!\n"));

}

__except (SimpleFilter(GetExceptionCode()))

{

    _tprintf(_T("* EXCEPTION_NONCONTINUABLE_EXCEPTION handled\n"));

}

_tprintf(_T("\n"));

Output:

CONTINUE EXECUTION scenario demonstration #2

* Throwing user defined exception (0xe1234567)

* Filter returns EXCEPTION_CONTINUE_EXECUTION

* Filter returns EXCEPTION_EXECUTE_HANDLER

* EXCEPTION_NONCONTINUABLE_EXCEPTION handled

      
 

GetExceptionInformation Function

In our last example, we will explain how to specify more detailed information when we throw an exception. Also, we will see how we can get this information inside filter function.

We can get more detailed exception information by calling GetExceptionInformation function. Its return type is LPEXCEPTION_POINTERS. This is a pointer to _EXCEPTION_POINTERS structure. This structure contains two fields. The first is ExceptionRecord (type PEXCEPTION_RECORD) and points to machine-independent exception information. The second field is ContextRecord (type PCONTEXT) which points to process-specific information.

This is how our filter function looks like. It only demonstrates how exception information is acquired and always returns EXCEPTION_EXECUTE_HANDLER value.

DWORD DetailedFilter(LPEXCEPTION_POINTERS lpExceptionPointers)

{

    DWORD dwExceptionCode = lpExceptionPointers->ExceptionRecord->ExceptionCode;

    DWORD dwParameterCount = lpExceptionPointers->ExceptionRecord->NumberParameters;

    _tprintf(_T("* DetailedFilter function\n"));

    _tprintf(_T(" + Exception code 0x%x\n"), dwExceptionCode);

    if (lpExceptionPointers->ExceptionRecord->ExceptionFlags
==
EXCEPTION_NONCONTINUABLE)

    {

        _tprintf(_T(" + Exception is noncontinuable\n"));

    }

    else

    {

        _tprintf(_T(" + Exception is continuable\n"));

    }

    _tprintf(_T(" + Parameters\n"));

    for (DWORD i = 0; i < dwParameterCount; i++)

    {

        _tprintf(_T(" - %u\n"),
lpExceptionPointers->ExceptionRecord->ExceptionInformation[i]);

    }

    return EXCEPTION_EXECUTE_HANDLER;

}

The following code demonstrates how we throw this exception and set exception info including exception parameters.

_tprintf(_T("Exception with parameters demonstration\n"));

__try

{

    DWORD dwExceptionCode = (0xe << 28) | 0x1234567;

    ULONG_PTR params[] = {123, 456, 789};

    _tprintf(_T("* Throwing exception with parameters\n"));

    RaiseException(

        dwExceptionCode,

        0,

        3,

        params

    );

}

__except (DetailedFilter(GetExceptionInformation()))

{

    _tprintf(_T("* __except block executed as expected"));

}

_tprintf(_T("\n"));

Output:

Exception with parameters demonstration

* Throwing exception with parameters

* DetailedFilter function

+ Exception code 0xe1234567

+ Exception is continuable

+ Parameters

- 123

- 456

- 789

* __except block executed as expected

No comments: