PSEH

From ReactOS Wiki
Revision as of 17:21, 2 April 2005 by KJK::Hyperion (talk | contribs) (How to write Filter functions)
Jump to: navigation, search

PSEH is a thin library to enable the handling of system exceptions in Windows and ReactOS without compiler support for SEH. It's being developed and maintained by KJK::Hyperion. It has been proven reliable and portable. It's single-handedly what keeps ReactOS from crashing even more often, and if you'll ever write kernel code for ReactOS, basic knowledge of PSEH is pretty much a requirement.

Syntax

PSEH has two distinct syntaxes. They're completely equivalent and can be freely mixed in the same function. Virtually no functional difference exists. See the Design Guide section for information beyond mere syntax.

Old syntax

The old syntax is slightly more efficient, but it's harder to translate native SEH (as used in Microsoft Visual C++, Borland C++ and others) into it.

_SEH_TRY_FILTER_FINALLY(FilterFunc, FinallyFunc)
{
 // Try block (code that might throw an exception)
 if(!MayThrow())
  _SEH_LEAVE;

 SomethingElse();
}
_SEH_HANDLE
{
 // Handler (code executed if FilterFunc catches the exception)
 Status = _SEH_GetExceptionCode();
}
_SEH_END;

Several short-hand forms are available:

  • Never catch exceptions, always execute a finally function:
 _SEH_TRY_FINALLY(FinallyFunc)
 {
 }
 _SEH_END_FINALLY;
  • Catch exceptions depending on a filter function, handle caught exceptions:
 _SEH_TRY_FILTER(FilterFunc)
 {
 }
 _SEH_HANDLE
 {
 }
 _SEH_END;
  • Always handle exceptions, always execute a finally function:
 _SEH_TRY_HANDLE_FINALLY(FinallyFunc)
 {
 }
 _SEH_HANDLE
 {
 }
 _SEH_END;
  • Always handle exceptions:
 _SEH_TRY
 {
 }
 _SEH_HANDLE
 {
 }
 _SEH_END;

New syntax

