Structured Exception Handling

Native exceptions in Windows are Structured Exceptions Handling (SEH) and Visual C++ has a language extension to allow you to catch these exceptions. It is important to understand that they are not the same as C++ exceptions, which are considered by the compiler to be synchronous, that is, the compiler knows if a method may (or specifically, will not) throw a C++ exception, and it uses this information when analysing code. C++ exceptions are also caught by type. SEH is not a C++ concept, so the compiler treats structured exceptions as being asynchronous, meaning it treats any code within an SEH protected block as potentially raising a structured exception, and hence the compiler cannot perform optimizations. SEH exceptions are also caught by exception code.

The language extensions for SEH are extensions to Microsoft C/C++, that is, they can be used in C as well as C++ so the handling infrastructure does not know about object destructors. Additionally, when you catch an SEH exception, no assumptions are made about the state of the stack or any other part of your process.

Although most Windows functions will catch the SEH exceptions generated by the kernel in an appropriate way, some purposely allow them to propagate (for example, the Remote Procedure Calls (RPC) functions, or those used for memory management). With some Windows functions you can explicitly request that errors are handled with SEH exceptions. For example, the HeapCreate set of functions will allow a Windows application to create a private heap, and you can pass the HEAP_GENERATE_EXCEPTIONS flag to indicate that errors in creating the heap, and allocating, or reallocating memory in a private heap, will generate an SEH exception. This is because the developer calling these functions may regard the failure to be so serious that it is not recoverable, and hence the process should terminate. Since an SEH is such a serious situation, you should review carefully whether it is appropriate (which is not entirely impossible) to do much more than report details of the exception and terminate the process.

SEH exceptions are essentially low-level operating system exceptions, but it is important to be familiar with the syntax because it looks similar to C++ exceptions. For example:

    char* pPageBuffer; 
unsigned long curPages = 0;
const unsigned long PAGESIZE = 4096;
const unsigned long PAGECOUNT = 10;

int main()
{
void* pReserved = VirtualAlloc(
nullptr, PAGECOUNT * PAGESIZE, MEM_RESERVE, PAGE_NOACCESS);
if (nullptr == pReserved)
{
cout << "allocation failed" << endl;
return 1;
}

char *pBuffer = static_cast<char*>(pReserved);
pPageBuffer = pBuffer;

for (int i = 0; i < PAGECOUNT * PAGESIZE; ++i)
{
__try {
pBuffer[i] = 'X';

}

__except (exception_filter(GetExceptionCode()))
{
cout << "Exiting process.n";
ExitProcess(GetLastError());

}

}
VirtualFree(pReserved, 0, MEM_RELEASE);
return 0;
}

The SEH exception code is highlighted here. This code uses the Windows VirtualAlloc function to reserve a number of pages of memory. Reserving does not allocate the memory, that action has to be carried out in a separate operation called committing the memory. Windows will reserve (and commit) memory in blocks called pages and on most systems a page is 4096 bytes, as assumed here. The call to the VirtualAlloc function indicates that it should reserve ten pages of 4096 bytes, which will be committed (and used) later.

The first parameter to VirtualAlloc indicates the location of the memory, but since we are reserving memory, this is unimportant so nullptr is passed. If the reserving succeeds, then a pointer is returned to the memory. The for loop simply writes data to the memory one byte at a time. The highlighted code protects this memory access with structured exception handling. The protected block starts with the __try keyword. When an SEH is raised, execution passes to the __except block. This is very different to the catch block in C++ exceptions. Firstly, __except exception handler receives one of three values to indicate how it should behave. Only if this is EXCEPTION_EXECUTE_HANDLER will the code in the handler block be run (in this code, to shut down the process abruptly). If the value is EXCEPTION_CONTINUE_SEARCH then the exception is not recognized and the search will continue up the stack, but without C++ stack unwinding. The surprising value is EXCEPTION_CONTINUE_EXECUTION, because this dismisses the exception and execution in the __try block will continue. You cannot do this with C++ exceptions. Typically, SEH code will use an exception filter function to determine what action is required of the __except handler. In this code, this filter is called exception_filter, which is passed the exception code obtained by calling the Windows function GetExceptionCode. This syntax is important because this function can only be called in the __except context.

The first time the loop runs no memory will have been committed and so the code that writes to the memory will raise an exception: a page fault. Execution will pass to the exception handler and through to exception_filter:

    int exception_filter(unsigned int code) 
{
if (code != EXCEPTION_ACCESS_VIOLATION)
{
cout << "Exception code = " << code << endl;
return EXCEPTION_EXECUTE_HANDLER;
}

if (curPage >= PAGECOUNT)
{
cout << "Exception: out of pages.n";
return EXCEPTION_EXECUTE_HANDLER;
}

if (VirtualAlloc(static_cast<void*>(pPageBuffer), PAGESIZE,
MEM_COMMIT, PAGE_READWRITE) == nullptr)
{
cout << "VirtualAlloc failed.n";
return EXCEPTION_EXECUTE_HANDLER;
}

curPage++;
pPageBuffer += PAGESIZE;
return EXCEPTION_CONTINUE_EXECUTION;
}

It is important in SEH code to only handle exceptions that you know about, and only consume the exception if you know that the condition has been completely addressed. If you access Windows memory that has not been committed, the operating system generates an exception called a page fault. In this code, the exception code is tested to see if it is a page fault, and if not, the filter returns telling the exception handler to run the code in the exception handler block that terminates the process. If the exception is a page fault then we can commit the next page. First, there is a test to see if the page number is within the range that we will use (if not, then close down the process). Then, the next page is committed with another call to VirtualAlloc to identify the page to commit and the number of bytes in that page. If the function succeeds, it will return a pointer to the committed page or a null value. Only if committing the page has succeeded will the filter return a value of EXCEPTION_CONTINUE_EXECUTION, indicating that the exception has been handled and execution can continue at the point the exception was raised. This code is a standard way to use VirtualAlloc because it means that memory pages are only committed when, and if, they are needed.

SEH also has the concept of termination handlers. When execution leaves the __try block of code through a call to return, or by completing all of the code in the block, or by calling the Microsoft extension __leave instruction, or has raised an SEH, then the termination handler block of code marked with __finally is called. Since the termination handler is always called, regardless of how the __try block is exited, it is possible to use this as a way to release resources. However, because SEH does not do C++ stack unwinding (nor call destructors), this means that you cannot use this code in a function that has C++ objects. In fact, the compiler will refuse to compile a function that has SEH and created C++ objects, either on the function stack or allocated on the heap. (You can, however, use global objects or objects allocated in calling functions and passed in as parameters.) The __try/__finally construct looks useful, but is constrained by the requirement that you cannot use it with code that creates C++ objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset