The NT Insider

The Exception to the Rule - Structured Exception Handling
(By: The NT Insider, Vol 6, Issue 2, Mar-Apr 1999 | Published: 15-Apr-99| Modified: 16-Aug-02)

 

Windows NT drivers that directly manipulate user mode addresses or that call functions that might raise exceptions must be written to handle the resulting exceptions.  The Windows NT operating system along with Visual C/C++ provides a mechanism known as Structured Exception Handling (SEH) for handling exceptions.

 

Drivers that do not handle exceptions will typically cause the operating system to halt with the ominous sounding bug check code KMODE_EXCEPTION_NOT_HANDLED.  Normally, this is indicative of a driver accessing an invalid user address.  Of course, if the driver were to use SEH the exception would be handled and the system would not generate the bug check.

 

Visual C uses the keywords __try and __except to implement structured exception handling.  When building drivers for Windows NT the build environment provides definitions for the keywords ?try? and ?except? by defining them in terms of the actual Visual C keywords.  These abbreviated keywords can be used in C programs, but cannot be used in C++ programs.  Here at OSR we frequently write C code but store it in files with the ?.cpp? extension to ensure we obtain the better type checking available with C++ code ? and in that case we cannot use the ?try? and ?except? keywords because ?try? is actually a true language keyword in the C++ environment.  Alas, since the C++ exception handling mechanism is not compatible with the operating system?s exception handling mechanism you cannot use the C++ ?try? keyword within a kernel mode driver.

 

Note that the SEH model we are describing here should not be confused with the termination handling model that is also available.  Unfortunately, it is easy to confuse the two because termination handling uses the ?__try? keyword to indicate the beginning of a termination block, and the ?__finally? keyword to indicate the termination block.  Termination handling is useful when trying to ensure that resources are properly released.  This is accomplished by executing the code within the termination block regardless of how it is exited.  But, since it doesn?t relate to exception handling we?ll defer discussing it to a future article.

 

In Figure 1 below we provide example code that uses structured exception handling when probing a user mode buffer to ensure it is valid:

 

 

//

// We'll be reading data from the user mode address.  We should make certain it is

// valid.

//

BOOLEAN OsrProbeForRead(Buffer, Length)

{

    ULONG index;

    UCHAR dummyArg;

    PUCHAR effectiveAddress;

 

    //

    // Probe the input buffer by reading a byte from each page in the range - that's enough.

    //

   

    __try {

       

        for (index = ADDRESS_AND_SIZE_TO_SPAN_PAGES(Buffer, Length);

             index;

             index--) {

 

            effectiveAddress = (PUCHAR) Buffer;

 

            effectiveAddress += ((index-1) * PAGE_SIZE);

 

            dummyArg = *effectiveAddress;

           

        }

       

    } __except (EXCEPTION_EXECUTE_HANDLER) {

 

        DbgPrint("Exception is 0x%x\n", GetExceptionCode());

       

        return FALSE;

       

    }

   

    //

    // If we make it to here, the input buffer is valid.

    //

 

    return TRUE;

}

Figure 1 -- Structure Exception Handling

 

Thus, we consider this as consisting of a protected block (the code between the __try and the __except) and an exception handler (the code following the __except(<expression>).)  In looking at this particular example there are several features of note:

 

-          -          The expression after the __except clause indicates the actions that should be taken when an exception occurs.  The code block following the __except () is executed only if the expression evaluates to a non-zero value.

-          -          There are two functions that can only be used in the scope of the __except expression: GetExceptionInformation, which actually invokes the intrinsic function __exception_info, and GetExceptionCode, which actually invokes the intrinsic function __exception_code.  These two functions provide additional information about the exception that occurred.

-          -          Not all exceptions can be caught.  In such a case the operating system will still bug check with KMODE_EXCEPTION_NOT_HANDLED.

-          -          The exception logic is not invoked if the protected block does not raise an exception.

 

As we demonstrated in this example, the primary use of SEH in drivers is to protect the access to user mode buffers.  This is because the page fault handling logic within the operating system generates an exception (STATUS_ACCESS_VIOLATION) if an invalid address is accessed.  Of course, other exceptions can also be generated.  Indeed, the entire ?name space? for unique types of exceptions is 32 bits wide.

 

Typically, Windows NT uses status code as exception values as well.  For example, your own driver can raise an exception using the documented call ExRaiseStatus().  Note that the name even suggests that you utilize a status code!

 

Exception handling is based upon a stack of individual exception handlers.  Thus, the code generated by the compiler for a __try exception builds a new exception registration block, and then inserts that into the stack.  Then, if an exception occurs the operating system begins by examining the first exception registration block.  This entails evaluating the expression that followed the __except block and acting upon the resulting value.  There are three valid values to which this expression can evaluate:

 

-          -          EXCEPTION_EXECUTE_HANDLER (1) ? this indicates that the exception block should be executed.

-          -          EXCEPTION_CONTINUE_SEARCH (0) ? this indicates that this exception handler does not handle this particular exception. The exception registration block is removed from the stack and the next exception handler is examined.  If the operating system runs out of exception blocks it bug checks.

-          -          EXCEPTION_CONTINUE_EXECUTION (-1) ? this indicates that the exception should be ignored and execution should continue from the point where the exception occurred.

 

In general, we use either EXCEPTION_EXECUTE_HANDLER or EXCEPTION_CONTINUE_SEARCH.  Presumably, if a driver were able to resolve the cause of the exception it might be possible to use EXCEPTION_CONTINUE_EXECUTION, but we have yet to see a case where this is done successfully within a driver.  Perhaps one of our loyal readers would like to prove us wrong by sending in a sample!

 

