Developers Club geek daily blog

1 year, 4 months ago
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications In last article about SecureBoot I very much lacked an opportunity to make the screenshot at the UEFI setup through BIOS Setup, but then helped out the redirection of the text console in a serial port. This excellent solution, but is available it on the few server motherboards, and through it it is possible to receive only character graphics, and it would be desirable to receive this — it and looks more pleasant, and it is not necessary to cut its every time from a terminal window.
That's it we will also be engaged in it in this article, and at the same time I will tell what is the DXE driver and how to write, collect and test such independently as keyboard input and an output to the screen in UEFI work how to find it on which it is possible to write files among attached devices of storage how to save something in the file from UEFI and how to adapt some external code on With for work as a part of a firmware.
If it is still interesting to you — I wait for you under a cat.


Disclaimer


Before speaking about writing and debugging of drivers for UEFI, It is necessary to tell at once that experiments with a firmware — business dangerous, they can lead to "brick", and in the most unsuccessful exceptional cases — to failure of the equipment therefore I warn in advance: everything that you will read here, you use at own risk, I do not bear and I will not bear responsibility for loss of operability of your firmware or payment. Before beginning any experiments with a firmware, it is necessary to make the complete copy of all contents of SPI flash by means of a programmator. Only this way you can guarantee successful recovery of a firmware after any program failure.
If you have no programmator, but to try to write and debug the DXE driver there is a strong wish, use for this OVMF, VmWare Workstation 12 or any other systems of virtualization with support of UEFI at your choice.

What there is necessary and why it is the DXE driver


Our task consists in removing a screenshot from all screen during operating time of some UEFI-application, for example BIOS Setup, clicking of a certain key shortcut, to find file system with access to record and to save the received screenshot on it. Also it would be quite good to receive some indication of the status. Since for removal of a screenshot it will be required to interrupt work of UEFI-applications, the program for their removal by the application cannot be, no preemptive multitasking is still provided in UEFI therefore we need the DXE driver.
The scheme of its work is planned approximately following:
0. We are loaded only after emergence of text input (to process combination key strokes) and a graphic output (that was from what to remove screenshots).
1. We hang up the processor of clicking of a combination of LCtrl + LAlt + F12 (or any other on your taste) on all available input text consoles.
2. We find all output graphic consoles in the processor, we do from them a screenshot and we will recode it in the PNG format (since UEFI-applications usually do not use millions of flowers, in this format screenshots turn out tens kilobyte in size instead of several megabytes in BMP).
3. We find the first FS with a recording capability in a root in the same processor and we save the received files there.
It is possible to expand functionality with the choice not of the first FS, and, for example, only USB devices or only the sections ESP, we will leave it for independent work to the reader.

We select SDK


For writing of a new code for work in UEFI there are two different SDK — newer EDK2 from UEFI Forum and GNU-EFI from independent developers based on the old Intel code. Both solutions mean that you will write a code on C and/or the assembler, in our case we will try to cost pure C.
Not to me to judge what SDK is better, but I suggest to use EDK2 since it official and cross-platform, and new features (together with correction of old bugs) appear in it much quicker thanks to proximity to a source of changes, plus it is used all known to me by IBV for writing of the code.
EDK2 is in process of permanent development, and steadily add 2-3 kommit a day, but as we do not pursue the latest trends here (all the same they for anybody do not work) therefore we will use a stable cut of EDK2 last at the moment which is called UDK2015 to its trunk.
To provide a krossplatformennost and a possibility of assembly with different compilers, EDK2 generates make-files for each platform, using the configuration files TXT (an environment configuration), DEC, DSC and FDF (a packet configuration) and INF (a component configuration), in more detail I will tell about them on the narration course, and now it is necessary to get EDK2 and to collect HelloWorld, than and we will be engaged if you cannot wait to receive particulars right now — proceed in documentation.

We configure an assembly environment


