Exceptions on Windows x64. How it works. Part 1
The implementation of the mechanism is in the exceptions folder of the git repository at this address .
1. Function, its prologue, body, epilogue and function frame
Any function has a prologue, body and epilogue. Let us dwell on the prologue and epilogue, because no questions arise with the body itself, since it is for its sake that everything is conceived.
In the prologue of the function is the code that performs the preliminary actions that are necessary before the function body works. These include saving general purpose registers, the values of which could be set by the calling function, allocating memory on the stack for local function variables, setting a frame pointer, and saving the processor's XMM registers. The prologue has strict rules regarding the actions that it can perform and their sequences. First, if required, the prolog saves the first 4 parameters in the area of register parameters (more on this area and everything related to it, it will be written in section 3), then general registers are pushed, memory is allocated on the stack, and optionally, a frame pointer is set functions and stored XMM processor registers. Any of the listed actions may be absent, but the described order of execution is strictly followed. Such strict rules allow you to analyze the actions of the epilogue by its program code, which will be discussed in more detail below. Figure 1 illustrates the prologue of a function that saves the first 4 passed parameters, saves three general-purpose registers, allocates memory, and saves the XMM register.

Figure 1
It should also be noted that general-purpose registers can be saved even after allocating memory on the stack (as well as after setting up a function frame, if any), but in this case it is performed not by pushing, but by ordinary writing to memory as shown in the previous example. It should also be noted that in very rare cases, 8 bytes can be allocated on the stack before pushing general purpose registers in the function prolog. Such a prologue is most typical for exception handlers, for which the processor does not push the error code onto the stack, and such an 8-byte allocation allows it to be simulated.
The saved general purpose registers, the allocated memory on the stack, and the saved XMM registers collectively form the so-called frame of the function that every function called has. Below, in figure 2, is a stack consisting of three frames. The first frame is the frame of the function in the context of which the exception occurred. For brevity, the figure shows only the area of the frame that is pushed by the processor at the time of exclusion. The second frame is an exception handler frame, which consists of an empty error code (let the exception in this example be caused by division by zero, which does not push the error code onto the stack, and our handler, like the Windows handler, generates an empty code for uniformity), saved registers RAX, RCX, RDX, R8, R9, R10, R11, stored registers XMM0, XMM1, XMM2, XMM3, XMM4, XMM5 and return address. These general-purpose saved registers and XMM registers are listed for a reason, we will talk about this in section 3. The third frame is the frame of the function that the exception handler called. Its frame consists of the stored registers RBP, RBX, XMM and the allocated space for local function variables. The arrow indicates the direction of stack growth.

Figure 2
A function may have a frame pointer. In this case, access to the frame is through this pointer. This is primarily necessary in cases where during the execution of the function the allocated space in the stack can be dynamically changed (i.e., the allocation of memory in the stack is additionally performed in the function body, and not in the prolog). And since this entails a change in the stack pointer, it will not point to a function frame. If the function does not have a frame pointer, it cannot dynamically allocate memory on the stack, therefore, the stack pointer is static and is also a frame pointer of the function. Figure 3 illustrates such a prologue. After saving all the registers and allocating memory, a function is called in the function body, which in RAX returns the size of the structure,

Figure 3
If the prolog allocates an area on the stack larger than one page (i.e., more than 4Kb), then it is likely that such a selection will cover more than one virtual page of memory and, therefore, such a selection should be checked before actually executing it. For this purpose, the prologue of a function calls a special function that performs this check. The name of the function is _chkstk. Also, this function does not change the values of the registers in which the parameters are transmitted (these registers will be described in detail in Section 3). Figure 4 shows an example of a function prolog that allocates 4K of memory on the stack.