_SEH2_TRY
{
 // Try block
 if(!MayThrow())
  _SEH2_LEAVE;

 SomethingElse();
}
_SEH2_EXCEPT(FilterFunc)
{
 // Handler
 Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
_SEH2_TRY
{
 // Try block
}
_SEH2_FINALLY(FinallyFunc)
_SEH2_END;

Design guide

How to write try blocks

The try block contains the code that you expect to raise exceptions. Exceptions are expected when you deal with unsafe data (for example, buffers passed from user mode), or when raising an exception is how some functions you're calling signal failure (examples are ProbeForRead, MmProbeAndLockPages and ExAllocatePoolWithQuotaTag). Do not just put all code inside a try block: unexpected exceptions are best left unhandled, so the system will halt and the bug causing the exception will be exposed.

A try block should be considered atomic. Don't try to jump into one, or jump out of one. Do not use continue or break directly inside a try block - try blocks are actually loops, so that won't have the effect you expect. The only accepted way to jump out of a try block is the _SEH_LEAVE/_SEH2_LEAVE macro: it will immediately terminate the current try block. The macro should be used just like break, and as such it must not be used inside a loop. Absolutely do not use return inside a try block: this will lead to crashes, or worse undetectable corruption, because some per-thread system state will still refer to local variables of the function you've returned from. Should this become a necessity, a _SEH_RETURN()/_SEH2_RETURN() macro will be provided. Contact the maintainer of PSEH if you need it.

A try block is optionally associated with a filter function and a finally function. The filter function is executed as soon as an exception is raised inside the try block (or inside any function called by the try block), or when another try block nested inside it declines to handle an exception, and its return value controls further processing of the exception. The finally function is called as soon as the try block goes out of scope, unless this happens in a way PSEH cannot control, such as jumping or returning out of the block.

Try blocks can be safely nested. Mixing try blocks in the old syntax with blocks in the new syntax is allowed too.

Try blocks aren't light-weight. They require quite some amount of precious stack (the kernel-mode stack has a size between 16 and 64 KB, versus the 1 MB in user-mode), and entering and leaving them involves a bit of manipulation. Use them sparingly, pretty much only when they're your only line of defense against system crashes. Try blocks nested into one another are significantly less expensive, but each level of nesting still has a cost - try to avoid nesting as well. Try not to use sibling try blocks in the same function: it's supported, but wastes CPU time and possibly stack space. Instead, try folding the two blocks into one, and using a state variable to track in which part of the block you are.

Examples

How to write Filter functions

A Filter function is a function called when an exception is raised in a try block, or when the previous Filter function declines to handle an exception. The value returned from the Filter function specifies how the processing of the exception should continue:

_SEH_CONTINUE_SEARCH 
The handler cannot handle this exception: the next Filter function in the stack is called, and so on until one returns a value other than _SEH_CONTINUE_SEARCH. If all filter functions in the current thread return _SEH_CONTINUE_SEARCH, abnormal termination occurs. In user mode, the current process is terminated abnormally with the exception code as the return code. In kernel mode, the system stops with error 0x7E (SYSTEM_THREAD_EXCEPTION_NOT_HANDLED) if the current thread is a kernel-mode thread, or 0x8E (KERNEL_MODE_EXCEPTION_NOT_HANDLED) if the current thread is an user-mode thread executing in kernel mode. Despite this risk, returning _SEH_CONTINUE_SEARCH is not necessarily bad. If the exception is truly unexpected, it's better to crash the system, so the bug behind it will be exposed and can be tracked down.
_SEH_EXECUTE_HANDLER 
The handler can handle this exception: the processing of the exception stops, the faulting try block is interrupted just before the instruction that raised the exception, all the frames between the faulting try block and the previous try block are unwound (calling any Finally functions found along the way), and execution is resumed in the current try block's handler.
_SEH_CONTINUE_EXECUTION 
The Filter function can fix the error and cause the exception not to be raised again: the processing of the exception stops, and the interrupted try block is resumed just before the faulting instruction. This condition is hard to handle: don't return this code if you aren't sure the exception won't be raised again. Returning _SEH_CONTINUE_EXECUTION unconditionally, or without properly removing the cause of the exception, can hang the current thread in an endless loop.
Some exceptions, such as those raised explicitely with RaiseException, RtlRaiseException or ExRaiseStatus, aren't continuable in any way: explicit raises are considered unconditional jumps by most compilers, and there may well be no instruction at all after the one that raised the exception. You cannot continue such a non-continuable exception by returning _SEH_CONTINUE_EXECUTION. If you do, a STATUS_NONCONTINUABLE_EXCEPTION exception is raised.

Passing NULL as the Filter function has the same effect of a function that always returns _SEH_CONTINUE_SEARCH, but it's faster. Likewise, _SEH_STATIC_FILTER(_SEH_EXECUTE_HANDLER) is equivalent to a function that always returns _SEH_EXECUTE_HANDLER.

The Filter function is the only piece of code that can inspect what happened and where, or even alter the conditions that caused the exception. If you need to examine any data about the current exception other than the exception code, such as what instruction caused it, you have to do it in the Filter function. To this end, you can use two macros:

_SEH_GetExceptionPointers() 
Returns a pointer to an EXCEPTION_POINTERS structure. EXCEPTION_POINTERS is defined as:
typedef struct _EXCEPTION_POINTERS
{
 PEXCEPTION_RECORD ExceptionRecord;
 PCONTEXT ContextRecord;
}
EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
ExceptionRecord points to an EXCEPTION_RECORD structure, containing all the basic information about the exception. EXCEPTION_RECORD is defined as:
typedef struct _EXCEPTION_RECORD
{
 NTSTATUS ExceptionCode;
 ULONG ExceptionFlags;
 struct _EXCEPTION_RECORD * ExceptionRecord;
 PVOID ExceptionAddress;
 ULONG NumberParameters;
 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
}
EXCEPTION_RECORD;
The fields have the following meaning:
ExceptionCode 
The kind of exception that was raised. The most common exception codes are:
  • STATUS_ACCESS_VIOLATION: some code attempted to access inaccessible memory, for example it tried reading from unallocated memory or writing to read-only memory;
  • STATUS_DATATYPE_MISALIGNMENT: a data structure wasn't properly aligned;
  • STATUS_BREAKPOINT: a breakpoint instruction was met;
  • STATUS_INVALID_HANDLE: CloseHandle or ZwClose was called with an invalid handle;
ExceptionFlags 
Can be zero or EXCEPTION_NONCONTINUABLE. If EXCEPTION_NONCONTINUABLE is set, the exception cannot be continued by returning _SEH_CONTINUE_EXECUTION.
ExceptionRecord 
The exception that caused this exception. For example, if you try to continue a non-continuable exception, the STATUS_NONCONTINUABLE_EXCEPTION exception raised afterwards has this field pointing to the exception you attempted to continue.
ExceptionAddress 
Pointer to the instruction that raised this exception.
NumberParameters 
Number of valid elements in ExceptionInformation.
ExceptionInformation 
A maximum of 15 additional parameters for this exception. For example, STATUS_ACCESS_VIOLATION has two parameters. The first is zero if the inaccessible memory was read from, or non-zero if the inaccessible memory was written to. The second contains a pointer to the inaccessible memory.
ContextRecord points to a structure containing a copy of the CPU status at the time the exception was raised. The contents of the structure can be altered, and if the faulting code is continued after its alteration, it will continue with a CPU state altered in the same way. For more information about this structure, consult the Microsoft Platform SDK.
The structure returned by _SEH_GetExceptionPointers, and all the structures it points to, are only valid until the Filter function returns. If you need to access them later, you must make copies.
If you use _SEH_GetExceptionPointers, you need to include the system header that defines EXCEPTION_POINTERS and EXCEPTION_RECORD. The header for user mode is <windows.h>, and the header for kernel mode is the appropriate master header for your driver's model (<ntddk.h> for standard drivers, <wdm.h> for WDM drivers, etc.).
_SEH_GetExceptionCode() 
Returns the exception code as a signed long. This macro is faster than the functional equivalent _SEH_GetExceptionPointers()->ExceptionRecord.ExceptionCode. If all you need is the exception code (sufficient in most cases), use this macro.

How to write handle blocks

How to write finally functions

How to share variables