My First UEFI Application

Photo by AltumCode on Unsplash

Introduction

UEFI is a specification that defines a software interface between an operating system (OS) and a platform firmware replacing the legacy BIOS firmware interface originally present in all IBM-PCs. Besides the firmware interfaces, the UEFI also defines the boot services that are required for booting the OS. Last but not least, the UEFI specification defines the runtime services which are services that will be available parallel to the OS execution.

There are some frameworks that implements the UEFI specification. Among them, we have the EDK II. This framework is an open source implementation of the UEFI Spec developed by the TianoCore community.

According to TianoCore community,

In this article we will do the necessary introduction to the basics of UEFI modules while creating an Hello World UEFI Application for ARMv8 architecture using EDK II framework.

All scripts and code that will be developed in this article can be found at: http://github.com/davysouza/hello-world-uefi

Setting up the Development Environment

First of all, we need to create our development environment. Since we are going to compile a code from x86 to an ARMv8 platform, a cross-compilation is required. To do so, we are going to install the required packages and download the gcc-arm cross-compiler. However, before we proceed, lets check the linux version on our development PC:

The application developed during this article was developed in an Ubuntu 20.10 desktop (amd64). The process may also work on WSL if you are using a Windows system.

OS version checked, we need to install the required packages:

In order to make things easier and more organized to us, we are going to set a source path variable:

Let’s download the gcc-arm cross-compiler:

After that, we need to clone the EDK II repository into our workspace:

Before start compiling anything, we need to say to EDK II where is the compiler that we are going to use. The same must be done for the Python interpreter. Both of them are expected by EDK II during the build process:

All things done, now we can run the edksetup.sh script to configure the EDK II development environment and build the EDK II BaseTools:

The EDK II BaseTools contains all the tools required for building EDK II.

If all commands were successfully executed we are ready to start coding our application!

Hello World

Developing an UEFI application is slightly different from developing a C application. The EDK II framework already implements a set of libraries that make the development process easier and similar to a C application. Usually, UefiLib meets most of our needs. However, besides the C files, an UEFI application requires some other files to describe the behavior of the application.

First things first, let’s start by creating the HelloWorld.c file:

The Uefi.h header is a required include for any UEFI module of type UEFI_APPLICATION. It includes all the base types defined for an UEFI application as all the headers defined in the UEFI Specification.

The EntryPoint function is the main function of our application. An UEFI application entry point has a specific header defining two parameters: The ImageHandle and the SystemTable. Defined by the UEFI Specification, the SystemTable is where everything about UEFI can be found; all the runtime and boot services can be accessed through the SystemTable. More details can be found at the volume 2 of the PI Specification. For now, what we need to know is that we are using SystemTable to print an UTF-16 string by calling the OutputString function.

We can make a cleaner version of this code by using UefiLib:

The Print function will work as the standard printf of C, but using UTF-16 strings. I strongly recommend you to check UefiLib and see what can be done by using this library. It’s surprising how effective and useful this library becomes during the development.

Last, it was added a Stall function just to stay on the screen for some seconds before the system restarts. The application will run on an environment with no OS, so after the end of the application the system will restart since there is no other application running on them.

Now, we need to create an .INF file:

According to EDK II Module Writer’s Guide,

The first section include some variables needed during the build process. At this section, we define the type of the application that will be built (UEFI Application, UEFI Library, UEFI Driver, etc.), the GUID and the name of the entry point function. The next sections will define the source code of the application as well as the packages and libraries required to build the application. You can check a detailed explanation of INF file here.

Two different .INF files cannot have the same GUID. Be sure to not copy the same GUID in all files. A new GUID can be generated by running the uuidgen command.

Moving on, the last file to be created is the .DSC file:

According to EDK II DSC Specification,

Similar to INF, the first section define some variables that can be used later in the build steps. We can highlight the supported architectures and output directory. The Library Classes section will provide a map between the library class names used by an EDKII module and the Library Instances that are selected by the platform INF file. The last one, the components sections contain lists of EDK II Modules, in our case, HelloWorld module. More details about this file can be found here.

All things done, let’s build our application!

Building

This script will define the environment variables needed for the build, run the EDKII setup and build the application. At the build command we need to say which DSC and INF files of the application as well as the architecture, toolchain and build type that we are going to use.

If you want to build an application for a X64 platform, just replace the AARCH64 parameter with X64.

If the compilation works fine you should be able to seeing a message like that:

At this point, we have everything needed to run the application.

Running

When we run an application on an UEFI environment, for security reasons this binary must be signed. The Secure Boot is the UEFI protocol that will prevents the loading of binaries that are not signed with a valid digital signature.

Since we are developing this application just for study purposes, we are not going to sign our application. The way to avoid this issue is by disabling the Secure Boot option inside the BIOS options of your system:

The UEFI architecture requires that the application that we are going to run be on a FAT-32 flash drive in a specific path so that the application may be detected during the boot process:

  • $FLASH_DRIVE/EFI/BOOT/BOOTAA64.efi (AARCH64)
  • $FLASH_DRIVE/EFI/BOOT/BOOTX64.efi (X64)

The script below will copy the application to the expected place inside the flash drive. Be sure to replace the <FLASH_DRIVE_PATH> with the path where your flash drive was mounted:

Now we need just to use the flash drive to boot the system into our application. To do so, you may need to change the Boot Order list inside the BIOS options to specify the device where we are going to boot.

That is all!!!

Congratulations! You have written your first UEFI application!

References

If you like this post you may want to check the repository with source code. Feel free to send any questions about the topic and subscribe to get more knowledge about UEFI development.

Software Development Analyst | Computer Science | Artificial Intelligence | Embedded Software | C, C++, Python

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store