Let’s take a step back for a moment (for the archives only), as this description is a bit misleading.
First, a high level, logical overview, from the operating system layer (excluding more complicated corner cases like nested unwinds, frame consolidation unwinds, and collided exceptions):
The NT Structured Exception Handling (SEH) model implements a two-part method for handling exceptions. In this model, the first part of exception handling is known as “dispatching”, and involves logically searching for a handler function that wants to accept the given exception. The second part of exception handling is known as “unwinding”, and involves logically tearing down the state of frames in between the frame causing the exception, and the frame that handled (i.e. accepted) the exception.
Each function in a program has the opportunity to declare a single logical handler, typically termed the “language-specific handler”, which is logically called during each of the two parts of exception handling (dispatch, and the unwind), as appropriate.
When an exception occurs, the operating system initiates the first phase of exception handling, dispatching. The exception dispatcher logically searches backwards through the call stack; each frame on the stack is inspected to see if it contains an exception handler; if so, that handler is invoked for exception dispatch. The handler is given the opportunity to handle the exception; if it decline to do so, the exception dispatcher searches the next frame to see if it wants to handle the exception, and so on. (If nobody handles the exception, the process crashes or a bugcheck occurs, depending on whether this is user mode or kernel mode.)
If the handler does want to handle the exception, it has two choices; the first of which is to ask the exception dispatcher to just re-try execution of the code that raised exception (typically after adjusting the state of the program). If this happens, then exception dispatching is logically completed. Otherwise, the exception handler is responsible for invoking the unwinder (via RtlUnwindEx - http://msdn.microsoft.com/en-us/library/windows/apps/ms680615(v=vs.85).aspx - or, RtlUnwind on native x86 only), specifying a target IP and frame pointer to unwind to.
The unwinder searches the stack downwards from the point where the exception occurs, frame by frame, aiming to reach the specified target frame pointer. Along the way, each intervening frame is logically inspected to see if it contains an exception handler; if so, that handler is invoked for unwind. The handler performs whatever necessary cleanup tasks are necessary for that stack frame (such as running C++ object destructors, etc.), and then returns back to the unwinder. In this way, each intervening frame that participates in exception handling is given the chance to clean up appropriately.
Once the unwinder reaches the target frame, it arranges for control to resume at the target IP, and unwinding logically completes; the program picks off running again in the target frame, at whatever code resides at the target IP. Control is transferred directly by the unwinder, which does not return to its caller.
This more or less describes a simple, high level view of how the operating system itself approaches exception handling. There are other nuances and complexities for various corner cases, which you can research online if you are particularly interested in; I’ll not cover them here for brevity’s sake. Now, most programs do not use “raw” exception handling (except for the very rare function written in assembler that participates in exception handling directly). Most programs use a high level language like C or C++ with a compiler that supports SEH; the compiler provides a “language-specific handler” that is called each time the operating system asks a function written in that language to participate in exception handling.
The language-specific handler handles various tasks like supporting the concept of nested __try/__except blocks, or __finally blocks, in C (http://msdn.microsoft.com/en-us/library/s58ftw19.aspx). In C++, it handles details like arranging for C++ objects to be destructed at appropriate times, as well as abstracting the details of SEH away in favor of the standard C++ exception handling model that is externally visible to C++ programs. In effect, the language-specific handler’s purpose is to adhere to the operating system level contract for exception handling, and then provide whatever per-function, per-language behavior is required, such as nested try scopes, etc. Based on the underlying primitives exposed for exception dispatch and unwind, you can likely imagine some logical ways that, for example, the high level __try/__except and __finally constructs might be implemented by a C compiler and its corresponding language-specific handler.
Various other high-level constructs heavily leverage this framework for various means; for example, safe setjmp and longjmp utilize the unwinder infrastructure to tear down state in intervening frames in a safe fashion.
Now, the above is only part of the story; different architectures map the high level exception handling model to their local peculiarities in various ways. The standard, modern approach that we take is that each executable has an exception directory that contains a table of functions and, indirectly, a description of each function’s language-specific exception handler (and other minutia needed to unwind the processor nonvolatile register state through the function). Every architecture except x86 implements this approach (for example, AMD64 and ARM do, and IA64 did, etc.), and it would be the one that I would strongly recommend that you study first when learning about the NT exception handling model. In this model, the operating system looks up a function in its table of functions that support exception handling, and, if it finds a match, it can then learn what the language-specific exception handler for that function is, etc. The exception dispatcher and unwinder look up exception handling and unwinding information for each frame visited in this fashion.
The AMD64 exception handling ABI is fairly well documented on MSDN (http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx), for suggested reading.
On native x86, which is by far the odd-ball out, a somewhat different implementation is used. Ignoring later bolt-ons like SafeSEH for the moment, instead of a static description of exception handlers, the x86 maintains a linked list of frames that participate in exception handling, the list head of which is in turn stored in a per-thread location (NT_TIB.ExceptionList, what Maxim alludes to below with fs:[0]). Frames participating in exception handling are logically pushed on to this list during frame establishment, and popped off during unwind or frame de-establishment. The unwinder implementation is minimalistic compared to other architectures as all registers except the frame pointer are considered volatile across an x86 unwind; at heart, it simply removes entries from the frame chain and invokes exception handlers as appropriate. The same high level SEH model is ultimately implemented, but in a different way.
Again, this is only a high level overview and glosses over various details for more complicated edge conditions, for which there are other references available.
- S (Msft)
(Exception handling guy.)
-----Original Message-----
From: xxxxx@lists.osr.com [mailto:xxxxx@lists.osr.com] On Behalf Of Maxim S. Shatskih
Sent: Monday, August 26, 2013 11:50 PM
To: Windows System Software Devs Interest List
Subject: Re:[ntdev] Exception Handling in Windows driver
determine the handler behaviour (like Linux’s page fault handler
compares the faulting PC against an exception table, to allow functions like copy_from_user to fault).
Oh yes, Windows is similar.
Each function which contrains __try has a hidden _SEH_Rec local var, which is a small structure.
On function entrance, the _SEH_Rec is put to the top of their list, the current list head is kept at fs:[0] thread/processor local storage.
Also, the _SEH_Rec has a pointer to this function’s constant table, which also has the address of the __except operator in the function.
When exception occurs, the _SEH_Rec list is scanned and EIP is compared to each function’s constant table, and the matching one is executed. Then the filter expression (which is inside __except()) is executed, and then RtlUnwind.
–
Maxim S. Shatskih
Microsoft MVP on File System And Storage xxxxx@storagecraft.com http://www.storagecraft.com
NTDEV is sponsored by OSR
Visit the list at: http://www.osronline.com/showlists.cfm?list=ntdev
OSR is HIRING!! See http://www.osr.com/careers
For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars
To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer