📜 ⬆️ ⬇️

Exceptions in the UEFI application

Any programmer who is familiar with UEFI is aware that the built-in exception handling mechanism is not there. These are try / except blocks, which are extensions of Microsoft C / C ++ compilers. It can be very useful to have such a mechanism and fully use the advantages that it provides. Therefore, in this article we will focus on solving this problem. Also attached to the article is the full implementation of the mechanism with its demonstration based on the UEFI application. Only 64-bit processors from Intel are affected, and only they are implied in the discussion. The implementation of the mechanism is located in the exceptions folder of the git repository at: https://github.com/anatolymik/machineries.git .

First, let's talk a little about how exceptions are generally handled. First of all, the exception is a special situation. As a rule, an exception occurs because the processor cannot execute a specific instruction. When such an event occurs, the processor calls an exception handler whose entry point is in the IDT table. There are many exceptions to the processor, therefore, the corresponding handler is called according to its type.


As can be seen from the figure above, exception handlers are only 256. Moreover, the first 32 are reserved for exceptions. The rest are used to handle interrupts, which we will not discuss here. As already mentioned, the corresponding handler is called according to the type of exception. For example, when dividing a number by 0, an exception handler of 0 is called, or if the processor encounters an unfamiliar instruction in the stream, an exception handler 6 will be called. For more information, see the “Intel 64 and IA-32 Architects Software Developer's Manual”. We have just touched on the processor hardware, without which hardware error handling is impossible, but that's not all.

Speaking of try / except blocks, it should be mentioned that when the compiler encounters them, it generates meta information that describes the location of each block in the executable file. This information is located in a separate place, more precisely, in the .pdata section of the PE image.
')

As can be seen from the figure, the code, data and various information about the executable file (for example, as in our case, the descriptors of try / except blocks) are arranged in a single image. All these data are divided into so-called sections, in accordance with the type of data itself. Returning to the discussion of the .pdata section, it should be mentioned that for each try / except block, besides its location, the address of its handler is also stored. Typically, this is code enclosed in the except and finally blocks. More information about the PE file format can be found in the Microsoft Portable Executable and Common Object File Format Specification. We have just touched upon the software necessary for the functioning of the mechanism in question, now let's look at all this once again in a comprehensive manner.

Despite the fact that try / except blocks are processed by the compiler in the process of generating code, and in general they are the mechanism of the language itself, their operation requires support from the operating system. Moreover, it should be noted that part of the processing is performed by the operating system, and part is performed by the executable file itself. There is also some part that is executed by the executable file itself, but is not generated by the compiler, and instead is required to be implemented. For example, the reserved name __c_specific_handler is the name of the function that is responsible for handling the exception. In the absence of its implementation, the linker will not be able to generate the executable file. In the Windows development tools, the implementations of these functions are included in the libraries that are linked with the application.


As shown in the figure above, when an exception occurs, the processor calls the appropriate handler from the IDT table. Exception handlers are set by the operating system during its initialization, i.e. at the time the exception occurs, the operating system itself receives control. Handlers of all exceptions are very similar, they retain all the necessary information for processing and information unique to a specific type of exception. Then all these handlers call the search and call handler function. The function scans the .pdata section in order to find a handler corresponding to a specific section of the code in which the exception occurred. And if the handler is found, the operating system passes control to it, otherwise the application fails. We have just reviewed the process of the occurrence and handling of exceptions, so we can proceed to discuss what needs to be done in the UEFI application in order for exceptions to function fully. Also, it should be noted that the description given is very simplified.

Since there is nothing in UEFI that has anything to do with exceptions, it is obvious that all of the above should be implemented. Further in the process of listing, in order to facilitate the study of source codes from the implementation attached to the article, we will also refer to its functions, files and folders. First of all, you need to implement exception handlers. They are located in the exccpu.asm file from the exc folder. You also need to set their addresses in the IDT, this is performed by the CpuInitializeExceptionHandlers function, located in the exccpu.cpp file from the exc folder. Almost all of these handlers call the CpuDispatchException function from the exccpu.cpp file, which in fact is the starting point for finding and calling the exception handler. The DispatchException and UnwindStack functions, which are located in the excdsptch.cpp file from the exc folder, are responsible for finding and calling exception handlers. To scan the .pdata section, you need the implementation of the PE image processing functions. These functions are used by the previously mentioned functions for searching and calling exception handlers. The implementation of the PE image scanning functions is grouped into a separate folder - pe. Finally, implementations of the reserved functions __C_specific_handler and _local_unwind are needed. Both functions are implemented in the excchandler.cpp file from the exc folder. The entry point to the application is the EfiMain function, which is located in the efi.cpp file from the project root. It should be noted that setting handler addresses in IDT is simplified for demonstration purposes. If we talk about the full implementation, you will need an isolated IDT.

Perhaps that's all. One has only to add that the description of this mechanism has been repeatedly covered on the Internet in a variety of details. And this article is unique, rather the attached implementation and demo. Although, initially when the need arose for the implementation of this mechanism for projects operating outside the Windows environment, the previously mentioned descriptions were not enough. Including it was necessary to reverse Windows, and the description of UNWIND_INFO version 2 could not be found at all. Therefore, initially the article was conceived in a different format, in a very detailed one, after which there would be no questions left. In practice, its writing has degenerated into writing documentation, and not an article, which can be boring to read, because From the very beginning it is unclear what problem is being solved. Therefore, it was written just such a simple and easy option, without any details. A detailed version is planned to publish in parts.

Source: https://habr.com/ru/post/320948/


All Articles