Structured abnormal treatment (SEH)
Graduation is finally going to get it. I promised to write such an article a few months ago. Now I make up. Structured abnormal processing is a mechanism provided by an operating system to optimize the structure of the program, providing more robust program execution. Environment. Try thinking that you don't have to think about where there is a memory access error, where there is an empty pointer, etc., which has been written down according to the logical structure of the program, not to check if the function is successful, how is this? A happy thing (this is the propaganda word of SEH, does not mean my point of view, here is completely unable to liability).
Structural exception handling --- SEH, is an operating system level concept, the operating system for each thread (Windows platform thread is the basic unit of system scheduling) Maintain an exception handling linked list, when there is an abnormality, control transfer To the operating system, the operating system traverses this linked list in a certain manner, finds the appropriate processing function, performs processing work, and performs the stack of unwind.
When the User Mode thread runs, the operating system allows the FS register to point to the thread (TEB), this TEB is a User Mode accessible data structure, embed a TIB called NT_TIB structure in his beginning (thread information block) ) This TIB is stored in SEH to be used.
Struct _Teb
{
NT_TIB NTTIB;
......
}
Struct NT_TIB
{
EXCEPTION_REGISTRATION_RECORD * EXCEPTIONLIST
.....
}
Struct Exception_Registration_Record
{
EXCEPTION_REGISTRATION_RECORD * NEXT;
ENUM _EXCEPTION_DISPSITION (* Handler) (_exception_record * exceptionRecord, void * establisherframe, _Context * ContextRecord, void * dispatcherContext);
}
When the thread is running, the FS segment points to the TEB structure. This can be seen in the following assembly code.
Let's talk about what the operating system is made when you have an abnormal event.
First of all, we must understand what is abnormal. If you think, you will be an unusual place (-b), when you encounter an abnormal, the operating system will get control (specific situation, I will It cannot be described in detail herein), after a series of actions such as the necessary saving ready, the operating system passes the FS index to the teb, that is, TIB, then access to ExceptionList, call the Handler function pointer to the Handler function pointer points to the function, if The function returned, check the return value of the function, if the return value indicates that he cannot handle this exception, then through the NEXT pointer index to the next Record, repeat, to the end of the list, no one can handle, automatic kill Remove this thread.
Where is the handler? It is installed when the app is executed. Maybe you already know, the handler generally points to a function called _except_handler3, from above, this function is The key to the entire SEH, the following will be described in detail.
In the C language, the syntax of SEH is __TRY ....__ except ....__finally constitutes this (specific syntax, not detailed here), everyone knows that C language is to translate into machine language , Then point by the CPU, which is the __TRY structure will be converted to what kind of machine language? What is the matching language of his peer? Because SEH involves too much underlying, especially memory The layout is very important, so this conversion must be taught here.
The compiler has encountered a __try structure, he knows that the SEH code should be generated, that is, to complete the link of the Exception_Registration_Record,
Push _EXCEPT_HANDLER3; this Record constructor is above the stack
Mov Eax, FS: [0]; original Record
Push EAX
MOV FS: [0], ESP
This code is complete, what is the stack look like? (Low address is on, high address is below)
| The original Record pointer | fs: [0] points here
| Today's HANLDER |
Just constitute a RECORD structure, just connected with the original list. It just meets the requirements of the operating system. After understanding how the compiler arranges RECORD, we will come to see the real handler, relatively, each handler To make different things, if you generate a handled handler for each try, it will be very troublesome, so the vc is implemented, let Handler point to the same function, but in this way, the features of the handler itself are implemented Complex, because he must distinguish which TRY is the current exception that belongs to which function is that it is to establish an appropriate data structure to allow the Handler to get this information to make correct processing.
VC maintains a data structure called Scopetable for each function, and he records the situation of TRY used in functions.
Typedef struct _scopetable
{
DWord Previoustrylevel; // Previous TRY Link Pointer
DWORD LPFNFILTER; / / __EXCEPT code address inside the small brackets behind
DWORD LPFNHANDAL; / / ___EXCEPT The code address inside below the braces below
} Scopetable, * pscopetable;
When the VC generates code, generates a Scopetable array for each function. When establishing SEH Record, put this Table pointer into the stack while putting the current Trylevel, in the stack, so __except_handler3 can access these data and can process an exception.
First explain what is Trylevel, trylevel is an identifier, and he marks the position of the current code execution, and he actually indicates which Try is currently located.
INT i = 0; // Trylevel = -1
__TRY
{
i = 1; // Before performing this code, let Trylevel = 0
__TRY
{
i = 2; // Before performing this code, let Trylevel = 1
}
__EXCEPT (Exception_execute_Handler)
{
i = 3;
}
__TRY
{
i = 4; // Before performing this code, let Trylevel = 2
}
__EXCEPT (Exception_execute_Handler)
{
i = 5;
}
}
__EXCEPT (Exception_execute_Handler)
{
i = 6;
}
Please ignore the I this variable, it is existed for the code in the code.
Trylevel This is used to mark which TRY is located in. This value will be used as a subscript index to scopetable. In the scopetable, the current TRY corresponds to the __except expression, and the address of the Except's processing code. Scopetable is still There is a PrevTrylevel member, which links the Try Block link, and search for the handle to handle the handle, such as the above code, if it is an exception in the TRY of I = 2, first check it to __except, this can Trylevel indexed to Scopetable, if you do not process, you should check the except corresponding to the previous try, which is the one of i = 6, but how to know the Scopetable in this try (because the handset function and filter function address is recorded in Table This is the use of PrevTrylevel, and the prevtylevel = 0 in the Table, which indexeds to the first TRY scopetable, exactly what we are looking for. You will think about it, I = 4 corresponding to Scopetable Prevterylevel is also equal to 0, yes, you are right. As long as you understand the truth of this part, the rest is too much.
Next, look at how the real assembly code is generated. In the beginning of the function code, it is usually like this.
Push EBP
MOV EBP, ESP
Push 0FFFFFFFH; here is Trylevel
Push XXXX; this is the pointer of the Scopetable array.
Push __except_handler3push fs: [0]
MOV FS: [0], ESP
Sub EBP, 20h; here is not necessarily this number, it is related to the local variables used by the function
// If you encounter the TRY sentence,
MOV [EBP-4], 1; maybe 2, maybe it is 3, you should understand what the value is dry here.
It can be seen that in addition to Handler, you have set the Trylevel and Scopetable pointers, because this is to be used in Handler. You may be strange, how to get the pointer to the Trylevel and Scopetable? How to look at the memory layout in Handler?
[EBP-0] = prev EBP
[EBP-4] = Trylevel
[EBP-8] = Scopetable Pointer
[EBP-0C] = Handler
[EBP-10] = Prev Registration Record
Ah ... If we have the RECORD pointer, you can access the Trylevel them in front of the forward, Yes, Record's pointer will be passed to you as a parameter. This is really a way to access the trylevel and other variables.
In the last thing, then enter the Handler function body, you should know that getExceptionInformation () is with the getExceptioncode () function, you may be strange MSDN mentioned that they can only use it in some places, why? Because they achieve The code is very strange
GetExceptionInformation implementation code
MOV EAX, [EBP-14]
RET
You should know that EAX is the return value of the save function, that is, this function is just the value of [EBP-14], and it doesn't set the value of EBP (EBP is a function of Frame Pointer, you should also know, How many EBP-XX is a local variable of a function), that is, it returns a value of a partial variable of the caller. Oh, it is actually the same .vc is almost the time of establishing the code. Such a space, while the Handler is dynamically set this value, pointing to the appropriate address.
OK, enter the Handler's body, first look at it, first don't say, the operating system will help you fill this value, and you can get this information with getExceptioninformation, the second is a void * parameter, actual On the operating system, the current Registeration Record address is passed to you. This is a very critical pointer, and the third is not much more, it is a context with the architecture. The last parameter is actually pointing SCOPETABLE, but this parameter is not used.
The pseudo code of Handler is given below. Before you, let's take a look at Handler.
The main task of Handler is to find the appropriate __except statement, check its return value, if you are exception_execute_handler (of course, there is a contractue ", you will perform the code behind the Except, otherwise go to the previous search, as for how to turn Go to the previous try, it is very clear above.
Handler has to handle a situation, which is to do unwind. The operating system has to call you to the Handler function twice, and tell you that the first parameter is to tell you that the lookup processing or unwind.
// Compare the above layout to think about this structure
Struct _exception_registration
{
Struct _Exception_registration * prev;
Void (* handler) (PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
Struct Scopetable_ENTRY * Scopetable;
Int trylevel;
INT_EBP;
}
After understanding the Handler's task, look at the actual code.
INT __EXCEPT_HANDLER3 (_exception_record * pexceptionRecord, Exception_registration * pregistrationFrame, _Context * PContextRecord, void * pdispatcherContext)
{
Long Filterfuncret
Long Trylevel
Exception_Pointers ExceptPTRS
PSCopetable pscopetable
CLD // Clear The Direction Flag (Make No Assumptions!), This is the default operation of the C language compiler
// if neither the eXception_unwinding nor exception_exit_unwind bit
// is set ... this is true the first time through the the handler (The Handler
// Non-unwinding case
/ / Check is to do unwind
IF (! (! (!))))
{
// Set the value of [EBP-14], remember what is the [EBP-14] mentioned above, the ExceptionRecord here is inside the Handler's stack.
// So it's limited, the Handler function returns, this does not exist, [EBP-14] This pointer points to the unknown area, so MSDN is limited
// Do you understand the call location of the getExceptionXXX function?
// build the Exception_Pointers Structure on the Stack
ExceptPTRS.EXCEPTIONRECORD = PEXCEPTIONRECORD;
ExceptPTRS.CONTEXTRECORD = PCONTEXTRECORD;
// put the point to the exception_pointers 4 bytes Below the
// establisher frame. See asm code for getExceptioninformation
// Think about it, where is the -4 point?
* (PDWORD) (PBYTE) pregistrationframe - 4) = & exceptptrs;
// Get Initial "trylevel" Value, look at the layout and look at the definition of the above structure
Trylevel = pregistrationFrame-> Trylevel
// Get a pointer to the scopetable array
Scopetable = pregistrationframe-> scopetable;
Search_for_handler:
IF (PregistrationFrame-> Trylevel! = Trylevel_none / * - 1 * /)
{
// If it is empty, it means this is a finally statement, and Finally is used to make unwind.
IF (pregistrationframe-> scopetable [trylevel] .lpfnfilter)
{
Push EBP // Save this Frame EBP
// !!! Very Important !!! Switch to Original EBP. THIS IS
// what allows all locals in the frame to have the hand
// Value as before the exception occurred.
// EBP is a function of Frame Pointer, which is very important for execution of a function. Since it is to perform Filter (Except's small parentheses // inside the statement), you must restore the value of EBP, how is EBP recovered What? The above code can be seen as a MOV EBP, ESP, this ESP is
/
EBP = & pregistrationFrame -> _ EBP
// Call the filter function calls the statement inside the Except parentheses to check this return value
Filterfuncret = Scopetable [trylevel] .lpfnfilter ();
POP EBP // RESTORE HANDLER FRAME EBP
IF (FilterFuncret! = Exception_Continue_Search)
{
IF (FilterFuncret <0) // Exception_Continue_execution
Return EXCEPTIONCONTINUEEXECUTION; // Rely on the operating system Complete Continue Execution
// if we get here, exception_execute_handler WAS Specified
Scopetable == pregistrationFrame-> Scopetable
// does the actual OS Cleanup of Registration Frames
// causes this function to recurse
// Perform unwind, the operating system will call them in the previous Handler before the Registration Record, and then disconnect these RECORD lists.
__Global_Unwind2 (PregistrationFrame);
// ONCE We get here, Everything is all cleaned up, except
// for the last frame, where we'll Continue Execution
EBP = & pregistrationFrame -> _ EBP
// Operating system helps us complete the previous unwind, current Record's unwind wants yourself
__local_unwind2 (PregistrationFrame, Trylevel);
/ / This is setjmp / longjmp support code
// nlg == "non-local-goto" (setjmp / longjmp stuff)
__Nlg_notify (1); // EAX == Scopetable-> lpfnhandler
// set the current trylevel to whatver scopetable entry
// Was Being Used When a Handler Was Found
// Modify the current Trylevel for PrevTrylevel, it is clear, from the current Try Block, it naturally has come to a Try Block
PregistrationFrame-> Trylevel = Scopetable-> PreviousTrylevel;
// Call the _except {} block. Never returns.
// goto except statement, here is not returned, because the compiler does not generate a RET code in the Except statement
PregistrationFrame-> scopetable [trylevel] .lpfnhandler ();
}
}
Scopetable = pregistrationframe-> scopetable; trylevel = scopetable-> Previoustrylevel
Goto search_for_handler;
}
Else // Trylevel == Trylevel_none
{
RetValue == Disposition_Continue_Search;
}
}
}
Else // Exception_unwinding or exception_exit_unwind Flags Are Set
{
// Do unwind (trigger by __global_unwind2 function)
Push EBP // Save EBP
EBP = & pregistrationframe -> _ EBP // set ebp for __local_unwind2
__local_unwind2 (pregistrationframe, trylevel_none)
POP EBP // RESTORE EBP
RetValue == Disposition_Continue_Search;
}
}
It doesn't not mention the compiler to generate code for you.
__TRY
{
i = 0;
}
__EXCEPT (Exception_execute_Handler)
{
i = 1;
}
Here, I am placed in the EBP-20, and I omit the settings of FS: [0]
__TRY:
MOV [EBP-4], 0; Trylevel = 0
MOV [EBP-18H], ESP; Save ESP
MOV [EBP-20H], 0; Execute I = 0
JMP __finish; jump out of the TRY sentence
__except_filter:
MOV EAX, EXCEPTION_EXECUTE_HANDLER; return
RET
__except_body:
MOV ESP, [EBP-18H]; first restore the ESP value, that is, reply the running stack
MOV [EBP-20H], 1; Perform I = 1;
__finish:
MOV [EBP-4], 0FFFFFFFH; Trylevel = -1
Almost here, I have to talk about it, and more detailed, I can refer to the article I mentioned in MSJ many times.
http://www.microsoft.com/msj/0197/exception/exception.aspx
If you are more interested in the code generated by VC, you can use the IDA Softice dynamic static tracking.