It is meant what necessary for assembly of a code on C and the software assembler is already set by your machine. If is not present, to users I suggest to install Windows the Visual Studio 2013 Express for Windows Desktop, GCC 4.4-4.9 and NASM will be necessary for users of Linux and OSX.
If all this is already set, it was necessary only to download UDK2015, to unpack all contents of UDK2015.MyWorkSpace.zip there where you have a right to creation of files (yes though directly on a desktop or in a house directory), and then to unpack contents of BaseTools (Windows) .zip or BaseTools(Unix.zip) in the directory of MyWorkSpace which turned out on the previous step which then to rename into something decent, for example into UDK2015.
Now we open the terminal, we pass into just created directory of UDK2015 and we execute edksetup.bat script there (or .sh) which will copy a set of text files in Conf subdirectory, will interest us tools_def.txt and target.txt.
The first file rather big, in it there are determinations of variables of an environment with ways to C and ASL compilers, assemblers necessary for an assembly environment, linkovshchik, etc. If it is necessary for you, you can correct the ways specified there or add the set of utilities (so-called ToolChain), but if you followed my advice, then without changes will suit you or VS2013 (if you have 32-bit Windows), or VS2013x86 (in case of 64-bit Windows), or GCC44 | … | GCC49 (depending on your GCC version which that kindly shows in response to gcc - version).
The second file contains assembly settings by default, in it I recommend to set the following values:
ACTIVE_PLATFROM = MdeModulePkg/MdeModulePkg.dsc # Основной пакет для разработки модулей
TARGET = RELEASE  # Релизная конфигурация
TARGET_ARCH = X64 # DXE на большинстве современным машин 64-битная, исключения очень редки и очень болезненны
TOOL_CHAN_TAG = VS2013x86 # | VS2013 | GCC44 | ... | GCC49 | YOUR_FANCY_TOOLCHAIN, выберите наиболее подходящий в вашем случае
Open one more terminal in UDK2015 and in Linux/OSX execute command:
. edksetup.sh BaseTools
In case of Windows of rather normal edksetup.bat without parameters.
Now we will test an assembly environment the build command if everything was made truly, then after certain time on will end with the message it seems
- Done -
Build end time: ...
Build total time: ...
If instead of Done you see Failed, something means with your settings not so. I checked the above for VS2013x86 in Windows and GCC48 in Xubuntu 14.04.3 — UMVR.

Structure of the project


Applications and drivers in EDK2 gather not separately, and in structure t.n Package, i.e. a packet. The packet, except applications, includes also libraries, sets of heading files and files with the description of a configuration of a packet and its contents. It is made to allow different drivers and applications to use different implementations of libraries, to have access to different heading files and GUID'AM. We will use MdeModulePkg, it is very general packet without any dependences on architecture and iron and if our driver manages to be assembled in it, it will almost work with guarantee at any implementations of UEFI 2.1 and newer. Minus of such approach is that the most part of libraries in it (for example, DebugLib used for receipt of a debug output) — just stubs, and it is necessary to write them most if there is such need.
Assembly of our driver requires the INF file with information on what libraries, protocols and files are necessary to it for assembly, and also adding of a way to this INF file to the DSC file of a packet that the assembly system in general knew that such INF file is.
Let's begin with the end: we open the UDK2015/MdeModulePkg/MdeModulePkg.dsc file and we glance over it to the section [Components] (it is possible to find it search is quicker). All files belonging to a packet are listed in the section one after another the beginning of the section here so looks:
[Components]
  MdeModulePkg/Application/HelloWorld/HelloWorld.inf
  MdeModulePkg/Application/MemoryProfileInfo/MemoryProfileInfo.inf
  ...
We add the future INF file together with way to it concerning UDK2015 there. I suggest to create for it directly in MdeModulePkg the CrScreenshotDxe folder, and to call the INF file CrScreenshotDxe.inf. As you already guessed, Cr is from "CodeRush", and the author of this article — modesty. As a result something like that will turn out:
[Components]
  MdeModulePkg/CrScreenshotDxe/CrScreenshotDxe.inf
  MdeModulePkg/Application/HelloWorld/HelloWorld.inf
  MdeModulePkg/Application/MemoryProfileInfo/MemoryProfileInfo.inf
  ...