Thus, looking back at Figure 1 we note that the exception handler is always executed if an exception occurs within that block.  While this might cause us to handle and ignore exceptions that we should not handle, our general thinking has been that since we are returning the error to the caller it is better to return an error than allow the system to bug check.

 

Frequently we also like to use a filter because it allows us to learn more about the exception.  Thus, in Figure 2 we show a routine that we call from the __except expression to store the exception information and then generate a bug check:

 

 

ULONG BackgroundExceptionFilter( ULONG Code, PEXCEPTION_POINTERS pointers)

{

    PEXCEPTION_RECORD ExceptionRecord;

    PCONTEXT Context;

 

    ExceptionRecord = pointers->ExceptionRecord;

    Context = pointers->ContextRecord;

 

    DbgBreakPoint();

 

    return EXCEPTION_EXECUTE_HANDLER;

}

 

Figure 2 -- A Selective Exception Handling Expression

 

In Figure 3 we demonstrate how we actually used that routine within a __try/__except block:

 

               runAgain = FALSE;

              

               __try {

                   

                    runAgain = (*backgroundTask->BackgroundTaskProc)(backgroundTask->Context);

                   

                } __except ( BackgroundExceptionFilter(GetExceptionCode(), GetExceptionInformation()) ) {

                    

                   DbgPrint(("Unexpected exception when calling background routine\n"));

                   

                }

 

Figure 3 -- SEH within _try/_except

 

Indeed, one of the challenges in using structured exception handling is that the very act of processing the exception causes the stack information to be discarded.  Thus, when examining the problem from within the debugger we frequently find that the stack has been unwound and the information about what the system was doing at the time of the exception has been lost.  An exception filter allows us to examine the exception record and retrieve that exception in a graceful fashion.

 

In general, SEH is most useful when calling functions that might in turn raise exceptions.  One example of this is when calling MmProbeAndLockPages.  It typically returns STATUS_ACCESS_VIOLATION (indicating the user address was not valid) but it can also raise STATUS_QUOTA_EXCEEDED (indicating that the memory cannot be locked due to resource constraints.)

 

Finally, in Figure 4 we show the filter routine we use in our File Systems Development Kit.  We protect each call out to the underlying file system with a __try/__except block.  If an exception occurs, this filter routine is called in the exception filter.

 

 

static BOOLEAN FsdSupExceptionFilter(PEXCEPTION_POINTERS ExceptionInformation,

                                     ULONG ExceptionCode)

{

 

    //

    // Print a bold warning message

    //

 

    DbgPrint("********************************************************\n");

    DbgPrint("*** FSDK DEBUGGING: Caught exception in FSD Routine  ***\n");

    DbgPrint("*** An exception has occurred in your FSD; this has  ***\n");

    DbgPrint("*** been caught by the FSDK.  Immediately following  ***\n");

    DbgPrint("*** this, the FSDK will trigger a breakpoint and if a***\n");

    DbgPrint("*** debugger is attached, it will halt.  You can then***\n");

    DbgPrint("*** examine the information in the current stack     ***\n");

    DbgPrint("*** frame to determine why the exception occurred.   ***\n");

    DbgPrint("*** THIS IS NOT AN FSDK BUG - THIS IS A BUG IN YOUR  ***\n");

    DbgPrint("*** FSD.  The FSDK traps this exception to aid in    ***\n");

    DbgPrint("*** debugging ONLY.                                  ***\n");

    DbgPrint("***                                                  ***\n");

    DbgPrint("*** You should execute the following windbg commands: **\n");

    DbgPrint("\n\n    !exr %x ; !cxr %x ;  !kb\n\n",

            ExceptionInformation->ExceptionRecord,

            ExceptionInformation->ContextRecord);

    DbgPrint("***                                                  ***\n");

    DbgPrint("***                                                  ***\n");

    DbgPrint("********************************************************\n");

 

    //

    // Now we trigger a breakpoint here in case there's a debugger

    // attached.  We use a try/except block to ensure that if there

    // is NOT a debugger attached we don't obscure whatever the original

    // fault was...

    //

   

    __try {

       

        DbgBreakPoint();

       

    } __except (EXCEPTION_EXECUTE_HANDLER) {

       

        //

        // Nothing!  Our goal was just to ensure we

        // didn't crash the system because a debugger wasn't enabled.

        //

       

    }

 

    DbgPrint("********************************************************\n");

    DbgPrint("*** FSDK DEBUGGING: Continued past breakpoint.  The  ***\n");

    DbgPrint("*** FSDK will now call KeBugCheckEx and the system   ***\n");

    DbgPrint("*** will halt.                                       ***\n");

    DbgPrint("********************************************************\n");

 

    return EXCEPTION_EXECUTE_HANDLER;

 

}

 

Figure 4 -- Another Exception Filter Example

 

Figure 4 also demonstrates another technique we have found to be useful, namely wrapping ?DbgBreakPoint? in its own __try/__except block.  If a kernel debugger is attached it will catch the debug exception (STATUS_BREAKPOINT - 0x80000003) and our exception handler will not be invoked.   However, if a kernel debugger is not attached it will invoke our exception handler ? which proceeds to ignore the exception and continue processing as if the breakpoint had not occurred.

 

We use this technique with all of our breakpoints because this ensures that even if we accidentally leave a breakpoint in a driver it won?t cause the system to crash.  It will, of course, slow down processing considerably, so we still try to remove breakpoints from frequently used code paths.

 

Using Structured Exception Handling is a requirement for any driver manipulating user buffers.  While not difficult, it is essential for ensuring that the system does not crash if the user buffer turns out to be invalid.  Thus, you should consider using it because it is a normal part of building robust device drivers for Windows NT.

 

This article was printed from OSR Online http://www.osronline.com

Copyright 2017 OSR Open Systems Resources, Inc.