Based on the material described in the
first and
second parts of this article, we will continue to discuss the topic of exception handling in Windows x64.
The described material requires knowledge of basic concepts, such as the prologue, epilogue, frame functions and understanding of basic processes, such as the actions of the prologue and epilogue, the transfer of function parameters and the return of the result of the function. If the reader is not familiar with the above, then before reading it is recommended to read the material from the first part of this article. Also, if the reader is not familiar with the PE image structures that are involved in the processing of an exception, then before reading it is also recommended to get acquainted with the material from the second part of this article.
The given description refers to the implementation in Windows, and, therefore, one should not assume that the implementation of this mechanism attached to the article will exactly coincide with it, although there is no conceptual difference. Details of the attached implementation in the article will not be considered, if this is not stated explicitly. Therefore, it is assumed that these details, if necessary, should be studied independently.
The article is accompanied by the implementation of the mechanism, which is located in the exceptions folder of the git repository at
this address .
')
1. Exceptions and their processing
In the following subsections, the handling of the exception and everything that underlies it will be discussed in detail. The given description refers to the implementation in Windows, and, therefore, one should not assume that the implementation of this mechanism attached to the article will exactly coincide with it, although there is no conceptual difference. Details of the attached implementation in the article will not be considered, if this is not stated explicitly. Therefore, it is assumed that these details, if necessary, should be studied independently.
1.1. Secondary functions
Before starting to discuss the exception handling process, consider the RtlLookupFunctionEntry and RtlVirtualUnwind functions that are exported by the ntoskrnl.exe module in kernel space and the ntdll.dll library in user space.
The RtlLookupFunctionEntry function, whose prototype is shown in Figure 1, returns a pointer to the RUNTIME_FUNCTION structure and the address of the beginning of the PE image corresponding to the code whose address is passed in the ControlPc parameter.
Picture 1The ImageBase parameter accepts a pointer to the variable where the function returns the address of the beginning of the PE image, and the HistoryTable parameter, which is optional, accepts a pointer to the structure that is used to cache the search. The format of the structure of the last parameter can be found in winnt.h. If the function returns NULL, either the corresponding PE image could not be found by the passed code pointer, or no corresponding record was found in the function table, which may mean that the function does not have a frame.
The RtlVirtualUnwind function, the prototype of which is shown in Figure 2, performs a virtual promotion function.
Figure 2It is called virtual because the function does not change the state of the physical processor, and instead takes in the ContextRecord parameter a pointer to a structure that describes the context of the processor at a particular point in time. The context of the processor after the promotion returns to the same structure. The CONTEXT structure itself is depicted below in Figure 3.
Figure 3The P1Home-P6Home fields are introduced for ease of use of the structure, for example, they can be used as a region of register and stack parameters. The ContextFlags field is bit-wise and describes the state of the entire structure, i.e. which fields reflect the state of the corresponding processor registers, and which ones do not. The field may contain the following flags:
- CONTEXT_CONTROL - if set, the SegSs, Rsp, SegCs, Rip, and EFlags fields reflect the state of the corresponding processor registers;
- CONTEXT_INTEGER - if set, the Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, and R8-R15 fields reflect the state of the corresponding processor registers;
- CONTEXT_SEGMENTS - if set, the fields SegDs, SegEs, SegFs and SegGs reflect the state of the corresponding processor registers;
- CONTEXT_FLOATING_POINT - if set, the fields Xmm0-Xmm15 and MxCsr reflect the state of the corresponding processor registers;
- CONTEXT_DEBUG_REGISTERS - if set, the fields Dr0-Dr3 and Dr6-Dr7 reflect the state of the corresponding processor registers.
It should be noted that the ContextFlags field is not interpreted in any way by the RtlVirtualUnwind function, and it always assumes that the structure contains the actual state of the processor at a particular point in time. Since the VectorRegister, VectorControl, DebugControl, LastBranchToRip, LastBranchFromRip, LastExceptionToRip, and LastExceptionFromRip fields are not directly related to the topic under discussion, their purpose will not be described here. The FltSave field is used when it is necessary to save the state of the full XMM context, including the state of the FPU.
The ImageBase parameter takes the address of the PE image whose code was executed. The ControlPc parameter carries the address of the instruction where execution was interrupted, and the FunctionEntry parameter accepts the address of the RUNTIME_FUNCTION structure corresponding to the address of the instruction.
The HandlerType parameter takes the type of handler expected. If the value is UNW_FLAG_EHANDLER and the function being spun has an exception handler, the function returns the address of this exception handler. If the value is UNW_FLAG_UHANDLER and the function being spun has a spinup handler, the function returns the address of this spinup handler. In all other cases, the function returns NULL. It should also be noted that if the execution of the unwind function was interrupted at the moment of execution of the prolog code or the epilog code, the function returns NULL. The philosophy here is that the exception and / or promotion handler is attached to the function body. If the function returns the address of one of the handlers, then it also returns the address of the data associated with this handler by the compiler of the corresponding programming language. The address is returned by the pointer passed in the HandlerData parameter. As described in Section 3 of the second part of this article, the address of the exception handler and / or unwind handler is stored in the ExceptionHandlerAddress field of the EXCEPTION_HANDLER structure when data associated with the handler is stored in the LanguageSpecificData field of the same structure.
The EstablisherFrame parameter takes a pointer to a variable where the function returns the frame pointer before the function is unwound. The ContextPointers parameter is optional and, if used, contains the address of the structure that repeats the contents of general registers and XMM registers after promotion. It should be noted that only the registers that participated in the promotion fall into the structure.
Below, in Figure 4, an example of how the function works.
Figure 4On the left is an example of an image that contains three functions: wmain, func2, and func1. The wmain function calls the func2 function, which in turn calls func1. The middle shows the assembler views of each of the functions, where the left shows the absolute address of the instruction in memory, the middle value of the RSP before executing the instruction, when the assembler mnemonic of the instruction itself is to the right. The RSP values are not depicted for all instructions, but only for those that were executed or for an instruction whose execution of the thread was interrupted. To the right of assembler representations of functions, the value of processor registers is shown before calling the corresponding functions, for brevity, only general registers and the instruction pointer register (RIP) are given. Labels indicate the values of the parameters that are passed to the function or returned by it.
The RtlVirtualUnwind function spins exactly one function, i.e. the contents of the CONTEXT structure after executing the function will become such that the function being spun is not called, with the exception that the values of the non-permanent registers will not be restored, and the RIP will contain a pointer not to the instruction to call the corresponding function, but to the instruction immediately after it. Also, the function RtlVirtualUnwind will return the frame pointer of the untwisted function, and, if required, a pointer to its handler and a pointer to the data of this handler.
As reflected in the example, the ImageBase parameter will be 0x7FF6AEAF000 (label 1); ControlPc will be equal to 0x7FF6AEAF104E (label 2); the FunctionEntry parameter will contain the address of the RUNTIME_FUNCTION structure (label 3) corresponding to the address of the instruction whose value is passed in the ControlPc parameter; The ContextRecord parameter will contain the register values of the interrupted function (label 4). A pointer to the RUNTIME_FUNCTION structure can be obtained using the RtlLookupFunctionEntry function. The function RtlVirtualUnwind after execution returns: the values of the registers after promotion (label 5); from the EXCEPTION_HANDLER structure, returns the address in the LanguageSpecificData field to the HandlerData parameter and returns a pointer to the handler that is extracted from the ExceptionHandlerAddress field of the same structure (label 6); the function will also return the frame pointer of the untwisted function to the EstablisherFrame parameter and its value will be 0x9C5DBCF900.
The function RtlVirtualUnwind, before unwinding, determines whether the processor performed a prologue, epilogue, or body of an unwound function. If it was a body, then spin is performed using the UNWIND_INFO structure. If it was a prologue, the spinup is also performed, with the exception that the function first determines in which place the execution of the prologue was interrupted, and it is from this point that the spinup is performed. If it was an epilogue, then for version 2 of the UNWIND_INFO structure, promotion is performed from it. Also, as in the case of the prologue, before promotion, the function determines in which place the execution of the epilogue was interrupted, and the promotion is performed from this place. For version 1 of the UNWIND_INFO structure, unwinding is performed by analyzing the subsequent instructions of the epilogue, since The UNWIND_INFO structure of this version does not contain any information about the epilogue. Section 1 of the first part of this article mentioned that the beginning of the epilogue of functions described by the structures of UNWIND_INFO version 1 are considered
add rsp, constant or
lea rsp, [frame pointer + constant] instructions. The fact is that XMM instruction analysis complicates the code of the spin function, and if an exception occurs before the conditional epilogue, then their values will be restored from the UNWIND_INFO structure, therefore the integrity of these registers is not damaged after unwinding. The only side effect is when an exception occurs at the time the XMM registers are restored, in which case the RtlVirtualUnwind function will return a pointer to the exception handler, which will therefore be called when the exception is processed. For the functions described by the UNWIND_INFO version 2 structures, this philosophy has slightly changed, and the instructions for pushing general registers out of the stack have been considered the beginning of the epilog, since instructions
add rsp, constant and lea rsp, [frame pointer + constant] cannot raise exceptions in principle. Figure 5 shows an example of a code, to the right of which an assembler representation of its two functions is shown: func1 and func2. The instruction addresses are absolute, and for compactness, their hexadecimal representation is missing. As an example, consider the promotion of the stack function func1. The figure shows three cases: A, B, C. Each of them represents the state of performance of the function: A - prologue, B - body, C - epilogue.
Figure 5Below, in Figures 6-8, each case is considered separately. On the left, the processor registers are shown before promotion, and on the right, after. For compactness, only general registers and the instruction pointer register (RIP) are shown, since in the cases described, only these registers are subject to change.
Figure 6 shows Case A. In this case, the processor performed the prolog of the function func1, whose execution was interrupted with the RDI register pushing instruction. Promotion of the stack in this case will be performed using the data structure UNWIND_INFO. First, the RSI register value is restored, then the return address is restored. Therefore, the RSI, RSP, RIP registers will be changed.
Figure 6Figure 7 shows Case B. In this case, the processor executed the body of the function func1, whose execution was interrupted by an 8-byte read instruction from the top of the stack to the RAX register. Promotion of the stack in this case will be performed using the data structure UNWIND_INFO. First, the stack memory allocated by the prologue for the local variables of the function will be released, then the values of the RDI and RSI registers will be restored, then the return address will be restored. Therefore, the RSI, RDI, RSP, RIP registers will be changed.
Figure 7Figure 8 shows case C. In this case, the processor executed the epilog of func1, the execution of which was interrupted on the instruction to eject the RDI register. Depending on the version of the UNWIND_INFO structure, spinning will be performed either by using the structure itself, if it is a version 2 structure, or by analyzing the epilogue code, if it is a structure of version 1. First, the values of the RDI and RSI registers will be restored, then the return address will be restored. Therefore, the RSI, RDI, RSP, RIP registers will be changed.
Figure 8The ControlPc parameter for case A will be equal to 0x7FF70C131036, for case B, 0x7FF70C13104E, for case C, 0x7FF70C131078. The ImageBase parameter for all three cases will be equal to 0x7FF70C130000.
The EstablisherFrame parameter for case A will take the value 0x6DE73AF6E0, for case B, 0x7E68EFF860, for case C, 0x979BB9FAD8. In all three cases, this will be the value of the RSP register before unwinding. Consider separately the values EstablisherFrame for a function that has a frame pointer. Figure 9 shows an example of such a function, where the addresses of instructions are absolute, and instead of their hexadecimal representation, the stack pointer (RSP) is shown before the instruction is executed.
Figure 9If the function was interrupted before executing the instruction at 0x7FF6D76C1101, then EstablisherFrame will take the value 0x9E84B9FAA0. If execution was interrupted before executing the instruction at 0x7FF6D76C110A, then EstablisherFrame will take the value 0x9E84B9FA90. If execution was interrupted before executing the instruction at 0x7FF6D76C1118, then EstablisherFrame will also take the value 0x9E84B9FA90. It should be noted that if the function has a frame pointer, as in this example, and it was set before the function was interrupted, EstablisherFrame will take the value of the stack pointer at the time the frame pointer is set, not the current stack pointer. In this example, the installation of the frame pointer was performed by the instruction at the address 0x7FF6D76C1106.
2. Processing
The whole process of exception handling can be divided into two parts.
The first part is finding and calling the exception handler. This part is performed by the operating system. The conceptual scheme of exception handling is shown below in Figure 10.
Figure 10The figure above shows the space of the whole process. The user space is shown on the left, while the kernel space is on the right. In each of the spaces are placed modules. For any application, the ntdll.dll module is always displayed, which performs the necessary auxiliary tasks for the user space. In kernel space, ntoskrnl.exe is always present, which is the core of Windows. The remaining modules are shown for example only. When an exception occurs, the processor calls a function from the corresponding gateway descriptor, which is an element of the Interrupt Descriptor Table. This function is a kernel function. For more information on the interrupt table, see the Intel 64 and IA-32 Architectures Software Developer's Manual. Further, this function together with the KiExceptionDispatch function prepares all the necessary data for processing, after which the KiDispatchException function is called, which performs additional actions before processing, one of which is that if an exception occurred in user space, then the processing of this exception is redirected to the user space. space. The ntdll.dll module is responsible for processing in user space. When all the necessary preparation for processing is done, the RtlDispatchException function is called, which performs a search and a call for the handler by scanning the .pdata of the image sections, and if the handler is found, then the function calls it. It should also be noted that the function does not unwind the stack, it only performs a search for a handler.
The second part depends on the format of the LanguageSpecificData field of the EXCEPTION_HANDLER structure generated by the compiler of the corresponding programming language and the implementation of the found exception handler relying on this field.
In this article, we look at the try / except and try / finally constructs of C / C ++, therefore, when describing the second part, we will discuss the format of the LanguageSpecificData field of the EXCEPTION_HANDLER structure generated by the compiler for these constructions.
In the following subsections, the entire process of preparing and searching for a processor is described in more detail. For the sake of certainty and simplification of the explanation, the whole process will be considered on the example of the processing of the dividing by zero exception, and the code that generated this exception will be the kernel mode code. Despite the fact that the whole explanation will be limited to an example of a specific exception, the description described will also be relevant for other exceptions, since if they do not repeat the exclusion of division by zero, they are very similar and conceptually behave the same way.
2.1 Preparation for processing
As it was already indicated earlier, when an exception occurs, the processor calls the function from the corresponding gateway descriptor, which is an element of the interrupt descriptor table. The function of the divide-by-zero gateway is the KiDivideErrorFault kernel function. Figure 11 below shows the assembler representation of the function. For brevity, only that part of the code that is directly related to the topic under discussion is displayed.
Figure 11As can be seen from the figure, the function first simulates an empty error code, since to avoid dividing by zero, the processor does not push the code onto the stack. Next, the function pushes the general-purpose registers, allocates memory on the stack, and sets the frame pointer. This is where the function prologue ends. Saving non-persistent general-purpose registers and XMM registers is performed in the function body. The function also stores in the stack variable the type of the called handler. The value 1 is always set for exceptions, 0 for interrupts and 2 for services, i.e. call kernel services from user space. The last action of the function is a call to the KiExceptionDispatch function. Before calling, the function also resets the direction flag, saves the MXCSR register of the XMM block, and then loads it with the standard value. This will be discussed in more detail below. Please note that the function does not have an epilog. The fact is that the work of the thread, after processing the exception, does not resume in the usual way, i.e. KiExceptionDispatch does not return a control, and therefore, an epilog is not needed. After the instruction of the function call follows the idle instruction. This is the so-called placeholder. It is assigned a special role, its presence allows the RtlVirtualUnwind function to reliably determine that an exception occurred during the execution of the function body. That is, if there is no such placeholder, then the RtlVirtualUnwind function, when promoting the KiExceptionDispatch function, retrieves the return address of the
retn instruction, instead of the
nop instruction. And, therefore, at the next iteration of the spinup (that is, when the KiDivideErrorFault function is already spinning), the RtlVirtualUnwind function will analyze whether the prologue, epilogue, or body was executed. As it was already noted in section 1 of the first part of this article, whether the epilog was performed is determined by the code of the function itself (or using records like UWOP_EPILOG, the UNWIND_INFO version 2 structure, which does not make a big difference, because in this case it plays a role the address of the instruction, not the stream of code bytes), and since the
retn instruction is used only in epilogs, the RtlVirtualUnwind function will make the erroneous assumption that the epilogue was being executed, and not the body. Consequently, this will lead to the fact that during the promotion of the KiDivideErrorFault function, the prologue will not be promoted, and the address of the frame following the stack above the function will be incorrectly determined.
The MXCSR XMM block register was not mentioned in Section 3 of the first part of this article, but call conventions also regulate its use when calling functions. This register is divided into a permanent and non-permanent part, as shown in Figure 12 below.
Figure 12The non-constant part consists of 6 state flags, bits 0-5. The rest of the register, consisting of control bits c 6-15, is considered constant. If the called function changes the state of the constant part, then it must restore it before returning. Moreover, the calling function, before calling other functions, must load the standard values into the permanent part, if it has been changed. The standard values of the fields of the constant part:
- Bit 6 is 0: the denormal operands are zeros;
- Bits 7-12 are 1: all exceptions are masked;
- Bits 13-14 are 0: round to the nearest;
- Bit 15 is 0: resetting the result to zero at the bottom overflow.
These rules can be violated only in two cases:
- if the purpose of the function is to change the constant part of the register;
- if the violation of these rules does not lead to a change in the behavior of the program, i.e. the program will behave as if the rules were not violated.
The state of the nonconstant part should not be interpreted in any way on the border of functions, i.e. a called function should not rely on its values, and the calling function after returning control to it, unless explicitly indicated in the function description.
As for the direction flag (DF), its default value is 0. If the flag has been set, then it must be reset before calling the function or before returning from the function.
The KiExceptionDispatch function takes 8 parameters. ECX contains the exception code that occurred; EDX is the number of parameters specific to this exception; R8 contains the address of the instruction that generated the exception; registers R9, R10, R11 contain the values of the parameters characteristic of this exception; RBP and RSP are pointers to stored non-persistent registers. As noted earlier, the function does not return control. Below, in Figure 13, an assembly representation of the function is shown. For brevity, only those parts of the code that are directly related to the topic under discussion are given.
Figure 13As can be seen from the figure, the function prolog first allocates memory in the stack, after which the constant XMM registers and general-purpose registers are saved. This is where the function prologue ends. Next, the function initializes the EXCEPTION_RECORD structure in the memory area allocated in the stack and calls the KiDispatchException function. After returning from a function, the following are restored: permanent general registers, permanent XMM registers, MXCSR register, non-permanent general registers and non-permanent XMM registers. Next, the stack memory allocated by the gateway function (in this example, the memory allocated by the KiDivideErrorFault function) is released, and it is returned to the interrupted flow. The EXCEPTION_RECORD structure is defined below in Figure 14.
Figure 14The ExceptionCode field contains the exception code. The ExceptionFlags field is a bit field and describes the type and state of the exception handling. Its flags will be discussed in detail in the process of discussing the search and call handler, as well as in the process of discussing the promotion of the stack. The ExceptionRecord field in some cases contains a pointer to another structure of the same type. For example, if during the search for an exception handler or a spinup handler, an invalid situation was detected (for example, the handler returned an invalid processing result), then a new exception will be thrown, the EXCEPTION_RECORD structure of which will contain a pointer to the EXCEPTION_RECORD structure for the exception in the context of which this situation occurred. In other cases, the field is NULL. It should be noted that this statement is true for 32-bit versions of Windows, and in 64-bit versions it is almost always NULL. The ExceptionAddress field contains the address of the instruction that raised the exception. The NumberParameters field contains the number of parameters in the ExceptionInformation array that are specific to the specific type of exception, and the definition of EXCEPTION_MAXIMUM_PARAMETERS is 15, i.e. this is the maximum number of parameters for all types of exceptions.
The KiDispatchException function takes 5 parameters: ExceptionRecord - a pointer to an EXCEPTION_RECORD structure describing the reason for the exception; NonvolatileRegisters - pointer to permanent registers; VolatileRegisters - pointer to non-permanent registers; PreviousMode - context of the thread in which the exception occurred (user or kernel context); FirstChance - the first processing attempt (TRUE or FALSE). The function returns no value.
ExceptionRecord describes the reason for the exception. VolatileRegisters is formed by the gateway function (in this example, the KiDivideErrorFault function). NonvolatileRegisters is generated by the KiExceptionDispatch function. It should also be noted that both structures contain not only the values of the registers at the time of the exception, but also other multi-sort information, which will not be discussed in this article, since It is not directly related to the topic under discussion. PreviousMode carries information about the context in which the exception occurred, and is equal to either KernelMode or UserMode. FirstChance is a boolean value indicating whether this attempt to handle the exception is the first one.
The KiDispatchException function is responsible for handling the exception without involving the exception handlers themselves, if possible. Also, if an exception occurred in user space, then the exception handling is redirected to it. A simplified block diagram of the function is shown in Figure 15 below.
Figure 15As shown in the figure, at the beginning of its work, the function forms a CONTEXT structure from the structures pointed to by NonvolatileRegisters and VolatileRegisters, and the fields reflecting processor registers that are not contained in these structures (for example, segment registers) are initialized with standard values. Therefore, this structure will reflect the values of the processor registers at the time of the exception.
The function then tries to handle the exception using the KiPreprocessFault function, without involving exception handlers. If the exception cannot be handled, then if it originated in the context of the kernel, the function calls the RtlDispatchException function, which searches for and calls the handler.
After the RtlDispatchException function has completed its work, and since the fields of the CONTEXT structure could be changed by exception handlers, the fields of this structure are copied back to the structures pointed to by NonvolatileRegisters and VolatileRegisters via the KeContextToKframes function, thus modifying the context of the interrupted flow.
If an exception occurred in a user context, the function does not call the handlers for security reasons, and instead, the function copies the RSP and RIP values when an exception occurs to the user stack, copies the EXCEPTION_RECORD and CONTEXT structures to the user stack, and modifies the engine frame so that when returning from the function, control was transferred to the user mode handler.
A pointer to a user mode handler is registered at the time of system initialization. The function responsible for handling exceptions in a user context is located in the ntdll.dll library called KiUserExceptionDispatch. Although a custom exception handler is called for user space, it is very similar to the kernel mode handler, and therefore no further explanation is needed regarding its operation.
2.2 Search and call handler
As mentioned earlier, the RtlDispatchException function searches and calls the handler. The function takes two parameters: ExceptionRecord - a pointer to the EXCEPTION_RECORD structure describing the reason for the exception; ContextRecord is a pointer to a CONTEXT structure that describes the state of the processor registers at the time of the exception. The function returns a boolean value, TRUE if the exception was processed, and FALSE otherwise.
The RtlDispatchException function performs a sequential scan on the stack of called functions. If the function has a handler, then the RtlDispatchException function calls it. If the handler returns ExceptionContinueExecution, then the RtlDispatchException function stops, otherwise the search for the handler continues. Below, in Figure 16, is a block diagram of the function.
Figure 16At the beginning of the function, the function receives the lower and upper limits of the stack. Since, when the exception handler is called, it is passed a pointer to the structure that describes the processor state at the moment the exception occurred, and since the function performs virtual stack promotion during the search, the contents of the transferred CONTEXT structure will change, and therefore the function copies its contents to its local variable.
Next, the function generates the initial value of the ExceptionFlags field for the EXCEPTION_RECORD structure. It should be noted that the field of the transmitted structure may contain the EXCEPTION_NONCONTINUABLE flag set, which means that it is not possible to continue the execution of the interrupted thread. Therefore, the function, when initializing the initial value, copies this flag from the passed structure to a local variable. The function then zeroes the function's frame pointer, the exception handler of which, in the course of its execution, raised the exception (that is, the nested exception) and copies the address of the instruction that generated the exception into the local variable from the transmitted EXCEPTION_RECORD structure.
Further, the function, using the RtlLookupFunctionEntry function, obtains the address of the PE image and a pointer to the RUNTIME_FUNCTION structure of the function of this image, during the execution of which an exception occurred. If the function does not return a pointer, then it is considered that an exception occurred during the execution of a simple function, which, as discussed earlier, do not have any information about promotion. Since simple functions do not allocate memory in the stack, their RSP value will point to the return address, therefore, for such functions, the RtlDispatchException function retrieves this address, copies its value in the Rip field of the local CONTEXT structure, and increases the value of the Rsp field of the same structure by 8, way simulating the promotion of a simple function. Now the contents of the local CONTEXT structure describe the state of execution of the next function in the stack above. Further, the function from the local CONTEXT structure copies into the local variable the address of the instruction belonging to the function already next in the stack above, and checks with the RtlpIsFrameInBounds function that the new RSP pointer is within the stack limit. If the pointer goes beyond these limits, it means that the exception handler was not found, and therefore the RtlDispatchException function will return FALSE.
Otherwise, the function will continue its work, starting with obtaining the address of the PE image and a pointer to the RUNTIME_FUNCTION structure, for the address of the new instruction, already for the next function above the stack.For personnel functions, the RtlLookupFunctionEntry function will return a pointer to the RUNTIME_FUNCTION structure. In this case, promotion of such functions is performed using the RtlVirtualUnwind function, which returns the frame pointer for the untwisted function. Immediately after the promotion, it checks that the frame pointer is within the stack limit. If the frame pointer goes beyond these limits, then the RtlDispatchException function sets the EXCEPTION_STACK_INVALID flag in the ExceptionFlags field of the passed CONTEXT structure and returns FALSE. Otherwise, if the RtlVirtualUnwind function did not return a pointer to the exception handler for the untwisted function, the RtlDispatchException function will continue promotion of the next function above the stack, after copying the address of the instruction belonging to this function,and checking the value of the Rsp field of the local CONTEXT structure to exceed the stack limit.If the RtlVirtualUnwind function returns a pointer to an exception handler, then the RtlDispatchException function will call it. Before calling it, the function will update the contents of the ExceptionFlags field of the passed EXCEPTION_RECORD structure from its local copy. The exception handler was first discussed in Section 3 of the second part of this article, and its prototype is shown in Figure 5. Before calling the handler, the function prepares the DISPATCHER_CONTEXT structure, which is actively used in cases of nested exceptions and active unwind. The structure definition is shown below in Figure 17.Figure 17The ControlPc field contains the address belonging to the function body for which the handler was called. The ImageBase field contains the address of the beginning of the PE image, which contains the function and its handler. The FunctionEntry field contains the address of the RUNTIME_FUNCTION structure of the same function. The EstablisherFrame field contains a function frame pointer. The TargetIp field is used in the promotion, and will be discussed in detail when discussing it. The ContextRecord field contains a pointer to a CONTEXT structure that reflects the current search state of the processor, i.e. pointer to the local variable of the function RtlDispatchException. The LanguageHandler field contains the address of the handler being called. The HandlerData field contains an address for data specific to the corresponding programming language. The HistoryTable field contains a pointer to the search cache table.The ScopeIndex field contains the current value of a local variable of the RtlDispatchException function, and its purpose will be discussed in detail when discussing promotion. The Fill0 field is not used at all and is present for alignment purposes.The RtlDispatchException function does not call the exception handler directly, and instead uses the helper function RtlpExecuteHandlerForException, which takes the same parameters as the handler itself, and also returns the same value. This function is actually a wrapper around the function of the exception handler and is used to catch the exceptions that occurred during the execution of the exception handler itself. The assembler representation of the function is shown below in Figure 18.Figure 18As shown in the figure, the function first allocates memory in the stack for register variables and one variable, stores a pointer to the structure passed to DISPATCHER_CONTEXT in this variable and calls the exception handler whose address is stored in the LanguageHandler field of the DISPATCHER_CONTEXT structure. Also note the presence of a function body placeholder. In addition to the reasons for its necessity described earlier, another one is added to them: since the exception handler is tied to the function body, it will not be called if there is no placeholder, and therefore, the RtlDispatchException function will be additionally violated for this reason. The assembler representation of the exception handler function is shown below in Figure 19.Figure 19As shown in the figure, first the handler checks whether promotion is performed, and if it is executed, the function returns ExceptionContinueSearch, thereby giving the unwind function an indication that the search handler will continue searching. Otherwise, the search for the exception handler was performed, during which another exception occurred and you need to copy the frame pointer of the function whose handler generated the new exception into the DISPATCHER_CONTEXT structure of the current search handler process.After the DISPATCHER_CONTEXT structure has been prepared, the RtlDispatchException function calls the exception handler. Immediately after calling the handler, the function sets the EXCEPTION_NONCONTINUABLE flag in its local copy of the flags if it was set in the passed to the EXCEPTION_RECORD structure by the handler. Next, the function resets the EXCEPTION_NESTED_CALL flag in the local copy and resets the frame pointer for the function, the exception exception handler of which, during its execution, raised an exception if the frame pointer of this function coincides with the previously fixed one. The following describes the corresponding actions of the functions depending on the result.If the handler returned ExceptionContinueSearch, the function will continue promotion of the next function above the stack, first copying the address belonging to this function and checking the value of the Rsp field of the local CONTEXT structure to exit the stack limit.If the handler returns ExceptionContinueExecution, the function will immediately stop its operation and return TRUE. Preliminarily, the function checks that the EXCEPTION_NONCONTINUABLE flag is not set, otherwise the function will raise an STATUS_NONCONTINUABLE_EXCEPTION exception.If the handler returned ExceptionNestedException, it means that another, incomplete search process of the exception handler was found in the search process, in the context of which a new exception occurred. In this case, the EstablisherFrame field of the DISPATCHER_CONTEXT structure will contain a pointer to the frame of the function whose exception exception handled the exception. As mentioned above, this value copies the exception handler of the RtlpExecuteHandlerForException function there. The RtlDispatchException function will set the EXCEPTION_NESTED_CALL flag for the ExceptionFlags field, and also update the frame pointer of the function whose handler threw the exception. This value will be updated only if the current pointer value is 0 (there were no nested exceptions), or the EstablisherFrame field of the DISPATCHER_CONTEXT structure contains a function frame pointer,which is stack higher than the function in the context of which a new exception occurred.If the handler returned ExceptionCollidedUnwind, this means that an active promotion was detected during the search process, in the context of which an exception occurred. This case will be described in more detail when describing stack promotion; here it is only necessary to indicate that in response to this result, the RtlDispatchException function will update the DISPATCHER_CONTEXT structure and the local CONTEXT structure so that the search for the handler will be resumed from where it was interrupted.In all other cases, the RtlDispatchException function throws a STATUS_INVALID_DISPOSITION exception.Conclusion
As mentioned in section 2, the whole process can be divided into two parts, and we have fully considered the first. In the next part of the article, the second part will be considered, which includes promotion of the stack and the principle of operation of try / except and try / finally blocks.