We save changes and we close the DSC file, more we will not change it if we do not want to configure a debug output, but it already another story altogether.
Now it is necessary to fill in the INF file:
It will look approximately so
[Defines]                                               # Основные определения
  INF_VERSION    = 0x00010005                           # Версия спецификации, нам достаточно 1.5
  BASE_NAME      = CrScreenshotDxe                      # Название компонента
  FILE_GUID      = cab058df-e938-4f85-8978-1f7e6aabdb96 # GUID компонента
  MODULE_TYPE    = DXE_DRIVER                           # Тип компонента
  VERSION_STRING = 1.0                                  # Версия компонента
  ENTRY_POINT    = CrScreenshotDxeEntry                 # Имя точки входа

[Sources.common]                     # Файлы для сборки, common - общие для всех арзитектур 
  CrScreenshotDxe.c                  # Код нашего драйвера
  #...                               # Может быть, нам понадобится что-то еще, конвертер в PNG, к примеру

[Packages]                           # Используемые пакеты 
  MdePkg/MdePkg.dec                  # Основной пакет, без него не обходится ни один компонент UEFI
  MdeModulePkg/MdeModulePkg.dec      # Второй основной пакет, нужный драйверам и приложениям

[LibraryClasses]                     # Используемые библиотеки
  UefiBootServicesTableLib           # Удобный доступ к UEFI Boot Services через указатель gBS
  UefiRuntimeServicesTableLib        # Не менее удобный доступ к UEFI Runtime services через указатель gRT
  UefiDriverEntryPoint               # Точка входа в UEFI-драйвер, без нее конструкторы библиотек не сработают, а они нужны
  DebugLib                           # Для макроса DEBUG
  PrintLib                           # Для UnicodeSPrint, местного аналога snprintf

[Protocols]                          # Используемые протоколы
  gEfiGraphicsOutputProtocolGuid     # Доступ к графической консоли
  gEfiSimpleTextInputExProtocolGuid  # Доступ к текстовому вводу
  gEfiSimpleFileSystemProtocolGuid   # Доступ к файловым системам

[Depex]                              # Зависимости драйвера, пока эти протоколы недоступны, драйвер не запустится
  gEfiGraphicsOutputProtocolGuid AND # Доступ к ФС для запуска не обязателен, потом проверим его наличие в рантайме
  gEfiSimpleTextInputExProtocolGuid  # 
It was necessary to create the CrScreenshotDxe.c file mentioned above:
With here such contents
#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/PrintLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleTextInEx.h>
#include <Protocol/SimpleFileSystem.h>

EFI_STATUS
EFIAPI
CrScreenshotDxeEntry (
    IN EFI_HANDLE         ImageHandle,
    IN EFI_SYSTEM_TABLE   *SystemTable
    )
{
    return EFI_SUCCESS;
}
If now to repeat the build command, it has to be successful, otherwise you made something incorrectly.
Now we, at last, have a procurement for our driver, and it is possible to pass directly to writing of a code. It is absolutely clear that such assembly system is no good and to work with it through editing text files not really pleasantly therefore each of IBV has own solution on integration of the assembly EDK2 system into some modern IDE, for example the AMI Visual eBIOS environment is the such Eclipse which is given short weight by plug-ins, and Phoenix and Insyde give short weight them the Visual Studio.
There is still a remarkable VisualUefi project behind authorship of the famous specialist in a computer security Alex Ionesku and if you love the Visual Studio too — suggest to try it, and we will continue to get poisoned by fumes on a hardcore so far, to support spirit of old school and so on.

We react to combination key stroke


