Exceptions on Windows x64. How it works. Part 2

    Based on the material described in the first part of this article, we continue the discussion of the topic of exception handling in Windows x64. And in this part, we will examine in detail those areas of the PE image that are involved in the process of handling exceptions. The described material requires knowledge of basic concepts, such as a prologue, an epilogue, a function frame and an understanding of basic processes, such as the actions of a prologue and an epilogue, the transfer of function parameters and the return of the result of a function. If the reader is not familiar with the above, then before reading it is recommended to familiarize yourself with the material from the first part of this article.

    The implementation of the mechanism, which is located in the exceptions folder of the git repository at this address , is attached to the article .

    1. .pdata section of the PE image


    The section contains a table of functions of the PE image and information about the promotion of frames of these functions. The operating system actively uses this table when searching for an exception handler. The size and location of the table are described in the optional header of the data directories (optional header data directories) of the PE image. The following sections describe the structures involved in the description of functions. The fields of these structures can store addresses that point to different areas of the image. All of these addresses, unless otherwise indicated, are addresses relative to the start of the image.

    You can find a more detailed description of the PE image in the Microsoft Portable Executable and Common Object File Format Specification document. This article will only provide information that is directly related to the topic under discussion.

    2. Function table


    The function table consists of elements of the RUNTIME_FUNCTION type, the definition of which is shown in Figure 1.


    Figure 1

    BeginAddress and EndAddress contain the addresses of the beginning and end of the function, respectively, and UnwindInfoAddress contains the address of the promotion information structure. BeginAddress contains the address of the first byte of the function, when EndAddress contains the address of the first byte immediately after the function. This structure can also describe not the whole function, but only its part (chunk). This situation will be discussed in more detail in Section 3.

    Elements of the table are sorted in ascending order, in accordance with the starting addresses of functions / parts of functions.

    3. Promotion information


    As mentioned earlier, the UnwindInfoAddress field of the RUNTIME_FUNCTION structure contains the address of the UNWIND_INFO structure, which is defined below in Figure 2.


    Figure 2

    The Version field, as the name implies, carries a version of the promotion information. Currently, the latest version is version 2.

    The SizeOfProlog field contains the prolog size in bytes.

    The FrameRegister field denotes the register number (register numbers will be listed below), which is used as the frame pointer, and the FrameOffset field contains a value in 16-byte blocks, which is added to the RSP value at the time the function frame pointer is set. In fact, the frame pointer will be set to RSP + FrameOffset * 16. The resulting offset value can be from 0 to 240. Using this intraframe offset allows you to increase the code density, because in this case, more short instructions can be used, which use an 8-bit signed value as the offset. For example, if FrameOffset is 0, then access to the first 128 bytes of frame data can be performed using short instructions, but there is no access to subsequent data, because An 8-bit signed value covers positive values ​​from 0 to 127. Now imagine that FrameOffset is 20, in which case access to the first 148 bytes of frame data can be performed with short instructions, because Now, as an offset, not only values ​​from 0 to 127, but also values ​​from -20 to 0 can be used. Figure 3 shows two examples of access to a frame at the same offset, but with a different FrameOffset value. Pay attention to the size of the instruction that accesses the frame in both cases. Figure 3 shows two examples of frame access at the same offset, but with a different FrameOffset value. Pay attention to the size of the instruction that accesses the frame in both cases. Figure 3 shows two examples of frame access at the same offset, but with a different FrameOffset value. Pay attention to the size of the instruction that accesses the frame in both cases.


    Figure 3

    If the FrameRegister is zero, then the frame pointer is not used, and the FrameOffset field does not carry any information. In this case, the frame indicator is RSP.

    The UnwindCode array describes the actions of the prolog. A detailed description of these codes is given in section 4. For alignment purposes, this array always has an even number of records. The CountOfCodes field contains the number of elements in this array. If this number is odd, then the last record in the array is not used, i.e., in fact, the number of records in the array is one more than indicated in the field. Array elements are sorted in reverse order, i.e. the first element will describe the last action of the prologue.

    The Flags field can contain three flags: UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER and UNW_FLAG_CHAININFO. The UNW_FLAG_EHANDLER flag indicates that the function has an exception handler that must be called during the search for the handler. The UNW_FLAG_UHANDLER flag indicates that the function has a termination handler that must be called during the promotion. The UNW_FLAG_CHAININFO flag means that the given UNWIND_INFO structure is not primary, but is a continuation (chained) of the previous UNWIND_INFO structure. Setting this flag excludes the setting of the UNW_FLAG_EHANDLER and UNW_FLAG_UHANDLER flags, and the FrameRegister and FrameOffset fields must be identical to the fields of the primary UNWIND_INFO structure.

    Immediately after the UNWIND_INFO structure, either the EXCEPTION_HANDLER structure or the RUNTIME_FUNCTION structure is located. The structure of EXCEPTION_HANDLER is defined in Figure 4.


    Figure 4

    If the Flags field contains the set UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER bit, or both, then the EXCEPTION_HANDLER structure follows the UNWIND_INFO structure. The ExceptionHandlerAddress field contains the address of the called handler, and the LanguageSpecificData field contains data specific to the corresponding programming language. The prototype of the handler function, as well as the type of the value returned by it, are presented below, in figure 5.


    Figure 5

    The ExceptionRecord parameter carries a pointer to a structure that describes the reason for the exception. The EstablisherFrame parameter carries a pointer to the frame whose handler was called. The ContextRecord parameter contains a pointer to a structure that contains the processor context at the time the exception occurs. In the process of searching for an exception handler or stack promotion, the contents of the structure can be changed by the handlers themselves. The result of these changes will be the target context of the processor, i.e. this context will be applied to the task in progress if its execution continues. The DispatcherContext parameter contains the current search context for the exception or stack promotion handler. The structures EXCEPTION_RECORD and DISPATHCER_CONTEXT and the process of finding a handler and promotion will be described in more detail in the next part of this article. The EXCEPTION_DISPOSITION type will also be discussed in the description of this process. The definition of EXCEPTION_RECORD, CONTEXT and DISPATHCER_CONTEXT structures can be found in winnt.h or in the implementation of this mechanism attached to the article (under the names SExceptionRecord, SContext and SDispatcherContext, respectively).

    Figure 6 shows an example of the structures RUNTIME_FUNCTION, UNWIND_INFO, and EXCEPTION_HANDLER that the compiler will generate for a particular function. The dashed line indicates the end of the function. The EXCEPTION_HANDLER structure is shown in the figure for completeness and, although it is not part of the UNWIND_INFO structure, the figure is presented as part of this structure, as if present, then immediately following the UNWIND_INFO structure, as already indicated. The addresses of the beginning of the image, the beginning of the function and the end of the function are absolute, all addresses in the generated structures are relative from the beginning of the image.


    Figure 6

    If the Flags field contains the UNW_FLAG_CHAININFO bit set, then the UNWIND_INFO structure is secondary (also called chained) and the RUNTIME_FUNCTION structure follows it. The UnwindInfoAddress field of the RUNTIME_FUNCTION structure contains the address of the previous UNWIND_INFO structure. The previous UNWIND_INFO structure, in turn, can also be secondary, but ultimately the UNWIND_INFO structure that does not have the UNW_FLAG_CHAININFO flag set will end up in this linked list. This will be a structure belonging to the prologue of the function entry point, which is also called the primary structure. The number of related structures can be up to 32 inclusive.

    Related structures are useful in two situations.

    The first situation: the compiler can perform optimization, as a result of which it can delay the persistence of some constant registers. Those. their saving will be performed not in the prologue of the function entry point, but in the function body. For such sections of code (in section 2, it was already mentioned that the RUNTIME_FUNCTION structure can not describe the entire function, but only its part, it was just about these sections of code), the compiler will generate a RUNTIME_FUNCTION structure that will point to the corresponding UNWIND_INFO structure, which will describe the storage these registers. In this case, the storage of these registers will be performed through a regular write to memory. Pushing onto the stack is not supported in this situation. As previously mentioned, this UNWIND_INFO structure is secondary, followed by the RUNTIME_FUNCTION structure,

    The second situation: it follows from the first, through the associated UNWIND_INFO structures, the size of promotion information can be reduced, because no need to duplicate an array of promotion codes from the primary and / or previous UNWIND_INFO structures.

    Figure 7 shows an example of the RUNTIME_FUNCTION and UNWIND_INFO structures that the compiler will generate for a function to which the previously described optimization is applied. The dashed line indicates the end of the function. The RUNTIME_FUNCTION structures that follow the UNWIND_INFO structures, although they are not part of these structures, are represented as part of them, because their presence depends on the fields of the UNWIND_INFO structures. The RUNTIME_FUNCTION structures, which are indicated separately in the figure, are elements of the function table and are sorted in the table as described in Section 2, in ascending order according to the starting addresses of the parts of this function. In the figure they are also presented in ascending order from bottom to top. Under each of them is the UNWIND_INFO structure to which they refer. The lowest RUNTIME_FUNCTION structure refers to the primary UNWIND_INFO structure. The rest refer to UNWIND_INFO structures, which are secondary. Therefore, after these UNWIND_INFO structures, RUNTIME_FUNCTION structures are located, which in their content completely repeat the lowest RUNTIME_FUNCTION structure and, thus, refer to the primary UNWIND_INFO structure. It is also seen from the example that the code generated by the compiler saves the RDI register not in the prologue of the function entry point, but in the function body. This part of the code is described by the middle RUNTIME_FUNCTION structure, and the corresponding UNWIND_INFO structure contains the corresponding entries typical for the prologue of this code section. The addresses of the beginning of the image, the beginning of the function and the end of the function are absolute,


    Figure 7

    If the Flags field does not contain any set bits, then no structures follow the UNWIND_INFO structure.

    4. Promotion codes


    The previous section mentioned the UnwindCode array of the UNWIND_INFO structure, which describes the actions of the prolog. Here we look at the structure of the elements of this array, which is shown in Figure 8, as well as each code that is used in the description of the prolog actions.


    Figure 8

    As can be seen from the figure, the content of the element, depending on the situation, is interpreted in one of three options.

    The upper structure is used to describe the action of the prologue.

    The CodeOffset field contains an offset from the beginning of the prologue to the instruction after the instruction that performs the described action.

    The OpCode field contains the code of the action being performed. Different actions take a different number of table entries, from 1 to 3. The first entry is always in the top structure format. The format and number of remaining entries depends on the action code (all codes will be described below). If there is only one additional record, then this record is interpreted as a FrameOffset field. If there are two additional records, then these records are also interpreted as a FrameOffset field, but the first record carries the lower 16 bits, and the second high 16 bits of the 32-bit value. The byte order in these records is direct (little endian). The purpose of these values ​​(in the case of one and two additional entries) is described in the corresponding action codes. If the OpCode field carries the UWOP_EPILOG code, then the structure has the format Epilogue structure. This situation will be described in detail later. It should be noted that this code is relevant only for the UNWIND_INFO version 2 structure.

    The OpInfo field depends on the value of the OpCode field. It may contain the number of the general register that is involved in the action, the XMM number of the register, or a numerical value whose purpose is described in the corresponding action codes. Options for interpreting the OpInfo field not listed here are described in the corresponding action codes. Figure 9 describes the mapping of the values ​​of the OpInfo field with general registers and XMM registers.


    Figure 9

    Some action codes contain an unsigned offset on the memory area inside the function frame. This offset is relative from the start of the function frame. If the function frame pointer is not used, then this is the offset relative to the RSP value. If the frame pointer is used, then this is the offset relative to the RSP value that was at the time the frame pointer was set. The value of this RSP will be equal to the Frame pointer - FrameOffset from the UNWIND_INFO structure * 16. All offsets inside the frame are multiples of 8 or 16, depending on the code of the action being performed. For UWOP_SAVE_NONVOL and UWOP_SAVE_NONVOL_FAR, the offsets are multiples of 8, because these codes store 8 byte registers. For UWOP_SAVE_XMM128 and UWOP_SAVE_XMM128_FAR, the offsets are multiples of 16, because these codes store 16 byte registers. As mentioned in section 1 in the first part of this article,

    All valid codes are described below. The description of the codes begins with their name, in parentheses are their respective numerical values ​​and the number of entries they occupy.

    UWOP_PUSH_NONVOL (0; 1 entry). Pushes a general-purpose register onto the stack, decreasing the RSP value by 8. The register number is indicated in the OpInfo field. As described in section 1 of the first part of this article, the prolog first performs exactly these actions, so these codes appear last in the UnwindCode array.

    UWOP_ALLOC_LARGE (1; 2 or 3 entries). Allocates a large area on the stack. This code has two forms. If OpInfo is 0, then the allocated size divided by 8 is stored in the next record, which allows you to allocate up to 512Kb - 8. If OpInfo is equal to 1, then the allocated size is stored in the next two entries, which allows you to allocate up to 4GB - 8.

    UWOP_ALLOC_SMALL ( 2; 1 entry). Allocates a small area on the stack. The allocated size is stored in the OpInfo field and calculated as follows - OpInfo * 8 + 8, which allows you to select from 8 to 128 bytes.

    The promotion codes that select an area on the stack always use the shortest form of encoding. If an area of ​​8 to 128 bytes is allocated, then UWOP_ALLOC_SMALL is used. If an area from 136 bytes to 512Kb - 8 is allocated, then UWOP_ALLOC_LARGE is used with the OpInfo field value equal to 0. If a region from 512KB to 4GB - 8 is allocated, then UWOP_ALLOC_LARGE is used with the OpInfo field value 1.

    UWOP_SET_FPREG (3; 1 record) . Sets the frame pointer. The OpInfo field is reserved and not used, and the process itself is described in detail in Section 3, when describing the FrameRegister field of the UNWIND_INFO structure.

    UWOP_SAVE_NONVOL (4; 2 entries). Stores a general-purpose register on the stack with instructions for writing to memory. The value is saved in the previously selected area. The number of the stored register is specified in OpInfo. The offset from the start of the frame divided by 8 is stored in the next record.

    UWOP_SAVE_NONVOL_FAR (5; 3 entries). Saves a general-purpose register on the stack, using a long offset, with memory write instructions. The value is saved in the previously selected area. The number of the stored register is specified in OpInfo. The offset from the start of the frame is stored in the following two entries.

    UWOP_EPILOG (6; 2 entries). For version 1 of the UNWIND_INFO structure, this code was called UWOP_SAVE_XMM and occupied 2 records, it retained the lower 64 bits of the XMM register, but was later removed and is now skipped. In practice, this code has never been used. For version 2 of the UNWIND_INFO structure, this code is called UWOP_EPILOG, takes 2 entries, and describes the function epilogue. A detailed description of this code will be given below.

    UWOP_SPARE_CODE (7; 3 entries). For version 1 of the UNWIND_INFO structure, this code was called UWOP_SAVE_XMM_FAR and occupied 3 records, it saved the lower 64 bits of the XMM register, but was later removed and is now skipped. In practice, this code has never been used. For version 2 of the UNWIND_INFO structure, this code is called UWOP_SPARE_CODE, takes 3 entries, and makes no sense.

    UWOP_SAVE_XMM128 (8; 2 entries). Saves all 128 bits of the XMM register on the stack. The number of the stored register is specified in OpInfo. The offset from the start of the frame divided by 16 is stored in the next record.

    UWOP_SAVE_XMM128_FAR (9; 3 entries). Saves all 128 bits of the XMM register on the stack. The number of the stored register is specified in OpInfo. The offset from the start of the frame is stored in the following two entries.

    UWOP_PUSH_MACHFRAME (10; 1 entry). Pushes the machine frame. A record is used to indicate the action of a hardware interrupt or exception. This code has two forms. If OpInfo is 0, then this means that the processor pushed the following registers sequentially onto the stack: SS, old RSP, EFLAGS, CS, RIP. If OpInfo is 1, then this means that the processor pushed the same registers onto the stack as if OpInfo was 0, but before pushing them pushed the error code onto the stack. Each of the values ​​after pushing is located at an address multiple of 8. If the value is less than 8 bytes, then the older unused bytes are reset. If this code is used, then in the UnwindCode array it appears the very last. If OpInfo is 0, then the RSP value decreases by 40, otherwise by 48. Figure 10 shows both cases,


    Figure 10

    Figure 11 shows an example of the UnwindCode array for the prolog shown in Figure 1 in the first part of this article.


    Figure 11

    Figure 12 shows an example of the UnwindCode array for the prolog shown in Figure 3 in the first part of this article. It should be noted that for the UWOP_SET_FPREG code, all the necessary information is in the FrameRegister and FrameOffset fields of the UNWIND_INFO structure.


    Figure 12

    Now we will consider UWOP_EPILOG in more detail.

    As noted earlier, this record is present only in the UNWIND_INFO version 2 structure. The record has the format of the Epilogue structure, which is shown in Figure 8. This code describes the location of the function epilogue. This allows you to determine whether the processor executed the epilogue code at the time of the interrupt / exception occurrence, not according to the program code, as described in section 1 in the first part of this article, but from the UnwindCode array.

    A function may have several epilogues, therefore, each epilogue will have one UWOP_EPILOG entry. If the UWOP_EPILOG code is used, then in the UnwindCode array this entry appears first, followed by at least one more UWOP_EPILOG entry. The first UWOP_EPILOG entry, in the OffsetLowOrSize field, describes the size of the epilogue. If bit 0 of the OffsetHighOrFlags field of the first UWOP_EPILOG record is set, then the OffsetLowOrSize field is not only the size, but also the offset to the function epilogue, which can only be when the epilogue is at the end of the function / part of the function. The offset by the epilogue in these entries is the opposite, i.e. it is not added to the start address of the function / part of the function, but is subtracted from the end address of the function / part of the function in order to calculate the start address of the epilogue. As already noted in section 2, addresses of the beginning and end of the function / part of the function are contained in the BeginAddress and EndAddress fields of the RUNTIME_FUNCTION structure. If bit 0 of the OffsetHighOrFlags field of the first UWOP_EPILOG record is not set, then this record is followed by the next UWOP_EPILOG record, whose OffsetLowOrSize and OffsetHighOrFlags fields contain the lower 8 bits and the highest 4 offset bits, respectively. As already noted, each function epilogue has an additional UWOP_EPILOG record, in this case, as in the previous one, the OffsetLowOrSize and OffsetHighOrFlags fields form a 12-bit offset from the end of the function / part of the function. the OffsetLowOrSize and OffsetHighOrFlags fields which contain the lower 8 bits and the upper 4 offset bits, respectively. As already noted, each function epilogue has an additional UWOP_EPILOG record, in this case, as in the previous one, the OffsetLowOrSize and OffsetHighOrFlags fields form a 12-bit offset from the end of the function / part of the function. the OffsetLowOrSize and OffsetHighOrFlags fields which contain the lower 8 bits and the upper 4 offset bits, respectively. As already noted, each function epilogue has an additional UWOP_EPILOG record, in this case, as in the previous one, the OffsetLowOrSize and OffsetHighOrFlags fields form a 12-bit offset from the end of the function / part of the function.

    Because The UWOP_SAVE_XMM code takes up two records, the number of records that UWOP_EPILOG takes is always even, and the last record may not be used. If this is the case, then the OffsetLowOrSize and OffsetHighOrFlags fields of the UWOP_EPILOG record are 0.

    Figure 13 shows an example of an UnwindCode array for a function that has one epilogue located at the end of the function. The addresses of the beginning and end of the function are absolute.


    Figure 13

    As reflected in Figure 13, despite the fact that the function allocates a region in the prolog on the stack, the beginning of the epilogue is considered to be the place where the pushing of general registers from the stack begins, and not the freeing of memory from the stack. This will be explained in the next part of this article.

    This is embedded in the concept of UWOP_EPILOG codes. An epilogue can consist of instructions for pushing general-purpose registers, followed by instructions that increase RSP by 8 (as was already noted in section 1 of the first part of this article), and return instructions.

    Figure 14 shows an example of an UnwindCode array for a function that has three epilogues. The addresses of the beginning and end of the function are absolute.


    Figure 14

    Conclusion


    In this part of the article we have finished considering the necessary theoretical material. In the next part of the article, we will discuss auxiliary functions and structures that are introduced into the operating system to simplify working with PE image structures. Then we look at the process of raising and handling the exception, as well as how these helper functions are used in this process.

    Also popular now: