Exception Handling and Kernel Debugging
Exception handling is also the basis for kernel debugging. When you enable kernel debugging in an operating system design, Platform Builder includes the kernel debugging stub (KdStub) in the run-time image to enable components that raise exceptions to break into the debugger. Now you can analyze the situation, step through the code, resume processing, or terminate the application process manually. However, you need a KITL connection to a development workstation in order to interact with the target device. Without a KITL connection, the debugger ignores the exception and lets the application continue to run so that the operating system can use another exception handler as if no debugger was active. If the application does not handle the exception, then the operating system gives the kernel debugger a second chance to perform postmortem debugging. In this context, it is often called just in time (JIT) debugging. The debugger must now accept the exception and waits for a KITL connection to become available for the debug output. Windows Embedded CE waits until you establish the KITL connection and start debugging the target device. Developer documentation often uses the terms first-chance exception and second-chance exception because the kernel debugger has two chances to handle an exception in this scenario, but they are, in fact, referring to the same exception event. For more information about debugging and system testing, read Chapter 5, "Debugging and Testing the System."
Hardware and Software Exceptions
Windows Embedded CE uses the same structured exception handling (SEH) approach for all hardware and software exceptions. The central processing unit (CPU) can raise hardware exceptions in response to invalid instruction sequences, such as division by zero or an access violation caused by an attempt to access an invalid memory address. Drivers, system applications, and user applications, on the other hand, can raise software exceptions to invoke the operating system's SEH mechanisms by using the RaiseException function. For example, you can raise an exception if a required device is not accessible (such as a USB camera or a database connection), if the user specified an invalid command-line parameter, or for any other reason that requires you to run special instructions outside the normal code path. You can specify several parameters in the RaiseException function call to specify information that describes the exception. This specification can then be used in the filter expression of an exception handler.
Exception Handler Syntax
Windows Embedded CE supports frame-based structured exception handling. It is possible to enclose a sensitive sequence of code in braces ({}) and mark it with the __try keyword to indicate that any exceptions during the execution of this code should invoke an exception handler that follows in a section marked by using the __except keyword. The C/C++ compiler included in Microsoft Visual Studio supports these keywords and compiles the code blocks with additional instructions that enable the system either to restore the machine state and continue thread execution at the point at which the exception occurred, or to transfer control to an exception handler and continue thread execution in the call stack frame in which the exception handler is located.
The following code fragment illustrates how to use the __try and __except keywords for structured exception handling:
__try {
// Place guarded code here.
} __except (filter-expression) {
// Place exception-handler code here.
}
The __except keyword supports a filter expression, which can be a simple expression or a filter function. The filter expression can evaluate to one of the following values:
■ EXCEPTION_CONTINUE_EXECUTION The system assumes that the exception is resolved and continues thread execution at the point at which the exception occurred. Filter functions typically return this value after handling the exception to continue processing as normal.
■ EXCEPTION_CONTINUE_SEARCH The system continues its search for an appropriate exception handler.
■ EXCEPTION_EXECUTE_HANDLER The system thread execution continues sequentially from the exception handler rather than from the point of the exception.
Exception handling is an extension of the C language, but it is natively supported in C+ + .
Termination Handler Syntax
Windows Embedded CE supports termination handling. As a Microsoft extension to the C and C++ languages, it enables you to guarantee that the system always runs a certain block of code not matter how the flow of control leaves the guarded code block. This code section is called a termination handler, and is used to perform cleanup tasks even if an exception or some other error occurs in the guarded code. For example, you can use a termination handler to close thread handles that are no longer needed.
The following code fragment illustrates how to use the __try and __finally keywords for structured exception handling:
__try {
// Place guarded code here.
} __finally {
// Place termination code here.
}
Termination handling supports the __leave keyword within the guarded section. This keyword ends thread execution at the current position in the guarded section and resumes thread execution at the first statement in the termination handler without unwinding the call stack.
A single __try block cannot have both an exception handler and a termination handler. If you must use both __except and __finally, use an outer try-except statement and an inner try-finally statement.
Dynamic Memory Allocation
Dynamic memory allocation is an allocation technique that relies on structured exception handling to minimize the total number of committed memory pages on the system. This is particularly useful if you must perform large memory allocations. Precommitting an entire allocation can cause the system to run out of committable pages and result in virtual memory allocation failures.
The dynamic memory allocation technique is as follows:
1. Call VirtualAlloc with a base address of NULL to reserve a block of memory. The system reserves this memory block without committing the pages.
2. Try to access a memory page. This raises an exception because you cannot read from or write to a non-committed page. This illegal operation results in a page fault exception. Figure 3-5 and Figure 3-6 show the outcome of an unhandled page fault in an application called PageFault.exe.
3. Implement an exception handler based on a filter function. Commit a page in the filter function from the reserved region. If successful, return EXCEPTION_CONTINUE_EXECUTION to continue thread execution in the __try block at the point where the exception occurred. If the page allocation failed, return EXCEPTION_EXECUTE_HANDLER to invoke the exception handler in the __except block and release the entire region of reserved and committed pages.
Figure 3-5 An unhandled page fault exception from a user's perspective
Figure 3-6 An unhandled page fault exception's debug output over KITL in Visual Studio 2005