Here everything is rather simple: when loading the driver we will touch all copies of the SimpleTextInputEx protocol which is published by the driver of the keyboard and most often exactly one, even in a case when several keyboards — the buffer that the general are connected to system if specially not to change something. Nevertheless, just in case we will touch all available copies, having caused in everyone the RegisterKeyNotify function which
as parameter accepts a key shortcut to which we intend to react, and the pointer on callback-function which is caused after clicking it will be necessary for a combination, and in it already and all main work will be carried out.
We translate from Russian on With
EFI_STATUS
EFIAPI
CrScreenshotDxeEntry (
    IN EFI_HANDLE         ImageHandle,
    IN EFI_SYSTEM_TABLE   *SystemTable
    )
{
    EFI_STATUS   Status;
    EFI_KEY_DATA KeyStroke;
    UINTN        HandleCount;
    EFI_HANDLE   *HandleBuffer = NULL;
    UINTN        i;
    
    // Set keystroke to be LCtrl+LAlt+F12
    KeyStroke.Key.ScanCode = SCAN_F12;
    KeyStroke.Key.UnicodeChar = 0;
    KeyStroke.KeyState.KeyShiftState = EFI_SHIFT_STATE_VALID | EFI_LEFT_CONTROL_PRESSED | EFI_LEFT_ALT_PRESSED;
    KeyStroke.KeyState.KeyToggleState = 0;
    
    // Locate all SimpleTextInEx protocols
    Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiSimpleTextInputExProtocolGuid;, NULL, &HandleCount;, &HandleBuffer;);
    if (EFI_ERROR (Status)) {
        DEBUG((-1, "CrScreenshotDxeEntry: gBS->LocateHandleBuffer returned %r\n", Status));
        return EFI_UNSUPPORTED;
    }
    
    // For each instance
    for (i = 0; i < HandleCount; i++) {
        EFI_HANDLE Handle;
        EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleTextInEx;
        
        // Get protocol handle
        Status = gBS->HandleProtocol (HandleBuffer[i], &gEfiSimpleTextInputExProtocolGuid;, (VOID **) &SimpleTextInEx;);
        if (EFI_ERROR (Status)) {
           DEBUG((-1, "CrScreenshotDxeEntry: gBS->HandleProtocol[%d] returned %r\n", i, Status));
           continue;
        }
        
        // Register key notification function
        Status = SimpleTextInEx->RegisterKeyNotify(
                SimpleTextInEx, 
                &KeyStroke;, 
                TakeScreenshot, 
                &Handle;);
        if (EFI_ERROR (Status)) {
            DEBUG((-1, "CrScreenshotDxeEntry: SimpleTextInEx->RegisterKeyNotify[%d] returned %r\n", i, Status));
        }
    }
    
    // Free memory used for handle buffer
    if (HandleBuffer)
        gBS->FreePool(HandleBuffer);

    // Show driver loaded
    ShowStatus(0xFF, 0xFF, 0xFF); // White
    
    return EFI_SUCCESS;
}
For successful compilation there are yet not enough TakeScreenshot and ShowStatus functions about which below.

We look for FS with access to record, we write data to the file


Before to look for available graphic consoles and to remove from them screenshots, it is necessary to find out whether it is possible to save these screenshots somewhere. For this purpose it is necessary to find all copies of the SimpleFileSystem protocol which is published by the PartitionDxe driver for each found volume which FS is known to a firmware. Most often the only known FS — FAT12/16/32 family (sometimes only FAT32) which according to the UEFI standard can be used for ESP. It is necessary to check further that on the found FS record is possible, it is possible to make it different methods, the simplest — to try to create on it the file and to open it on a read and write if it turned out — on this FS it is possible to write. The solution is, of course, not the most optimum, but working, I offer the correct implementation to readers as exercise.
Again we translate from Russian on With
EFI_STATUS
EFIAPI
FindWritableFs (
    OUT EFI_FILE_PROTOCOL **WritableFs
    )
{
    EFI_HANDLE *HandleBuffer = NULL;
    UINTN      HandleCount;
    UINTN      i;
    
    // Locate all the simple file system devices in the system
    EFI_STATUS Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiSimpleFileSystemProtocolGuid;, NULL, &HandleCount;, &HandleBuffer;);
    if (!EFI_ERROR (Status)) {
        EFI_FILE_PROTOCOL *Fs = NULL;
        // For each located volume
        for (i = 0; i < HandleCount; i++) {
            EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFs = NULL;
            EFI_FILE_PROTOCOL *File = NULL;
            
            // Get protocol pointer for current volume
            Status = gBS->HandleProtocol(HandleBuffer[i], &gEfiSimpleFileSystemProtocolGuid;, (VOID **) &SimpleFs;);
            if (EFI_ERROR (Status)) {
                DEBUG((-1, "FindWritableFs: gBS->HandleProtocol[%d] returned %r\n", i, Status));
                continue;
            }
            
            // Open the volume
            Status = SimpleFs->OpenVolume(SimpleFs, &Fs;);
            if (EFI_ERROR (Status)) {
                DEBUG((-1, "FindWritableFs: SimpleFs->OpenVolume[%d] returned %r\n", i, Status));
                continue;
            }
            
            // Try opening a file for writing
            Status = Fs->Open(Fs, &File;, L"crsdtest.fil", EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0);
            if (EFI_ERROR (Status)) {
                DEBUG((-1, "FindWritableFs: Fs->Open[%d] returned %r\n", i, Status));
                continue;
            }
            
            // Writable FS found
            Fs->Delete(File);
            *WritableFs = Fs;
            Status = EFI_SUCCESS;
            break;
        }
    }
    
    // Free memory
    if (HandleBuffer) {
        gBS->FreePool(HandleBuffer);
    }
    
    return Status;
}
This code needs nothing more, works as is.

