Although I am not enough to let you write an exception handling mechanism at your own operating system level, but let you know how SEH is working. In the sample program showseh program in this chapter, the __TRY block will be used to set up an exception handling linked list. After setting up, the program will traverse the SEH linked list and print out the content of each node.
The output of ShowSeh is shown in the figure below. I want you to pay attention to it. First, please note that the "Next REC" column is always incremented, which is the compiler to place the stack area of ExceptionRegistrationRecord. The front four data reflects the __TRY paragraph in showseh.c. Second, please note that the first four data has a constant ESP value. The last row of the output is displayed for each function of each function in ShowSeh.c.
The last thing that is worth noting is that the top 5 pen is the same, the address is located in the code area of showseh (rather than data area), showing the code generated by the compiler for each __TRY block Use the same exception handler. Just now, I said, the first four is the four _Try formed by showseh.c. As for the 5th, it is installed by the runtime function library prior to calling Main. The address of these exception routines is all a function in the running function library of __except_handler3 - Visual C . The last exception handler is the default system processing function, located in kernel32.dll.
Structured abnormal processing and parameter confirmation
An important use of Windows 95 SEH is to provide a quick and easy way to verify the parameters of the API function. The basic concept is: Suppose the parameters are correct, then perform a series of tests. However, before performing these tests, the program code first adds a new exception handler in the SEH linked table header. If the parameter is legal, there will be no matter if there is anything, and the newly added exception handler can also take it. These only need to spend extremely small execution time.
If the parameter is incorrect, it will cause the CPU abnormal situation, so the exception handler we install will be processed. This function tells the operating system to continue the execution of threads, even in difficult circumstances (possibly causing failure of API functions). Let's take a look at some virtual code for Win32 API functions. In the description of other APIs, I just read it on the role of SEH in the parameter test. For GetCurrentDirectorya, which subsequently introduced, I will show your virtual code to show how the operating system uses SEH.
In addition to displaying the concept of SEH, the virtual code of the getCurrentDirecotrya function also demonstrates what is a typical parameter confirmation layer. The function of the parameter confirmation layer is split into two parts. The part of the exported address is just a small piece of code for parameter verification. If the parameters are correct, the code will jump to the real big role (located in the same module). In Windows 3.1, the real function other than the parameter test layer itself, its function name is more than one T. For example, getProceaddress, which talked earlier. It was seen, it was divided into two parts:
GetProcessAddress Stub
Validate The Produue Name String Paramter
JMP iGetProcessAddress
IgetProcessAddress
Meat of the code what looks up a function address
In order to make things more consistency, I also adopted the principle of Windows 3.1 (add a T in front of the function).
GetCurrentDirectorya
GetCurrentDirectorya is a typical parameter test layer. It starts to generate an ExceptionRegistrationRecord in the stack (using the PUSH directive). The contents of the Prev_Structure in this structure are placed in the stack by PUSH FS: [0]. The exception processing function specified by ExceptionRegistrationRecord is a function I named X_INValid_Param_2_Handler. Functions If there are two parameters, and there is a parameter test layer, use this x_invalid_param_2_handler function during its inspection. After pressing the ExceptionRegistrationRecord into the stack, the program code gives a pointer to the structure referred to in FS: [0]. This makes the new ExceptionRegistrationRecord that just said becomes the first one in an exception handler chain. Thereafter, the function code can securely contact the LPSZCurdir parameter without having to be legal. The parameter checking action in this example includes whether the memory referred to by LPSZCurdir is available, whether it can be written, whether the length is sufficient, and the like.
Once the parameter test process occurs, X_INVALID_PARAM_2_HANDAL will get control. This function will be described in the next section. If the parameter is correct, the execution path will not deviate. GetCurrentDirectora will finalize the exception handle. POP FS: [0] restores the original header of the exception handler chain, the Add EPS 4 will erase the address of the exception handler.
Suppose everything is done (that is, there is no abnormality), the last action of getCurrrentDirectorya is to jump to IgetCurrentDirectorya. Surprisingly, kernel32 does not seem to apply the Current Directory Pointer stored in Environment Database, which enters Win16 Task Database and uses VWIN32 INT 21H interrupt mechanism to call INT 21H's 19H number (Get Current Directory) and 7147 function (for processing long file name).
GetCurrentDirectorya function's virtual code:
// paramters:
// DWORD CCHCURDIR
// LPTSTR LPSZCURDIR
// set up structured exception handling frame. We do this by CREATING
// The Exception Record on The Stack. An Exception Record Looks Like this
//
// prev_structure; // Pointer to Previous Record
// ExceptionHandler // Address of call on an Exception
//
Push Offset X_INValid_Param_2_Handler // Offset Of Handler
Push fs: [0] // HEAD of List in fs: 0
Move FS: [0], ESP // Point Fs: 0 at Record We Just Built.
IF (LPSZCURDIR && CCHCURDIR)
{
LPSTR LPSZENDPTR = LPSZCURRDIR CCHCURDIR 1;
LPSTR LPSZTEMP;
* lpszendptr = 0; // Harmlessly Write the last byte in the buffer. if a fault ivcures, The // Exception Handler Will Be Invoked.
IF (LPSZENDPTR! = LPSZCURDIR)
{
LPSZTEMP = LPSZCURDIR;
// Go THROUGH Each Page Between The Start and End of The Buffer and touch IT.
// if a fault ocurs, The Exception Handler Will BE Invoked.
While (LPSZTEMP { * LPSZTEMP = 0; // Harmlessly Write to The Page LPSZTEMP = 0x1000; // Advance Pointer to Next Page } } } // if we got here, everything went Well. Clear Off The Exception Record from The Statck // and restore the prepious head pointer. POP FS: [0] // Put The Previous Head of the List Into Fs: 0 Add Exp, 4 // Throw Away The Exception Handler Address WE PUSHED. Goto IgetCurrentDirectorya X_INVALID_PARAM_HANDLER This function is a typical endpoint of the abnormal conditions of "parameter illegal". This function is not directly called. There are 10 programs in Kernel32, each of which is handed over to the different parameters of x_invalid_param_handler. The reason for 10 small programs is that X_INValid_Param_Handler must be responsible for removing all the parameters in the stack in the stack. The x_invalid_param_handler function must perform three main actions. The first is to output "Invalid Parameter Passed TO: XXXXXX" information in the debug window. Second, the function calls RTLunwind, which is responsible for cleaning all "exception handle functions that can be installed after the parameter verification code". Third, it is also the most important point, call the ReturnFailureCode function (this is my name). The latter calculates the correct failure code, ready to hand over the original function (for example, getCurrentDirectorya), then jump to the exit progogue of the original function. All these stories only make a Win32 function that gets the unlaveiled parameters fail. If you are in the Windows 95 debug version, you will be able to get the diagnostic information of the illegal parameters. X_INVALID_PARAM_X_PARAM Function of Virtual Code: X_INVLIAD_PARAM_1_PARAM Proc X_INVALID_PARAM_HANDLER (0x04); X_INVALID_PARAM_2_PARAMS PROC // Parameters: // struct _exception_record * ExceptionRecord // void * establisherframe, // struct _context * contexTrecord, // void * dispatcherContext X_INVALID_PARAM_HANDLER (0x08) X_INVALID_PARAM_3_PARAMS PROC X_INVALID_PARAM_HANDLER (0x0c) X_INVALID_PARAM_4_PARAMS PROC X_INVALID_PARAM_HANDLER (0x10) X_INVALID_PARAM_5_PARAMS PROC X_INVALID_PARAM_HANDLER (0x14) X_INVALID_PARAM_6_PARAMS PROC X_INVALID_PARAM_HANDLER (0x18) X_INVALID_PARAM_7_PARAMS PROC X_INVALID_PARAM_HANDLER (0x1c) X_INVALID_PARAM_8_PARAMS PROC X_INVALID_PARAM_HANDLER (0x20) X_INVALID_PARAM_9_PARAMS PROC X_INVALID_PARAM_HANDLER (0x24) X_INVALID_PARAM_SPECIAL_PARAM_SPECIAL_PROC X_INVALID_PARAM_HANDLER (0x80000000); X_INVALID_PARAM_HANDLER function's virtual code: // Parameters: // DWORD CBPARAM // DWORD CALLER_RETADDR // struct _exception_record * ExceptionRecord // void * establisherframe // struct _context * ContextRecord // void * dispatcherContext // // locals: // DWORD faultebx // DWORD faultebp // DWORD FAULTESI // DWORD faultedi // dword fsomeflag // if The unwinding flags area set (True the first time through, " IF (0 == (pEXCREC-> ExceptionFlags & (EH_UNWINDING | EN_EXIT_UNWIND))) { IF (cbparams == 0) { Fsomeflag = -1; IF (! (Pestablisherframe-> 8 & 0x100)) Fsomeflag = 0; } Else Fsomeflag = 0; DPRINT ("Invalid Parameter Passed to: / N"); // send The Eip Out Via The Debugger Int 41h Interface. X_INT41_DS_PRINT ("% PLNS", PCONTEXT-> EIP); DPRINT ("(% 04x:% 08x) / N", PContext-> segcs, pcontext-> EIP); Push EBX, ESI, EDI // Preserve Across The RTLunwind Call. RTLunwind (pestablisherframe, ffc00bad, 0, 0); POP EDI, ESI, EBX SetLastaError (Error_INVALID_PARAMETER); // RESTORES EBX, ESI, EDI, ESP, And Returns to Original Code. Return ReturnFailurecode (CBParams, Fsomeflag, pestablisher, FAULTEBX, FAULTESI, FAULTED, FAULTEBP); } Return xcpt_continue_search; Thread Local Storage (thread local storage space) TLS is a good Win32 feature that makes multi-thread programming easier. TLS is a mechanism that passes it, and the program can have global variables, but it is based on the state of each thread. That is, all threads in the process can have global variables, but these global variables make sense for specific threads. For example, you might have a multi-thread program, each thread is for different files (therefore, each thread uses a different file handle). In this case, store the files handle used by each thread in TLS, which will be very convenient. When the thread needs to know the Handle used, it can be obtained from TLS. The focus is: the thread is used to obtain files Handle's code in any case, and the files from the TLS are different. Very clever, isn't it? There are conveniences of global variables, but also different threads. Of course, you can use a chain list to make a file handle relationship with a thread ID. Let each thread have a node. Use this to simulate TLS. When the thread needs to know which file handle it uses, it can look for a file Handle from the list. You can of course save the file handle in a local variable (located in your own stack). But there is therefore put this handle to pass between functions and functions. That pain. TLS can use a simple alloc / set / get / free function to eliminate these issues. Although TLS is very convenient, it is not unlimited. In Windows NT and Windows 95, there are 64 DWORD SLOTs for each thread. This means that each thread can use 64 "Different Significance for each thread". In order to keep one slot in each thread, the program should call TLSalloc. Each time you call TLSalloc, you will return an index value that can be used by all threads. This index value is often stored in global variables. When the thread needs a slot write data, it uses TLSSetValue to pass a TLS index and a data. Later, the thread is to remove this value, which calls TLSGetValue and incorporates a TLS index again. Finally, the program calls TLSFree and confes a TLS index and releases Slot. Of course, if of course, Slot can no longer be used by any thread because the TLS index value is common between each thread. Translation: If you want to understand the meaning of the previous paragraph and the actual impact of TLS on the system level, please detail detailed description of the four TLS functions later. Although TLS can store a single value, such as file handle, more common use is to store pointers, point to private data. There are many cases that multi-threaded programs need to store a bunch of data, and they are related to each thread. Many programmers's practice is to package these variables into a C structure, then save the structure of the structure in TLS. When the new thread is born, the program allocates some memory to the structure and stores the pointer in the TLS that is retained for the thread. Once the thread ends, the program code releases all assigned blocks. The best demonstration of this program style is the apispy32 of Chapter 10. Apispy32.dll Requires a stack to returns the function address it intercepted (I am using classical computer science terminology - stack, in fact I refer to a structural chain, and a stack pointer). Since the intercepted program may have many threads, APISPY32.DLL must retain each returned address for each thread. If each thread has 64 slots used to store our own data, where did these space come from? I have said earlier, and each THREAD DATABASE has 64 DWORDs to use TLS. When you set or remove data with a TLS function, in fact you really face the 64 DWORDs. No public file tells us TLS that can access other threads. Let us take a more detailed look at these TLS functions. TLSalloc Since the TLS only provides up to 64 slots to each thread (under Windows 2000, more than 1,000 slots can be used), so a method must track which SLOT has been used. KERNEL32 uses two DWORDs (a total of 64-bit) to record which Slot is available, which Slot has been used. These two DWORDs imagine a 64-bit array, if a bit in the 64 bit is set, it means that it corresponds to the TLS Slot has been used. This 64-bit TLS Slot array is stored in Process Database (maybe you will guess in Thread Database, no, not this). Remember that when you assign a TLS Slot, this SLOT can be referenced in any thread owned by the process. The 64-bit TLS Slot array stores two DWORDs in Process Database 0x88 and 0x8c. Although the following TLSalloc function virtual code may seem to be a bit complex, it is not true. All of them is just a scanning 64-bit array, see if there is 0. If found, it is changed to 1 and returns its location in an array (used as an index value). Therefore, if the 5th bit is 0, TLSalloc changes it to 1 and returns 4 (the index value starts from 0). TLSalloc function virtual code: // locals: // DWORD I // pdword ptlsinusebits // dword newflag X_logsomekernelFunction (Function Number for TLSalloc); i = 0; _ENTRYSYSLVEL (x_tlsmutex); PtlsinuseBits = & PPCurrentProcess-> TLSINUSEBITS1; // Poition Ptlsinusebits So That It Points at The First of the Two Tls Bit DWords That HAS // a free bit available While (* ptlsinusebits == 0xffffffff && (i <2)) { i ; PTLSINUSEBITS ; // Point At Next Dword of Tlsinusebits. } IF (i <2) // if a free bit-slot wsas found, it is 0 or 1 { i * = 32; // "i" starts at Either 0 or 1 // of the tlsinusebits dwords Newflag = 1; IF (* ptlsinusebits & newflag) { // Blast Through The Bits in This DWORD Unsil We Find One That's 0 (Available). // Keep Incrementing "I" so this we we're done, it's a tls index. DO { i ; Newflags << 1; } while (* ptlsinusebits & newflag) } * ptlsinusebits | = newflag; // Turn on the newly allocated bit to indeicate what // Corresponding TLS INDEX IS IN USE. } Else { // IF We get here, all the tls indices.. Return -1 and set the last error code. I = TLS_OUT_OF_DEXES; // 0xffffffFFFFFFFF INTERNALSETLASTERROR (Error_NO_MORE_ITEMS); } _Leavesyslevel (x_tlsmutex); Return I; supplement: The above description of TLSalloc is the same by observing the behavior of the function in Windows 2000 and the description of the virtual code described above. TLSSetValue TLSSetValue can put data into previously assigned TLS Slot. The two parameters are the TLS Slot index value and the data content that is ready to be written. The function first checks if the array index is legal (less than 64). The earlier versions of Windows 95 also check if this index is indeed assigned, but in the beta3 version, only the simplest check of the above. If the index value is less than 64, TLSSetValue puts your specified data into an array of 64 DWORDs (located in the current Thread Database) appropriate location. In addition, TLSSetValue updates the second 64 DWORDS array. This array contains the EIP value, and the last time TLSSetValue is called there. Obviously, these EIPs are for debugging. Microsoft does not provide what method allows the application to take this data. Virtual code for the TLSSetValue function: // Parameters: // DWORD DWTLSINDEX; // LPVOID LPVTLSVALUE; // locals: // pthread_database PTDB // the thread Database Starts 0x10 Bytes Before The Tib Pointed to by the FS // register. make a pointer to the Thread Database PTDB = fs: [PTIBSEL] - 0x10; IF (DWTLSINDEX { PTDB-> TLSArray [dwtlsindex] = * lpvtlsValue; // Grab Return Eip Off The Stack and Store in The Others TLS Array That Runsptdb-> LasttlssetValueeiP [DWTLSINDEX] = [EBP 04]; Return Ture; } Else { PTDB-> getLastErrorcode = error_INVALID_PARAMETER; Return 0; } TLSGetValue This function seems to always TLSSetValue's mirror, the biggest difference is that it takes out data instead of saving data. Like TLSSetValue, this function is also checked before the TLS index value is legal. If so, TLSGetValue uses this index value to find the corresponding data item of the 64 DWORDS array (located in Thread Database) and returns its content. Virtual code for the TLSGetValue function: // Parameters: // DWORD DWTLSINDEX // locals: // pthread_database PTDB // The thread database starts 0x10 bytes before the Tib Pointed to by the fs register. // make a pointer to the thread database. PTDB = fs: [PTIBSELF] - 0x10; IF (DWTLSINDEX { // set Last Error Value TO 0 PTDB-> getLastErrorcode = Error_sucess; Return PTDB-> TLSARRAY [DWTLSINDEX]; } Else // The TLS INDEX PaSSED IN WA> = TLS_MINIMUM_AVAILABLE. { PTDB-> getLastErrorcode = error_INVALID_PARAMETER; Return 0; } supplement: In Windows 2000, TLSSetValue / TLSGetValue does not check DWTLSINDEX. TLSFree This function wipes all the efforts of TLSalloc and TLSSetValue. TLSFree first checks if the index value you handed over is indeed allocated. If so, it turns off the corresponding 64-bit TLS Slot (set it 0). Then, in order to avoid the content that is no longer legal, the TLSFree traverses each thread of the process, put the 0 on the TLS Slot that is just released. So, if a TLS index is later reassigned, all threads that use the index will guarantee a 0 value, unless they call TLSsetValue. Virtual code for TLSFree functions: // Parameters: // DWORD DWTLSINDEX // locals: // DWord RetValue // pdword ptlsinusebits; // pthread_database PTDB; // pk32objectListentry PK32Object X_logsomekernelFunction (Function Number for TLSFree); _ENTRYSYSLVEL (PKRN32MUTEX); _ENTRYSYSLVEL (x_tlsmutex); Point PtlsinuseBits to Either PpCurrentProcess-> TLSINUSEBITS1OR PPCurrentProcess-> TLSINUSEBITS2 AS ApproPriate. IF (DWTLSINDEX { DWORD Turnofflag; // Create A DWord with the appreate flag set what represents The TLS INDEX To Be FREED. Turnoffflag = 1 << (dwtlsindex & 0x1f); // if That Bit is Already Turned Off in The Process Database's Tlsinusebits Filed. // The TLS Index isn't Allocated. This is a bad thing, so go report an error. IF (0 == Turnoffflag & * Ptlsinusebits) { Goto error; } // Turn Off The Correct Bit in The Tlsinusebits Field of The Process Database * ptlsinusebits = ~ turnofflag; // Now Walk Through Each of The Threads of The Process, Putting The Value 0 // INTO The DWORD Assigned to the TLS INDEX We're Freeing. PK32Object = x_getnextObjectINList (PpcurrentProcess-> Threadlist, 0); While (PK32Object) { PTDB = PK32Object-> POBJECT; PTDB-> TLSARRAY [DWTLSINDEX] = 0; PTDB-> AnothertlsArray [dwtlsindex] = 0; PK32Object = x_getnextObjectINList (PpCurrentProcess-> Threadlist, 1); } RetValue = 1; } Else { Error: RetValue = 0; InternealSetLastError (Error_INValid_Parameter); } DONE: _LEVELSYSLVEL (X_TLSMUTEX); _LEVELSYSLVEL (PKRN32MUTEX); Return RetValue; Miscellaneous function The various functions described in this section cannot be classified in the previous topics. Even so, they are all important functions that emphasize "kernel32.dll uses Thread Database". GetLastError GetLastError is a mechanism that can make use of it to determine why a system call will fail. When the Windows 95 function fails, it can selectively set "last error code" in the current thread, indicating the failure. There are many error codes defined in WineError.h. GetLastError is a bit like the Error variable in the C function library. In addition to system functions, applications can also participate in this party and call SetLastError. Those error codes are best different from the system-defined error code. The GetLastError function is very simple. After determining the query object (a thread), it returns the contents of the thread Database's GetLasterRorcode member. If there is no query object (ie, a thread), this function returns the value in the Kernel32 global variable. This global variable appears in the virtual code below (I didn't name it). Getlasterror function's virtual code: IF (PPCurrentThread) Return PpCurrentthread-> getLastErrorCode Else Return X_LASTERRORFNOCURRENTTHREAD; // a Global Variable in kernel32.dll SetLasterror SetLastError is also very simple. If there is currently a thread, this function sets the getLastErrorCode member in THREAD DATABASE. Virtual code for setLastError functions: // locals: // dword fdwrror IF (PPCurrentThread) PpCurrentthread-> getLastErrorCode = fdwerror GetExitcodethread and IgetExitcodethread GetExitCodetRead returns an exit status of the thread specified by HThread. The thread exit state should be recorded in the TerminationStatus member of Thread Database. In normal implementation, its value is 0x103 (STILL_ACTIVE). GetExitcodethread is just a parameter check. After checking the pixel, it calls IGETEXITCODETHREAD. IGETEXITCODETHREAD first enters a paragraph that must be completed (Must Complete), then transforms HThread to the Thread Database pointer. After removing TerminationStatus members, it leaves the paragraph "must complete". supplement: The application of TLS is very wide, and the C / C running library uses TLS. The following figure is the internal management structure of TLS in Windows 2000: Internal management structure used to manage TLS Mrcosoft guarantees that at least TLS_MINIMUM_AVAILABLE SLOT is available. TLS_MINIMUM_AVAILABLE is defined as 64 in Winnt.h. Windows 2000 extends its existence to allows Slot for more than 1,000 TLS. This quantity is sufficient for any application.