A Crash Course On The DePths of Win32 Structured Exception Handling
Creation time: 2005-03-16
Article attribute: translation
Article submission:
Tombkeeper (T0MBKEEPER_AT_HOTMAIL.COM)
A Crash Course On The DePths of Win32 Structured Exception Handling
Matt Pietrek
Dong Yan translation
In all Mechanisms provided by all Win32 operating systems, the most widely public mechanism is probably the Structure Handling, SEHs. One mentioned structured abnormality can be remembered _try, _finally, and _except. There will be a detailed introduction to SEH in any good Win32 book. Even the Win32 SDK has a complete introduction to the use of _Try, _finally and _except for structural exception processing. Since there are so many places to mention SEH, then why should I say that it is not open? Essentially, Win32 structured abnormalities is a service provided by the operating system. The runtime library of the compiler has been encapsulated in this service operating system, and all the documentation that the introduction to SEH is a runtime library for a particular compiler. Keywords_Try, _finally and _except are not mysterious. Microsoft's OS and compilers define these keywords and their behavior. Other C compiler vendors only need to respect their customized semanties. At the same time, the SEH layer of the compiler reduces the hazards brought directly by the SEH of the pure operating system, it also hides the SEH of the pure operating system from everyone.
I have received a lot of email saying that they all need to implement the compiler-level SEH but can't find the public document. Originally, I can refer to the source code of Visual C and Borlang C runtime library. Take a look at them. However, I don't know why, the compiler level SEH is still a secret of a big. Both Microsoft and Borland provide SEH's innermost source code.
In this article, I will explain the structured abnormality from the most basic concept. When explaining, I will separate the operating system from the compiler code generation and runtime library support. When I am in depth, I am based on the Intel version of Windows NT 4.0. however. Most of the content I have said is equally applicable to other processors.
I will avoid referring to the actual C exception handling, C is caratch () instead of _except. In fact, the realization of true C abnormalities and the way I speak is also very similar. However, true C abnormal handling unique complexity will affect the concept I talk about here. For deep digging .h and .inc files and patted the related code of Win32 SEH, the best source is the header file (especially bsexcpt.h) of IBM OS / 2. This is nothing else with people with relevant experience, and the SEH mechanism here is defined when Microsoft has developed OS / 2. Therefore, Win32's SEH is very similar to OS / 2.
SEHIN THE BUFF
If you put the details of SEH together, the task is trusted, so I will start from the beginning of the simple start, a layer of first floor. If the structure has never used structured abnormality, it is not distracted. If you have used, you have to work hard to _Try, getExceptioncode and
Exception_execute_handler scanned from your mind, pretend that this is a brand new concept. Are you ready? GOOD. When the thread is abnormal, the operating system notifies the user to the user to know its occurrence. More particularly, when the thread occurs, the operating system calls the user-defined callback function. What can this callback function want to do what you want to do. For example, it can fix an exception program or play a paragraph. WAV file. Regardless of the callback function, the final movement is returned to a value telling the system what should be done below (this is not strict, but it can be considered like this). Since the user code causes an abnormality, the operating system will call the user's code. What is the callback function? In other words, what information does you need to know about anomaly? In fact, it doesn't matter because Win32 has been defined. The abnormality callback function is as follows:
Exception_Disposition
__cdecl _EXCEPT_HANDLER (
Struct_exception_record * ExceptionRecord,
Void * EstablisherFrame,
Struct_Context * ContexTrecord,
Void * DispatcherContext
);
This function prototype comes from standard Win32 header EXCPT.H, and it seems to have a little dizziness. If you slowly look, it seems that the situation is not so serious. For beginners, you can ignore the type of return value (Exception_DisPosition). What you want is this function called _EXCEPT_HANDLER, you need four parameters.
The first parameter is a pointer to Exception_Record. This struct is defined in Winnt.h, defined as follows:
Typedef struct _exception_record {
DWORD EXCETIONCODE;
DWORD EXCEPTIONFLAGS;
Struct_exception_record * exceptionRecord;
PVOID EXCEPTIONADDRESS;
DWord NumberParameters;
DWORD EXCETIONINFORMATION [EXCEPTION_Maximum_Parameters]
EXCEPTION_RECORD;
Parameters ExceptionCode is the number of the operating system assigned to an exception. Find the beginning of the "Status_" in the Winnt.h file to find a lot of such exception code. For example, the code of STATUS_ACCESS_VIOLATION is 0xC0000005. A more complete exception code can be found from the NTSTATUS.H file in the Windows NT DDK. The fourth element of the Exception_Record structure is the address at an abnormality. The rest of the Exception_Record domain can now be ignored. The second parameter of the _EXCEPT_HANDLER function is a pointer to the Establisher Frame structure. This is an important parameter in SEH, but now you don't have to take it. The third parameter is a pointer to the Context structure. The Context structure is defined in the Winnt.h file, which holds the value of the register of a certain thread. Figure 1 is the domain of the context structure. When used for SEH, the Context structure saves the value of each register when an abnormality occurs. Coincidentally, GetThreadContext and SetThReadContext are also the same Context structure. The fourth is also the last parameter called DispatcherContext, and now you don't deserve it.
Simply summarize, a callback function is called when an exception occurs. This callback function requires four parameters, all of which are structural pointers. In these structures, some domains are important, some are not important. The key issue is that the _EXCEPT_HANDLER callback function has received a lot of information, such as the type of abnormality and the occurrence of the occurrence. The exception callback function needs to use this information to determine the action taken.
I really want to give a sample program now to illustrate _EXCEPT_HANDAL, but still still have something to explain, that is, how do the operating system know when the anomaly occurs? How to call the callback function there? The answer is in another structure called Exception_Registration. This article can see this structure, so don't swallow your jujube. The only place where you can find an Exception_Registration officially defined is the exSup.inc file in the Visual C runtime library source code:
_EXCEPTION_REGISTRAC STRUC
PREV DD?
Handler DD?
_EXCEPTION_REGISTRATION ENDS
It can be seen that this structure is called _exception_registration_record in the NT_TIB structure definition of Winnt.h. However, _EXCEPTION_REGISTRATION_Record is not, so I can use only Struc definitions of assembly languages in ExSup.inc. This is an example for the SEH mentioned earlier.
In any case, we return to the current problem. How does the OS know the call location when an abnormality occurs? The Exception_Registration structure has two domains, the first one without tube. The second domain, Handler, a pointer to the _except_ handler callback function. A bit close to the answer, but there is a question is that where can the OS find this Exception_Registration structure?
To answer this question, it is necessary to remember that the structured abnormality is based on the thread. That is, each thread has its own exception handling callback function. In the June 1996 column, I told a key Win32 data structure, thread information block (TEB or TIB). A domain in this structure is the same for Windows NT, Windows 95, Win32S, and OS / 2. The first DWORD in TIB is a pointer to the Exception_Registration structure of the thread. On the Intel's Win32 platform, the FS register always points to the current TIB, so in FS: [0], you can find a pointer to the Exception_Registration structure. The answer is coming! When an abnormality occurs, the system looks at the TIB of the wrong thread and retrieves a pointer to the Exception_Registration structure, which gives a pointer to the _except_handler callback function. Now there is enough information that there is enough information to call the _except_handler function, see Figure 2.
Together with this little thing, I wrote a small program to demonstrate this very simple OS-level structured exception handling. Figure 3 is MYSEH.CPP, which only has two functions. The main function uses three embedded ASM blocks. The first block uses two PUSH instructions ("Push Handler" and "Push FS: [0]") build an Exception_Registration structure on the stack. PUSH FS: [0] saves the previous value of FS: [0] as part of the structure, but is not important. The important thing is that there is a 8-byte Exception_Registration structure on the stack. Next instruction (MOV FS: [0], ESP) points the first DWORD of the thread information to the new Exception_Registration structure. Build an Exception_Registration structure on the stack rather than using global variables. When using the compiler _Try / _except semantic, the compiler will build an Exception_Registration structure on the stack. I just want to explain the minimum work made by the compiler after using _try / _except. Go back to the main function, the next __asm block clear EAX register (MOV EAX, 0) and then use the value of the register as a memory address, and the next instruction writes to this address (MOV [EAX], 1), This triggers an exception. The last __asm block removes this simple exception handler: first restore the previous FS: [0] content, then pop up the Exception_Registration record (Add ESP, 8) from the stack.
Now suppose that you are running Myseh.exe, see the execution of the program. Mov [EAX], 1 The execution of the instruction triggered an Access Violation. The system looks at the TIB's FS: [0] and finds a pointer to the Exception_Registration structure. There is a pointer to the _except_handler function in the Myseh.cpp file in the structure. The system will put the desired four parameters and call the _except_handler function. One entry _EXCEPT_HANDLER, the code first prints "Yo! I Made It!". Then, _EXCEPT_HANDLER repair causes an exception problem. The problem is that EAX points to the address (address 0) that is not writable. What is done is to modify the value of EAX in Context to point to a writable memory unit. In this simple program, a DWORD type variable (scratch) is used for this purpose. The last action of the _except_handler function is to return the value of the ExceptionContinueExecution type, which defines in the standard EXCPT.H file.
When the operating system sees the returned ExceptionContinueExecution, it is considered that the problem has been resolved and re-executed instructions caused by anomalous. Because my _except_handler function modifies the EAX register to point to the valid memory, MOV EAX, 1 will be executed again, and the main function continues. Not very complicated, isn't it?
Moving in a Little Deeper
With this simplest situation, let's come back to fill a few gaps. Although it is so great, it is not perfect. For any size program, write a function to handle all exceptions that may happen in the program, then this function is probably a mess. More feasible situations can have multiple exception handles, each function is used for a particular part of the program. The operating system provides this feature. Remember that the system finds the exception_registration structure used by the abnormality handling the callback function? The first parameter of this structure is the one I ignored earlier, it is called prev. It does point to the pointer to another Exception_Registration structure. This second Exception_Registration structure can have a completely different processing function. And its prev domain can also point to the third Exception_Registration structure, and push it according to the time. Simply put, it is a linked list of an Exception_Registration structure array. The headers of this linked list are always directed by the first DWORD of the thread information block (FS: [0]) on the Intel machine.
What does the operating system do using this Exception_Registration structure? When an exception occurs, the system traversed this linked list and finds the callback function and an exception to Exception_Registration. For myseh.cpp, the callback function returns the value of the ExceptionContinueExecution type, which is in line with the abnormality. The callback function may not be suitable for the abnormality that happens. At this time, the system moves to the next Exception_Registration structure in the linked list and inquires if the abnormal callback is to handle this exception. Figure 4 shows this procedure. Once the system finds a callback function that handles this exception, stop traversal to the Exception_Registration list.
I gave an example of an exception call that cannot handle an exception, see the Myseh2.cpp of Figure 5. For the sake of simplicity, I used a little compiler level of exception handling. The main function is just a _TRY / _EXCEPT block. The _try block is a call to the HomeGrownFrame function. The function is very similar to the code in the previous Myseh program. It creates an Exception_Registration record on the stack and causes FS: [0] to this record. After the new handler is established, the function actively causes an exception and writes at the NULL pointer:
* (PDWORD) 0 = 0;
The abnormal callback function here is _except_ handler, which is very different from the front. The code first prints the exception code and flag of the ExceptionRecord parameter of the function. The reason for printing this exception flag will be explained later. Because this _except_handler function does not fix the code caused by exception, it returns ExceptionContinueSearch. This allows the operating system to continue looking for the next Exception_Registration in the list. The next exception callback is the _try / _except code for the main function. _EXCEPT block is just printed "caes the exceptionin main ()". The abnormality treatment here is simply ignored. A key issue here is to perform a control flow. When the handler cannot handle an exception, it is rejected to continue the control flow here. Accepting an abnormal handler When all the exception handling code is completed, the control flow is determined where to continue. This is not so obvious.
When using structured exception processing, if a handler does not process an exception, the function can be exited in an abnormal manner. For example, the handler in MySeh2's HomeGrownFrame function is not processed. Since a handler (main function) after the abnormal processing chain processes this exception, the PrintF after the abnormal instruction has never been executed. In a sense, the use of structured abnormal processing and use runtime library functions setjmp and longjmp are similar to LONGJMP. If it is running myseh2, its output may be surprising. It seems to call twice _except_handler functions. The first time I can understand, what is the second time?
Home Grown Handler: Exception Code: C0000005 Exception Flags 0
Home Grown Handler: Exception Code: c0000027 Exception Flags 2
EH_UNWINDING
Caught the Exception in main ()
The two lines starting from "Home Grown Handler", which is obvious, that is, the first exception flag is 0, and the second time is 2. Here, you need to mention the concept of unwinding. Further, when the abnormal callback refuses to handle an exception, it is called again. This callback did not happen immediately, and the other is more complicated. I also need to further clarify the scene when the abnormality occurs.
When an exception occurs, the system traverses the Exception_Registration structure chain table until you find a handler that handles this exception. Once the handler is found, the system is traversed once again until the exception node is processed. In the second traversal, the system is called a second call for all exception handles. The key difference is that in the second call, the exception flag is set to a value of 2. This value corresponds to EH_UNWINDING (the definition of EH_UNWINDING in the Except.inc on the Visual C runtime library source code, but there is no equivalent definition in Win32 SDK).
What does eH_UNWINDING mean? When an exception callback is called (with an EH_UNWINDING flag), the operating system will make a chance to be cleaned at a time. What clean up? A good example is the destructor of the C class. When the exception handler of the function refuses to handle an exception, the control flow generally does not exit from the function in a normal manner. Now consider a function, this function declares a partial C class. The C specification indicates that the patterned function must be called. The second sign is an exception handling callback for EH_UNWINDING is to provide an opportunity to make a destructive function and the cleaning work of _finally block. After the exception is processed and all previous Exception Frames is called to perform unwind, the program continues from the callback function selection. But remember, this does not mean set the instruction pointer to the code address you want and continue. The code that continues to execute requires the stack and frame pointer (Intel CPU ESP and EBP registers) all set corresponding values in the stack frames that process an exception. Therefore, the handler that accepts an exception is responsible for setting the stack pointer and the stack frame pointer to the value in the stack frame containing the SEH code for the abnormality.
Figure 6 unwinding from an exception
More generally, all things under the abnormal unwinding make all things under the stack area of the processing frames are removed, almost equivalent to those functions never called. Another effect of unwinding is that all Exception_Registrations before the Exception_Registration in the list is removed from the linked list. This makes sense because these Exception_Registration is generally built on the stack. After an exception being processed, the stack pointer and stack frame pointer in memory is higher than those that Exception_Registrations removed from the linked list. The Figure 6 is shown in FIG. Help! Nobody Handled IT!
So far I have assumed the operating system to find the handler in the Exception_Registration list. What if there is no corresponding handler? This situation will hardly occur. The reason is that the operating system is privately prepared for each thread to prepare a default exception handler. This default exception handler is always the last node of the linked list and is always selected to handle exceptions. Its behavior is different from the general abnormal callback function, and I will explain it.
Let's take a look at the system where this default, the final exception handler. This obviously wants to perform the previous period of the thread, to execute before any user code. Figure 7 is a pseudo code written by BaseProcessStart, and BaseProcessStart is an internal function of the kernel32.dll of Windows NT. BaseProcessStart requires a parameter, that is, the address of the thread entry. BaseProcessStart runs in the context of the new process and calls the entry point to start the first thread of the process.
Note that in the pseudo code, the call to LPFNEntryPoint is packaged in a pair of _Try and _except. This _TRY block is used to install the default final exception handler in an exception handling list. All registered exception handles are inserted in front of this handler in the list. If the LPFNENTRYPOINT function returns, the thread is running until the completion is completed without causing an exception. If so, BaseProcessStart calls ExitThread to end the thread.
If another situation, the thread has an abnormality, but there is no abnormal handler. In this case, control flow into the braces after the _except keyword. In BaseProcessStart, this code is called UnhandexceptionFilter API, and I will come back later. The key now is that the UnhandledExceptionFilter API contains the default exception handler.
If unhandexceptionFilter returns Exception_execute_Handler, the _excest station's _excePt block is executed. The _EXCEPT block code is to call EXITPROCESS to end the current process. Consider it carefully, this is still meaningful; a common sense is that if the program causing an exception and there is no handler to handle this exception, the system ends the process. This is the case shown in the pseudo code.
Also add to it. If the thread that causes an exception is running as a service and is used for a thread-based service, the _except block does not call EXITPROCESS but call EXITTHREAD. No one will end the entire service process because of a service error.
What did the default exception handlers in UnhandExceptionFilter do? When I propose this issue in the discussion class, there are not a few people who can guess the default behavior of the operating system when unprocessed abnormality. Through the demonstration of the default handler behavior, the answer is clear, people understand. I just run a procedure that actively caused an exception and pointed out the result (see Figure 8). Figure 8 Unhandled Exception Dialog
UnhandledExceptionFilter displays a dialog and tells you an exception. At this point, you can end the process, or you will debug the process of trigger an exception. There is also a lot of operation after this scene, I will talk about these things before the end of this article. As I mentioned, when an exception occurs, the code written by the user can be executed (usually this). Similarly, the code written by the user can be executed during the unwind operation. The user's code may still have problems and cause another exception. Therefore, the exception callback function can also return two other values: ExceptionNESTEDException and ExceptionCollidedunwind. Obviously these content is very deep, I don't want to introduce here. It is too difficult to understand the basic facts.
Compiler-Level SEH
Although I occasionally use _Try and _except, but what I am talking about is implemented by the operating system. However, look at the metamorphosis of the two programs that use the pure operating system SEH, the compiler is essentially necessary. Let's take a look at how Visual C builds its structured abnormality on top of the operating system level SEH.
Remember an important thing before proceeding, another compiler may be completely different from the pure operating system level SEH. No one says that the _try / _except model must be implemented in the _Try / _except model that must be implemented in the Win32 SDK document. For example, Visual Basic 5.0 uses structured abnormal processing in its runtime code, but its data structure and algorithm are completely different from me here. If you read the description of the WIN32 SDK document about structured exception processing, the so-called "frame-based" exception handler is found, the form is as follows:
Try {
// Guarded Body of Code
}
Except (filter-expression) {
// Exception-Handler Block
}
Simply put, all of the code in TRY is protected by an Exception_Registration on a function stack frame. In the entrance of the function, the new Exception_Registration is placed in the header of an exception handling linked list. At the end of the _Try block, its Exception_Registration is removed from the linked header. As mentioned earlier, the head of the abnormal processing chain is stored in FS: [0]. Therefore, if it is handled in the assembly code in the debugger, the following instructions are seen:
Mov DWORD PTR FS: [00000000] ESP
Or
Mov DWORD PTR FS: [00000000], ECX
It can be very convinced that the code is building or removing a _TRY / _EXCEPT block. Now I know a _try block corresponding to an Exception_Registration structure on the stack, what about the callback function in the Exception_ Registration? Using Win32 terms, the abnormal callback function corresponds to the Filter-Expression code. Filter-expression is the code in the keyword _except. It is this Filter-Expression code that determines whether the code in the back {} block is executed. Because Filter-Expression is written by the programmer, the programmer can determine whether the exception occurring in the code is handled in it. The Filter-Expression code can be simply only one "exception_execute_handler", or call a function to return P to 20 million, then return a code to tell the system Next, this is the programmer's choice. The key is that the documentation of Filter-Expression is corresponding to the exception callback function mentioned earlier.
I have just been very simple, but it is just a beautiful situation in ideals. Brutal reality is more complicated. For beginners, Filter-Expression is not directly called by the operating system. The actual situation is that the exception handler domains for each Exception_Registration point to the same function. This function is called __except_handler3 in the runtime library of Visual C . Yes
Another point is not to establish or remove the Exception_Registration every time you enter or exit_try blocks. For each function using SEH, only an Exception_Registration. In other words, you can use multiple _TRY / _EXCEPT combinations in a function, but only build an Exception_Registration on the stack. Similarly, you can nest another _TRY block in a function of _try block, Visual C still creates an Exception_Registration. If an exception handler for the entire EXE or DLL is enough and if you can handle multiple _TRY blocks with an Exception_Registration, it is clear that more mechanisms you have seen. This is done by data in a generally could not see. However, since this paper is to dissect a structured abnormality, let's take a look at these data structures.
The Extended Exception Handling Frame
The SEH implementation of Visual C does not use a pure Exception_Registration structure, but an additional data field is added at the end of the structure. The key to this additional data is that it allows a function (__except_handler3) to process all exceptions and turn the control stream to the corresponding filter-expnesss and code in the _except block. A little information about this Visual C extended Exception_Registration can be found from the exSup.inc file from the runtime library source code of Visual C . In this file, you can find the definition:
Struct_exception_registration {
; Struct _exception_registration * prev;
Void (* handler) (PEXCEPTION_RECORD,
; PEXCEPTION_REGISTRATION,
PCONTEXT,
; PEXCEPTION_RECORD ;; struct scopetable_entry * scopetable;
INT Trylevel;
INT_EBP;
; PEXCEPTION_POINTERS XPOINTERS
};
The first two fields have been seen, Prev and Handler. They form the most basic Exception_Registration structure. The newly added is the last three fields: Scopetable, Trylevel and _ebp. The Scopetable domain points to an array of Scopetable_entries type structures, and Trylevel is the index of this array. The last domain, _ebp is the value of the stack frame pointer (EBP) before creating Exception_REGISTration.
The _ebp domain has become a portion of the Exception_Registration structure of the exception_registration structure is not accidental. The structure contains it because most functions start with a PUSH EBP. This makes all other Exception_registration domains can be accessed through the negative offset of the frame pointer. For example, trylevel is in [EBP-04], the Scopetable pointer is in [EBP-08], etc.
After the extended Exception_Registration structure, Visual C is pressed into two additional values. The first DWORD is a pointer to the Exception_Pointers structure (a standard Win32 structure). This pointer is the pointer returned by the getExceptionInformation API. Although the SDK document implies that getExceptionInformation is a standard Win32 API, but in fact getExceptionInformation is a compiler-related function. When this function is called, Visual C generates the following code:
MOV EAX, DWORD PTR [EBP-14]
GetExceptionCode is also dependent on the compiler as getExceptionInformation. The value returned by getExceptionCode is the value of a domain in the data structure returned by getExceptioninformation. Visual C generates the following code that leaves the reader as an exercise.
MOV EAX, DWORD PTR [EBP-14]
MOV Eax, DWORD PTR [EAX]
MOV Eax, DWORD PTR [EAX]
Back to the extended Exception_registration structure, at the 8 bytes before the structure of the structural body, Visual C retains a DWORD to save the final stack pointer (ESP) after all ProLogue code execution. This DWORD is the value of a normal ESP register when the function is executed (except for the case where the parameter stack is to prepare the additional function).
It seems that I seem to have a lot of things, it is true. Before you continue, let's pause for a while, review the standard exception frame generated by Visual C to generate a function of structured exception handle:
EBP-00 _EBP
EBP-04 Trylevel
EBP-08 SCOPETABLE POINTER
EBP-0C Handler Function Address
EBP-10 Previous Exception_REGISTRATION
EBP-14 getExceptionPointers
EBP-18 STANDARD ESP IN FRAME is viewed from the viewpoint of the operating system, which constitutes only two of the pure Exception_Registration domains: [EBP-10], the processing function pointer at [EBP-0CH]. Other things in the frame are relying on Visual C . Remember these, let's look at the runtime library function that contains the Visual C of the compiler SEH, __ except_handler3.
__except_handler3 and the scopetable
Although I highly want to point out the Visual C runtime library source code and let the reader go to study the __except_handler3 function, but I can't, because the code of this function is not available. Here I have to use the pseudo code of __except_handler3 in my rush to cope (see Figure 9).
Although __except_handler3 looks a pile of code, but remember it is just an exception callback function, just like I introduced the article. Like the HomeGrown exception callback function in Myseh.exe and Myseh2.exe, this function also requires four parameters. At the highest level, __ except_handler3 is divided into two parts by an IF statement. This is because the function can be called twice, once it is normal, once in the unwind process. Most of the code is used in the non-unwinding callback.
The beginning of this code first creates an Exception_Pointers structure on the stack, and initializes it with the two parameters of __except_handler3. The address of this structure, that is, ExceptPTRS in the pseudo code, is placed in [EBP-14]. This initializes the pointer used by getExceptioninformation and getExceptionCode function. Then, __ except_handler3 gets current Trylevel from the Exception_Registration frame ([EBP-04]). The TryleVel variable is used as an index of the Scopetable array, making an Exception_Registration can be used in a plurality of _Try blocks and nested _TRY blocks in a function. The members of each Scopetable are as defined below:
Typedef struct _scopetable
{
DWord Previoustrylevel;
DWORD LPFNFILTER
DWord LPFNHandler
} Scopetable, * pscopetable;
The second and third parameters in Scopetable are easy to understand. They are the address of Filter-Expression and the corresponding _except block code. The PrevioustryLevel field is a bit complicated. Briefly, it is used for nested TRY blocks. Important is that there is a Scopetable member for each _try block in the function.
As mentioned earlier, the current Trylevel specifies the SCOPETable array member to use, and also specifies the address of the Filter-Expression and the _except block. Now, consider a situation, that is, a _try block is nestled in another _try. If the filter-expression of the inner layer _Try block does not process an exception, the filter-expression of the outer _TRY block must be processed. __except_handler3 How do I know which SCOPETABLE member corresponds to the outer _TRY? Its index is given by the Previoustrylevel domain of Scopetable members. With this mechanism, you can create any nesting_try block. The PrevioustryleVel domain is a node for possible exception handling linies in a function. The end of the linked list is indicated by a 0xFffffFFFFFFF. Back to __except_handler3, after getting the current trylevel, the code points to the corresponding Scopetable member and calls the filter-expression code. If filter-expression returns Exception_Continue_Search, __ except_handler3 continues to find the next Scopetable member, this member is specified by the Previoustrylevel domain. If the handler is not found during the traversal, __ except_handler3 returns Disposition_Continue_Search, which makes the system to move the next Exception_Registration frame.
If filter-expression returns Exception_execute_handler, it means that an exception should be processed by the corresponding _except block code. This means that all previous Exception_registration frames are removed from the linked list and to execute the _except block. The first job is done by calling __global_unwind2, then I will introduce. After some cleaning code, the execution of the code will leave __except_handler3 and enter the _except block. The strange thing is that the control flow has never returned from the _EXCEPT block, although __except_handler3 calls it. How to set the current Trylevel? This is dually processed by the compiler, and the compiler completes the modification of the Trylevel domain in the extended Exception_Registration structure in the ON-the-fly manner. If you look at the assembly code of the function using SEH, you will find that the different locations of the function code have the current Trylevel code of [EBP-04]. __except_handler3 How to call _except, and why does the control flow never return? Because a CALL instruction will return the address in the stack, you can think that this is chaos the stack. If you look at the code generated by the _EXCEPT block, it will find that the first thing it works is to load DWORD at the 8-byte of the Exception_Registration structure into the ESP register. As part of its proLogue code, the function saves the value of the ESP, so that _except can also retrieve it.
The showsehframes program
This is not for Exception_Registrations, Scopetables, Trylevets, Filter-Expressions, and unwinding these things that I can't stand, I was also like this. The topic of compiler-level structured exception handling has no help to more learning. If there is no understanding of understanding, many of them have no meaning. When faced a lot of theory, I naturally tend to write some code that uses these theories. If the program works, I know my understanding (usually) is correct. Figure 10 is the source code of Showsehframes.exe. It uses the _try / _except block to establish a linked list consisting of several Visual C SEH frames. After that, the information of each frame is displayed, as well as Scopetables established for each frame. The program does not generate any exceptions. I contain all the _TRY blocks to force Visual C to generate multiple Exception_ Registration frames, each frame with multiple Scopetable members.
The important functions in Showsehframes are Walksehframes and Showsehframe. Walksehframes first printed the address of __except_handler3 for a while. Next, the function is obtained from fs: [0] to a pointer to the abnormal linked table head and then traverses each of the nodes in the list. Each node is a VC_EXCEPTION_REGISTRATION type, I define this structure to describe the exception handling frame of Visual C . For each node in the linked list, WalkseHframes passes the pointer to the node to the showsehframe function.
Showsehframe first prints the address of the abnormal frame, the callback function address, the address of the previous exception frame, and a pointer to the Scopetable. Next, for each Scopetable member, the code prints the address of the Previous Trylevel, Filter-Expression, and the address of the _except block. How do I know how many members in Scopetable? In fact, I don't know. I assume that the current Trylevel in VC_EXCEPTION_REGISTRATION is less than the total number of members of Scopetable.
Figure 11 shows the results of Showsehframes. First, look at each line starting with "frame:". Note that each subsequent instance is how to display an exception frame on the high address on the stack. Next, in the first three frame: line, pay attention to the value of the handler is the same (004012A8). Look at the beginning of the output, this 004012A8 is the address of the __except_handler3 function of the Visual C runtime library. This confirms that a member I mentioned earlier handles multiple exceptions.
Maybe someone will be confused, because Showsehframes only uses two functions that use SEH, but there are three use __except_handler3 as an exception frame for the callback function. The third anomaly frame comes from the runtime library of Visual C . The CRT0.c source code for the runture C runtime library shows that the call to Main or WinMain is encapsulated in the _Try / _Except block. This _try block's Filter-Expression code is in the Winxfltr.c file.
Back to showsehframes, the last frame of Handler: This line contains a different address, 77F3AB6C. After searching, this address will find that this address is in kernel32.dll. This special frame is installed by the BaseProcessStart function of kernel32.dll, which I have said in the previous item. Unwinding
Before wearing unwinding's implementation code, let's briefly summarize the meaning of unwinding. In front, I mentioned how the exception handler information is saved in the list, and how it is directed by the first DWORD of the thread information block (fs: [0]). Because an exception handler is not necessarily a header node of a linked list, it requires an ordered method to remove all exception handler in the list before this actual handler.
As seen in the __except_handler3 function in Visual C , unwinding is done by the __global_unwind2 rtl function. This function is a very simple package for unapplicted RTLunwind API:
__global_unwind2 (void * pregistframe)
{
_Rtlunwind (pregistframe,
& __ Ret_Label,
0, 0);
__ret_label:
}
Although RTLunwind is a key API that implements compiler SEH, it is not open. It is a kernel32 function, and Windows NT calls kernel32.dll call Forward to NTDLL.DLL, in NTDLL.DLL is also an RTLunwind function. I am patching some of the pseudo code of this function, namely Figure 12.
Although RTLunwind looks very cumbersome, it is not difficult to understand if it is reasonably divided. This AP is first obtained from FS: [4] and FS: [8] to get the top and the bottom of the thread stack. These two values are important for the latter robustness, where the robustness here is to ensure that all unusual frames that are unwound are within the scope of the stack.
Then, RTLunwind creates an Exception_Record on the stack and set the ExceptionCode domain to status_unwind. And the Exception_Unwinding flag in the ExceptionFlags domain of Exception_Record is also set. The pointer to this Exception_Record structure is passed to each exception callback function as a parameter. Thereafter, the code calls the _rtlpCaptureContext function to create a CONTEXT structure, which will also serve as a parameter of the unwarning unwind call. The part of RTLUNWind is traversed by the linked list of the Exception_Registration structure. For each frame, the code calls the RTLPEXECUTEHANDLERFORUNWIND function, and this function will be said. It is this function to call an exception callback function with an Exception_Unwinding flag. After each callback, the corresponding exception frame removes it by calling RTLPunLinkHandler. When RTLUNWIND arrives at the frame of the first parameter specifies the address, stop unwinding frames. There are also many code for error check, which guarantees the normal execution of the program. If there is a problem, RTLUNWind will cause an exception to tell the problems encountered, and this exception's Exception_nonContinuable flag is set. When this flag is set, it is not allowed to continue, so the process must end.
Unhandled Exceptions
In front of this article, I fully describe the UnhandlexceptionFilter API. Never call this API directly (even though). In most cases, it is called by the Filter-Expression code from the default unusual callback of kernel32. The front baseprocessStart pseudo code illustrates this. Figure 13 is the pseudo code I have given the UnhandlexceptionFilter. The beginning of this API is somewhat strange (at least in my opinion). If an Exception_Access_ Violation is exception, the code is called _basepcheckforreadonlyResource. Although I didn't provide pseudo code of this function, I can talk about it here. If it is written by writing to the EXE or DLL's Resource Section (.RSRC), _basepcurrenttoplevelfilter modifies the properties of the abnormal page, allowing the write operation, unhandledExceptionFilter returns Exception_ Continue_execution, and re-executes instructions that cause an exception.
The next task of UnhandledExceptionFilter is to determine if the process is to run under the Win32 debugger. That is, the process is created in debug_process or debug_only_this_process flag. UnhandledExceptionFilter uses the NTQueryInformationProcess function to determine if the process is being debugged. If so, this API returns Exception_Continue_Search, indicating that other parts of the system will wake up the debugger process and inform the debugger to be abnormal. If you have a User-Installed Unhandled Exception Filter, call it. There is generally no callbacks for user-installed, but you can install one with the SetunhandExceptionFilter API. I provided the pseudo code of this API. This API just modifies a global variable with the address called by the new user, and then returns the value of the old callback.
After preparing, UnhandexceptionFilter can do its main job: to notify the program's error with the old face application error dialog. There are two ways to avoid the emergence of this dialog. The first is that the process calls SETERRORMODE and sets the SEM_NOGPFAULTERRORBOX flag. The other is to set the AUTO value of the AEDEBUG registration key to 1. At this time, UNHANDEXCEPTIONFILTER slightly over the program error dialog and automatically initiates the debugger specified by the Debugger value of the AEDebug key. If "Just In Time Debugging" is more familiar, this is the support of the operating system, and will also discuss it.
In most cases, these two conditions that escape this dialog box are fake, and UnhandexceptionFilter calls the NTRAISEHARDERROR function in the NTDLL.DLL function. It is this function to call the program error dialog. This dialog waits for the user to click the OK end process or the CANCEL debugging process.
If you click OK, UnhandexceptionFilter returns Exception_execute_handler. Calling UnhandledExceptionFilter's code is usually responded to end yourself (just like the baseprocessStart code). This brings an interesting question. Most people think that the system ends the process without processing exceptions. In fact, more accurate statement is that the system has worked some work, so that the unprocessed exception has enabled the process to end himself. If Cancel, click the Cancel of the Programs Error dialog, the UnhandleDexceptionFilter is actually interesting, and the debugger loads the process of abnormal. After the debugger ATTACH to the error process, the code first calls createEvent to create an event to notify the debugger. Both event handles and current process IDs are passed to Sprintf, sprintf format the command line of the startup debugger. After all the best, UnhandExceptionFilter calls createProcess to start the debugger. If CREATEPROCESS is successful, the code calls NTWAITFORSINGLEOBJECT for the event created earlier. This call has been blocked until the debugger process notifies this event, indicating that the debugger has successfully attached to an error process. UnhandledExceptionFilter also has other sporadic code, but I only have important saying this here.
INTO THE INFERNO
At present, this is too unfair if it remains. I have talked about how the operating system calls user-defined functions; how generally, the internal operation of the general callback and how the compiler uses them to implement _Try and _catch; talk about the situation when no one is handled, and the system Treatment. The rest of the abnormal callback is started. Yes, let's go deep into the insider to see the beginning of structured abnormal processing.
Figure 14 shows me for the pseudo code written for KiuseRexceptionDispatcher and some related functions. KIUSEREXCEPTIONDISPATCHER is located in NTDLL.DLL, which is the starting point after an exception. This is not 100% accurate. For example, in an Intel system, an exception will cause control to a processor of Ring 0 (kernel mode). This processing program is defined by the interrupt descriptor table entry corresponding to this exception. I will skip all kernel mode code and assume that the CPU executes KiuseRexceptionDispatcher when an exception occurs.
The key to KiuseRexceptionDispatcher is the call to RTLDISPATCHEXCEPTION. This call launches the lookup of the registered exception handler. If the handler processes an exception and continues, the call to RTLDISPATCHEXCEPTION is no longer returned. There are two possibilities if RTLDISPATCHEXCEPTION is returned, there is two possibilities: either call NTCONTINUE to continue the process, or it produces another exception. If the latter, exception can no longer continue, the process must end. Then RTLDispatChexceptioncode, this is the code that traverses an abnormal frame. The function gets a pointer to the Exception_Registrations linked list and traverses each node to find the handler. Because the stack may crash, this function is very cautious. Before calling the handler specified by each Exception_Registration, the code must ensure that Exception_Registration is DWORD alignment in the thread stack and the address of the Exception_Registration in front.
RTLDISPATCHEXCEPTION does not directly call the address specified in the Exception_Registration structure, but call RTLPEXECUTEHANDLERFOREXCEPTION to do this dirty. RTLDISPATCHEXCEPTION either continues to travers an exception frame according to the situation within RTLPEXECUTEHANDLERFOREXCEPTIONTIONPTION. This secondary abnormality indicates that there is a problem in an abnormal callback function that cannot be executed. RTLPEXECUTEHANDLERFOREXCEPTION code is closely related to another function RTLPEXECUTEHANDLERFORUNWIND. I mentioned this function when I said in the previous unwinding. Both functions load the EDX register with different values before sending control to the ExecuteHandler function. The modulation state is that RTLPEXECUTEHANDLERFOREXCEPTION and RTLPEXECUTEHANDLERFORUNWIND are different front ends of the same ExecuteHandler function. ExecuteHandler is where the Handler domain of Exception_Registration is taken out and executed. Perhaps it seems to be somewhat strange, the call to the abnormal callback function is also encapsulated by a structured exception handler. Although it is a bit strange here, it is reasonable to consider it. If the abnormal callback causes another exception, the operating system needs to know this event. According to the anomaly, the initial callback or the callback in unwind, ExecuteHandler returns Disposition_NESTED_EXCEPTION or Disposition_Collided_unwind. These two codes can be "red alert! Turn off now!" Level. The reader may be as difficult as I am just the same as SEH. Similarly, it is difficult to remember who is called. In order to help me, I painted a picture of Figure 15.
Now, setting the EDX register to do what to do before executing ExecuteHandler? it's actually really easy. If an error is incorporated when the user's handler is called, no matter what executeHandler is purely exception, regardless of the EDX. It puts the EDX register stack as the Handler domain of the minimum Exception_Registration structure. In essence, ExecuteHandler uses pure abnormalities and I use it in Myseh and Myseh2 programs.
Conclusion
Structured abnormal processing is a wonderful feature of Win32. Thanks to the support layer like Visual C , the general programmer can benefit from SEH with less learning costs. However, in the operating system, things can be more complicated than Win32 documentation. Unfortunately, because almost all people think that the system-level SEH is a difficult problem, there is no article in this regard. The lack of the document in system-level details has not been improved. In this article, I have already demonstrated the system-level SEH is expanding around a relatively simple callback function. If it is understood that the nature of the callback function is understood, the upper layer has built other understanding levels, and the structure of the system level is not so difficult to master.