We look for the graphic console and we do a picture of its screen


Having checked that to save screenshots is on what, we will be engaged in their removal. For this purpose it is required to touch all copies of the GOP protocol which is published by GOP drivers and Videobios'Y (more precisely, not VBIOS which knows nothing about any protocols, and the ConSplitter driver implementing a layer between old VBIOS and UEFI) for each output device with graphics. This prtokol has a Blt function for copying of the image from a freymbuffer and in it while we need only the first. By means of object of Mode of the same protocol it is possible to get the current display resolution which is necessary for selection of the buffer of the necessary size and removal of a screenshot from all screen, but not from its some part. having received a screenshot, it is worth checking that it not absolutely black because to save such — an excess waste of time and places on FS, the black rectangle of the necessary size can and be drawn in Paint. Then it is necessary to transform the picture from BGR (in which it is given by Blt) to RGB (which is necessary to an enkoder of PNG) otherwise colors on screenshots will be wrong. We code the picture received after converting and we save it in the file on that FS which we found on the previous step. We will collect the file name in a format 8.3 from current date and time, the chance so is less that one screenshot will rewrite another.
Again we translate from Russian on With
EFI_STATUS
EFIAPI
TakeScreenshot (
    IN EFI_KEY_DATA *KeyData
    )
{
    EFI_FILE_PROTOCOL *Fs = NULL;
    EFI_FILE_PROTOCOL *File = NULL;
    EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput = NULL;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Image = NULL;
    UINTN      ImageSize;         // Size in pixels
    UINT8      *PngFile = NULL;
    UINTN      PngFileSize;       // Size in bytes
    EFI_STATUS Status;
    UINTN      HandleCount;
    EFI_HANDLE *HandleBuffer = NULL;
    UINT32     ScreenWidth;
    UINT32     ScreenHeight;
    CHAR16     FileName[8+1+3+1]; // 0-terminated 8.3 file name
    EFI_TIME   Time;
    UINTN      i, j;

    // Find writable FS
    Status = FindWritableFs(&Fs;);
    if (EFI_ERROR (Status)) {
        DEBUG((-1, "TakeScreenshot: Can't find writable FS\n"));
        ShowStatus(0xFF, 0xFF, 0x00); // Yellow
        return EFI_SUCCESS;
    }
    
    // Locate all instances of GOP
    Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiGraphicsOutputProtocolGuid;, NULL, &HandleCount;, &HandleBuffer;);
    if (EFI_ERROR (Status)) {
        DEBUG((-1, "ShowStatus: Graphics output protocol not found\n"));
        return EFI_SUCCESS;
    }
    
    // For each GOP instance
    for (i = 0; i < HandleCount; i++) {
        do { // Break from do used instead of "goto error"
            // Handle protocol
            Status = gBS->HandleProtocol(HandleBuffer[i], &gEfiGraphicsOutputProtocolGuid;, (VOID **) &GraphicsOutput;);
            if (EFI_ERROR (Status)) {
                DEBUG((-1, "ShowStatus: gBS->HandleProtocol[%d] returned %r\n", i, Status));
                break;
            }
        
            // Set screen width, height and image size in pixels
            ScreenWidth  = GraphicsOutput->Mode->Info->HorizontalResolution;
            ScreenHeight = GraphicsOutput->Mode->Info->VerticalResolution;
            ImageSize = ScreenWidth * ScreenHeight;
            
            // Get current time
            Status = gRT->GetTime(&Time;, NULL);
            if (!EFI_ERROR(Status)) {
                // Set file name to current day and time
                UnicodeSPrint(FileName, 26, L"%02d%02d%02d%02d.png", Time.Day, Time.Hour, Time.Minute, Time.Second);
            }
            else {
                // Set file name to scrnshot.png
                UnicodeSPrint(FileName, 26, L"scrnshot.png");
            }
            
            // Allocate memory for screenshot
            Status = gBS->AllocatePool(EfiBootServicesData, ImageSize * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL), (VOID **)&Image;);
            if (EFI_ERROR(Status)) {
                DEBUG((-1, "TakeScreenshot: gBS->AllocatePool returned %r\n", Status));
                break;
            }
        
            // Take screenshot
            Status = GraphicsOutput->Blt(GraphicsOutput, Image, EfiBltVideoToBltBuffer, 0, 0, 0, 0, ScreenWidth, ScreenHeight, 0);
            if (EFI_ERROR(Status)) {
                DEBUG((-1, "TakeScreenshot: GraphicsOutput->Blt returned %r\n", Status));
                break;
            }
            
            // Check for pitch black image (it means we are using a wrong GOP)
            for (j = 0; j < ImageSize; j++) {
                if (Image[j].Red != 0x00 || Image[j].Green != 0x00 || Image[j].Blue != 0x00)
                    break;
            }
            if (j == ImageSize) {
                DEBUG((-1, "TakeScreenshot: GraphicsOutput->Blt returned pitch black image, skipped\n"));
                ShowStatus(0x00, 0x00, 0xFF); // Blue
                break;
            }
            
            // Open or create output file
            Status = Fs->Open(Fs, &File;, FileName, EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0);
            if (EFI_ERROR (Status)) {
                DEBUG((-1, "TakeScreenshot: Fs->Open of %s returned %r\n", FileName, Status));
                break;
            }
            
            // Convert BGR to RGBA with Alpha set to 0xFF
            for (j = 0; j < ImageSize; j++) {
                UINT8 Temp = Image[j].Blue;
                Image[j].Blue = Image[j].Red;
                Image[j].Red = Temp;
                Image[j].Reserved = 0xFF;
            }
        
            // Encode raw RGB image to PNG format
            j = lodepng_encode32(&PngFile;, &PngFileSize;, (CONST UINT8*)Image, ScreenWidth, ScreenHeight);
            if (j) {
                DEBUG((-1, "TakeScreenshot: lodepng_encode32 returned %d\n", j));
                break;
            }
                
            // Write PNG image into the file and close it
            Status = File->Write(File, &PngFileSize;, PngFile);
            File->Close(File);
            if (EFI_ERROR(Status)) {
                DEBUG((-1, "TakeScreenshot: File->Write returned %r\n", Status));
                break;
            }
            
            // Show success
            ShowStatus(0x00, 0xFF, 0x00); // Green
        } while(0);
        
        // Free memory
        if (Image)
            gBS->FreePool(Image);
        if (PngFile)
            gBS->FreePool(PngFile);
        Image = NULL;
        PngFile = NULL;
    }
    
    // Show error
    if (EFI_ERROR(Status))
        ShowStatus(0xFF, 0x00, 0x00); // Red
    
    return EFI_SUCCESS;
}
For work there is not enough lodepng_encode32 and ShowStatus which was already mentioned above, will continue.