Figure 4 The
epilogue performs actions opposite to the prologue: restores the XMM registers and general purpose registers that were saved after the memory was allocated on the stack, frees the memory on the stack (and if the frame pointer was used, it is dynamically allocated as well), pushes the registers general purpose, returns to the calling function or transfers control to the beginning of the current function, or another function. Figure 5 shows the epilogue corresponding to the prologue from the example in Figure 1. It can be seen from the figure that actions are performed that are opposite to the actions of the prologue. Also pay attention to the fact that the transferred parameters are not restored; you will find an explanation for this in section 3.

Figure 5
The epilogue, like the prologue, has strict rules regarding the processor instructions used. If the function did not use the frame pointer, then the memory on the stack, as reflected in the previous example, is freed by add rsp, the instruction constant, and if used, by lea rsp, [frame pointer + constant]. This is followed by instructions for pushing general-purpose registers from the stack, a return instruction, or an instruction to unconditionally switch to another function or to the beginning of the current function. Figure 6 shows the epilogue corresponding to the prologue from the example in Figure 3. Note that instead of the ret statement, jmp is used to call another function.

Figure 6
As for transition instructions, only a limited set of them is allowed. Despite the fact that the epilogue first restores the XMM registers and general-purpose registers, the beginning of the epilogue, when untwisted, is considered to be freeing the memory from the stack via add rsp, constant or lea rsp, [frame pointer + constant] instructions. The explanation for this will be given in the third part of this article, and the first information about promotion will be given in the next part of this article.
All of the above regarding the epilogue is true for functions whose version of the UNWIND_INFO structure is 1 (we will write more about UNWIND_INFO in the next part of this article). Whether the processor performed the epilogue of the function at the time of interruption / exception is determined by the code of the function itself. This is possible because, as has been repeatedly noted, the prologue and epilogue are superimposed on a strict procedure, and the epilogue is also limited by the processor instructions used by it. UNWIND_INFO version 2 structures can also describe the location of the function epilogue. We’ll talk about this in more detail in the next part of this article, it’s worth mentioning only that the epilogues of functions that are described by the UNWIND_INFO version 2 structures can free 8 bytes from the stack after pushing general registers, which we already spoke about during the discussion of the prologue. The same release of 8 bytes from the stack after pushing general-purpose registers is not expected from function epilogues that are described by UNWIND_INFO version 1 structures. Therefore, in existing Windows implementations, checking for the existence of this release in the program epilogue code of functions that are described by UNWIND_INFO version 1 structures is not performed. In the implementation of this mechanism attached to the article, such a check is also not performed. which are described by UNWIND_INFO version 1 structures is not executed. In the implementation of this mechanism attached to the article, such a check is also not performed. which are described by UNWIND_INFO version 1 structures is not executed. In the implementation of this mechanism attached to the article, such a check is also not performed.
At a minimum, a function has one epilogue.
2. Types of functions
There are two types of functions: frame functions and simple leaf functions.
Personnel functions are those that have their own frame on the stack and they do not have any restrictions on the part of their actions. They can call other functions, allocate memory on the stack, save and use any processor registers. If a function does not call other functions, then its stack has no restrictions in alignment; if it calls, then the stack must be aligned on a 16-byte boundary. Also, the personnel function has corresponding records for the promotion of its frame (we will talk about this in more detail in the next part of this article).
Simple functions are those functions that do not have their own frame on the stack, so they cannot do everything that frame functions can, including using any processor registers. Since a simple function cannot call other functions, it does not align its stack. Also, a simple function has no promotion records, as she has no frame.
3. Call Agreement
The first 4 parameters are passed to the function through the registers. If there are more, then the rest are passed through the stack. Also, the calling function for the first 4 parameters allocates an area on the stack called the register parameters area or home location. The called function can use this area to save the parameters, as the prologue in Figure 1 did, or for any other purpose. Even if the function accepts less than 4 parameters or does not accept them at all, the area of register parameters is always allocated on the stack. The parameters passed through the stack are located in an area called the stack parameters area. This region, unlike the region of register parameters, may be absent, and its size is equal to the size of all parameters that it includes. One parameter in the field of register and stack parameters always takes 8 bytes. If the parameter is larger than 8, then a pointer to it is passed instead. If the parameter size is less than 8 bytes, then the highest unused bytes in the corresponding areas are ignored. Figure 7 below shows the calls of two functions, one of which takes 6 parameters, and the other 1, to the left and right of the arrow of the direction of growth of the stack, respectively.

Figure 7
At the bottom of the stack is always the area of register parameters, above which the area of stack parameters follows. If the function is called, the return address will be located immediately below the register parameter area. In Section 2, it was mentioned that if a function calls other functions, then its stack should be aligned on a 16-byte boundary. On this 16-byte boundary, the register parameter area always starts.
The first 4 parameters are passed through the registers RCX, RDX, R8 and R9, if it is an integer or user type, the size of which is 1, 2, 4 or 8 bytes. Otherwise, a pointer to the corresponding parameter is passed. For strings and arrays, their pointer is always passed. If the parameter is a floating-point number, then XMM0, XMM1, XMM2, XMM3 registers are used for its transfer, provided that the parameter size does not exceed 8 bytes, otherwise a pointer to it is passed. If a pointer to a parameter is passed instead of the parameter itself, then the parameter itself is placed in temporary memory on a 16-byte boundary. Figure 8 shows examples of passing parameters to functions.

Figure 8
When XMM is used to transfer a parameter, the XMM register is used, which by number corresponds to one of the registers RCX, RDX, R8 or R9. For example, in Figure 8, parameter 3 of func1 carries a floating-point number, in which case the XMM2 register will be used. If this parameter were an integer, as in the func2 function, then the register R8 would be used.
The function returns the result via RAX or XMM0. Floating-point numbers and vectors up to 16 bytes (e.g. _m128) are returned in XMM0. Integers and user types that are 1, 2, 4, or 8 bytes in size are returned in RAX. If the return value is less than 8 bytes, then the most significant unused bytes are not defined. In all other cases, the first parameter of the function is a pointer to the area where the value is returned, and in RAX this pointer is returned. It should also be noted that in this case, the transmitted parameters are shifted by one parameter to the right, i.e. the first parameter will be transferred not to RCX, but to the RDX register, and the 4th parameter will be transferred not to R9, but on the stack. Figure 9 shows examples of returning a result.

Figure 9 The
C ++ compiler imposes additional restrictions on user types. If the result is returned by a non-static function (which is a member of a class, structure, etc.), or the type itself has a constructor, destructor, assignment operator, private or protected non-static members, non-static members of the link type, inherited parent, virtual functions or members, containing any of the above, the result is returned not to RAX, but to the memory area, the pointer to which was passed in the first parameter.
Registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14 and R15 are considered permanent (nonvolatile or callee-saved), i.e. the called function must save them before use and restore them before returning, and the calling function can rely on the values of these registers after calling the functions.
The registers RAX, RCX, RDX, R8, R9, R10 and R11 are considered volatile (volatile or caller-saved), i.e. the called function should not save them before use, and the calling function should not rely on the values of these registers after calling the functions. For this reason, the epilogue shown in Figure 5, corresponding to the prologue from the example in Figure 1, does not restore the registers RCX, RDX, R8, R9 saved by the prologue. And for the same reason, the exception handler mentioned in section 1 saves only them, because these registers are not restored by the called functions before returning.
Like general purpose registers, registers XMM0 - XMM5 are considered unstable, and registers XMM6 - XMM15 are constant.
Conclusion
In this part of the article, we examined the basic concepts, definitions and processes that, at first glance, although they are not directly related to the topic under discussion, but, nevertheless, knowledge and understanding of which is necessary for consideration of the subsequent material, since this is the main one upon which the discussed mechanism is built. The continuation of the article will be devoted to the description of those areas of the PE image that are involved in the process of handling exceptions.