We code the image in the PNG format


The best method to write a code — not to write it therefore we will take ready library for coding and decoding of PNG by the name of lodepng. We swing, we put near our S-file, we add ours to the INF file in the section [Sources.common] of the line lodepng.h and lodepng.c, we include the heading file, iiya … nothing is compiled, t.k lodepng does not expect that the standard library of language C can take and be absent here and so entirely. Anything, dopily, not for the first time.
We will add the following to the beginning of lodepng.h:
#include <Uefi.h>                           // Для успешной сборки в среде UEFI
#define LODEPNG_NO_COMPILE_DECODER          // Отключаем декодер PNG
#define LODEPNG_NO_COMPILE_DISK             // Отключаем запись на диск, т.к. fopen/fwrite у нас нет
#define LODEPNG_NO_COMPILE_ALLOCATORS       // Отключаем стандартные malloc/realloc/free, т.к. их у нас нет
#define LODEPNG_NO_COMPILE_ERROR_TEXT       // Отключаем сообщения об ошибках 
#define LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS // Отключаем текстовые данные в PNG, т.к. не нужны
#if !defined(_MSC_VER)                      // Определяем тип size_t для GCC, у MS он встроен при настройках сборки по умолчанию
#define size_t UINTN
#endif
Also we will comment out a line with #include <string.h> which we do not have too. It is possible to create, of course, the local file with the same name, having defined the size_t type there, but if began to change — we will change.
With lodepng.c it is a little more difficult since from standard library, except size_t, it also needs memset, memcpy, malloc, realloc, free, qsort, and still he uses calculations with a floating point. Implementation of qsort can be dragged off at Apple, to make functions of work with memory wrappers over gBS-> CopyMem, gBS-> SetMem, gBS-> AllocatePool and gBS-> FreePool respectively and to signal about work with FPU it is necessary to define the constant CONST INT32 _fltused = 0; differently the linkovshchik will swear on its absence. About commenting of files with standard #include I do not tell an ama any more — all and is so clear.
Similarly qsort.c also is given to normal fight, do not forget to add it to the INF file only.

We output the status


It was necessary to write the ShowStatus function and our driver is ready. To receive this status it is possible to display, for example, by different methods numbers from 0x00 to 0xFF in CPU IO port 80h which is connected to the POST coder, but all have it not, and on notebooks — at all does not meet. It is possible to peep the speaker, but it, first, platformo-is dependent, and secondly — wildly enrages after couple of screenshots. It is possible to blink bulbs on the keyboard, this additional task for the reader, and we will show the status of work with the graphic console directly via this graphic console — displaying a small square of the necessary color in the screen upper left corner. At the same time the white square will mean "the driver is successfully loaded", yellow — "FS with a recording capability is not found", blue — "A screenshot of the current console completely black, to save there is no sense", red — "there was an error" and, at last, green — "the screenshot is removed and saved". It is necessary to bring it a square to all consoles, and after short time to recover that piece of the image which was jammed by it.
Last time we translate from Russian on With
EFI_STATUS
EFIAPI
ShowStatus (
    IN UINT8 Red, 
    IN UINT8 Green, 
    IN UINT8 Blue
    )
{
    // Determines the size of status square
    #define STATUS_SQUARE_SIDE 5

    UINTN        HandleCount;
    EFI_HANDLE   *HandleBuffer = NULL;
    EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput = NULL;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL Square[STATUS_SQUARE_SIDE * STATUS_SQUARE_SIDE];
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL Backup[STATUS_SQUARE_SIDE * STATUS_SQUARE_SIDE];
    UINTN i;
    
    // Locate all instances of GOP
    EFI_STATUS Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiGraphicsOutputProtocolGuid;, NULL, &HandleCount;, &HandleBuffer;);
    if (EFI_ERROR (Status)) {
        DEBUG((-1, "ShowStatus: Graphics output protocol not found\n"));
        return EFI_UNSUPPORTED;
    }
    
    // Set square color
    for (i = 0 ; i < STATUS_SQUARE_SIDE * STATUS_SQUARE_SIDE; i++) {
        Square[i].Blue = Blue;
        Square[i].Green = Green;
        Square[i].Red = Red;
        Square[i].Reserved = 0x00;
    }
    
    // For each GOP instance
    for (i = 0; i < HandleCount; i ++) {
        // Handle protocol
        Status = gBS->HandleProtocol(HandleBuffer[i], &gEfiGraphicsOutputProtocolGuid;, (VOID **) &GraphicsOutput;);
        if (EFI_ERROR (Status)) {
            DEBUG((-1, "ShowStatus: gBS->HandleProtocol[%d] returned %r\n", i, Status));
            continue;
        }
            
        // Backup current image
        GraphicsOutput->Blt(GraphicsOutput, Backup, EfiBltVideoToBltBuffer, 0, 0, 0, 0, STATUS_SQUARE_SIDE, STATUS_SQUARE_SIDE, 0);
        
        // Draw the status square
        GraphicsOutput->Blt(GraphicsOutput, Square, EfiBltBufferToVideo, 0, 0, 0, 0, STATUS_SQUARE_SIDE, STATUS_SQUARE_SIDE, 0);
        
        // Wait 500ms
        gBS->Stall(500*1000);
        
        // Restore the backup
        GraphicsOutput->Blt(GraphicsOutput, Backup, EfiBltBufferToVideo, 0, 0, 0, 0, STATUS_SQUARE_SIDE, STATUS_SQUARE_SIDE, 0);
    }
    
    return EFI_SUCCESS;
}
Now everything is ready and successfully gathers if is not present — saw, will not gather yet, or download my ready driver with GitHub and compare to yours, I can be banal some changes forgot to describe.

We test result in UEFI Shell


We take away our assembled driver from UDK2015/Build/MdeModulePkg/RELEASE/X64/MdeModulePkg/CrScreenshotDxe/CrScreenshotDxe/OUTPUT, only two files — the CrScreenshotDxe.efi driver and section of dependences of CrScreenshotDxe.depex will be necessary for us for it from there
For a start we will test operation of the driver from UEFI Shell. Copy the CrScreenshotDxe.efi file on the USB USB stick from UEFI Shell, be loaded into it, pass into an USB stick root with the fs0 command: (number can change depending on quantity of the disks connected to your system) and execute the load CrScreenshotDxe.efi command. If saw the message on success and the white square which flew in the upper corner of the screen — the driver means it is loaded and works. At me it looks here so:
UEFI Shell
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
This screenshot, as well as all the subsequent, is removed by our driver therefore the square in a corner on it is not visible.
Further safely press LCtrl + LAlt + F12 and watch the status. On my systems with AMI the graphic console one and therefore I see the flown green square and I receive one screenshot for one clicking of a combination. On my systems with Phoenix and Insyde there were about two graphic consoles, one of which empty therefore I see at first a blue square, and then green, a screenshot at the same time too only one. The result of testing from UEFI Shell for them looks also, only permission there not 800х600 any more, but 1366х768.
There now, from a shell everything works and it is possible to remove screenshots from UEFI-applications, here such:
RU.efi
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications

We test result in the modified firmware


Unfortunately, thus not to remove a screenshot from BIOS Setup — the driver is loaded too late. Solutions possible here two, the first — to add our driver together with section of dependences to firmware DXE volume by means of UEFITool, the second — to add it to OptionROM of some PCIe-device then modification of a firmware is not required. I still will try to implement the second method later when I receive the necessary piece of iron, and here with the first there are no problems any. We insert, we sew, we start, we stick the USB stick, we come into BIOS Setup, we click LCtrl + LAlt + F12 — voila, we see blue and green squares, everything works. The result here so looks:
Password entry form
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
Information tab
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
Main tab
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
Security tab
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
Boot tab
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
Exit tab
We write the DXE driver for removal of screenshots from BIOS Setup and other UEFI-applications
It is success, misters.

Conclusion


The driver is written, the code is laid out on GitHub, it was necessary to check idea with OptionROM, and the subject, one may say, is closed.
If to you it is still unclear what here in general occurs, you found a bug in a code, or just want to discuss article, the author, monstrosity of UEFI or how it was good at the time of legacy BIOS — welcome to comments.
Thanks to readers for attention, good to you DXE drivers.

This article is a translation of the original post at habrahabr.ru/post/274463/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus