UEFI: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (Fix lint errors)
 
(163 intermediate revisions by 34 users not shown)
Line 1: Line 1:
(U)EFI or (Unified) Extensible Firmware Interface is a specification for x86, x86-64, ARM, and Itanium platforms that defines a software interface between the operating system and the platform firmware/BIOS. The original EFI was developed in the mid-1990s by Intel for use developing firmware/BIOS for Itanium platforms. In 2005 Intel transitioned the specification to a new working group called the Unified EFI Forum, consisting of companies such as AMD, Microsoft, Apple, and Intel itself. All modern PCs ship with UEFI firmware and UEFI is widely supported by both commercial and open source operating systems. Backwards compatibility is provided for legacy operating systems.
== Introduction ==


== UEFI basics ==
(U)EFI or (Unified) Extensible Firmware Interface is a specification that defines a software interface between the operating system(s) and the platform's firmware. In the mid 90s Intel was creating a new processor architecture that was 64-bit, but wasn't backwards-compatible with the old x86. This architecture was the Itanium 64. Because the IA-64 only supports 64-bit instructions, the PC BIOS couldn't be used, therefore Intel developed the EFI specification. Later on this specification was managed (and still is) by the UEFI board, an association of several companies such as AMD, Microsoft, Intel, Apple and so on.


===Downloading UEFI images===
== Support ==
If you choose [[VirtualBox]] for virtualization, then UEFI is already included, no need to download the image manually. You just have to enable it in the VM's preferences by clicking "Settings" / "Systems" / "Enable EFI (special OSes only)" checkbox.


Otherwise for emulation and virtual machines, you'll need an OVMF.fd firmware image. It might be tricky to find, so here are some alternative download links too:
The (U)EFI specification is currently defined for the Itanium platform, the x86-platform (64-bit included) and the ARM platform. Since Apple's movement with Mac OS X from the PowerPC architecture to the x86-architecture, they have been using EFI, although with minor differences here and there. Most modern operating systems, boot loaders and boot managers support (U)EFI as well nowadays. Most 64-bit versions of Microsoft Windows, FreeBSD, Apple Mac OS X and Linux all have support for UEFI to some extent. As for boot loaders and managers you generally have (E)GRUB and ELILO. As for emulators and virtual machines, you can generally use VirtualBox and Qemu.
* [https://github.com/tianocore/tianocore.github.io/wiki/OVMF TianoCore Download] (official link)
* [https://github.com/BlankOn/ovmf-blobs OVMF-blobs] (unofficial precompiled 32 bit and 64 bit versions, easy to use)
* [https://www.kraxel.org/repos/ RPM packages] (official TianoCore packages)
* [https://packages.debian.org/sid/ovmf Debian packages]
* [https://packages.gentoo.org/useflags/ovmf Gentoo packages]
* [https://aur.archlinux.org/packages/ovmf-git/?comments=all Arch packages]


Under Linux, you can also install these with your distro's package manager, for example:
== Developing for (U)EFI ==


'''Debian / Ubuntu'''
One can develop applications, boot loaders or drivers for (U)EFI if he/she has the required hardware or software to develop for it. As said before you can use VirtualBox and Qemu as both of them seem to have a (U)EFI implementation of some sort. Another option is to use Intel's TianoCore, which is supposedly their own (U)EFI firmware interface implementation to run on top of the PC BIOS and/or as a Coreboot payload. The simplest way is by getting the right hardware. Generally there are several motherboards available that have an (U)EFI implementation, sometimes with a PC BIOS implementation next to it. Below is a list of noticeable BIOS companies:
<syntaxhighlight lang="bash">
# apt-get install ovmf
</syntaxhighlight>


'''RedHat / CentOS'''
* AMI (Aptio).
<syntaxhighlight lang="bash">
* Phoenix (SecureCore, TrustedCore, AwardCore).
# yum install ovmf
* Insyde (InsydeH20).
</syntaxhighlight>


'''MacOS'''
Apple macs are also well known to implement UEFI 1.0 (not 2.0)
== Tools ==


Use the OVMF-blobs repo.
The (U)EFI Development Kit, the (U)EFI Toolkit and the (U)EFI specifications might be interesting to use. As for writing actual (U)EFI software you can use several compilers such as GCC and Microsoft Visual Studio for C and FASM for Assembly. The (U)EFI Toolkit might be interesting when using a C compiler, as it contains several C/C++ headers.


'''Windows'''
Note that the C language is incompatible with EBC (EFI Byte Code) and thus a special compiler supporting the EFI C dialect is required. GCC is not one such compiler.


Use the OVMF-blobs repo or download the RPM version, then use ''7-Zip File Manager'' to extract the OVMF.fd file from the downloaded archive.
== Booting ==


===UEFI vs. legacy BIOS===
The (U)EFI system does not look for a boot sector, instead it looks for a file located on a [[FAT]] formatted disk. Other filesystems may be supported.
A common misconception is that UEFI and BIOS are mutually exclusive. In reality, both legacy motherboards and UEFI-based motherboards both include BIOS ROMs. The differences are in where they find the bootloader/OS, how they prepare the system before executing it, and what convenience functions they provide.


====Platform initialization====
The system looks for a file in an approved directory. Normally, there are a bunch of "UEFI variables" stored in the computer's NVRAM that specify the path of the file that must be loaded as an UEFI application. Actually, the NVRAM stores multiple bootable entries in it, each one with it's own label, just as a menu-driven boot loader would do.
On a legacy system, BIOS performs all the usual platform initialization (memory controller configuration, PCI bus configuration and BAR mapping, graphics card initialization, etc.), but then drops into a backwards-compatible real mode environment. The bootloader must enable the A20 gate, configure a GDT and an IDT, switch to protected mode, and for x86-64 CPUs, configure paging and switch to long mode.


UEFI firmware performs those same steps, but it also enables the A20 gate and prepares either protected mode environment with flat segmentation (for 32-bit x86 processors) or a long mode environment with identity-mapped paging (for x86-64 processors).
But that's not everything, because there is some incompatible behaviour. Some (U)EFI implementations just boot from the file /EFI/BOOT/BOOTX64.EFI. In the case of Apple, apple has modified the firmware to support HFS+. In apple computers a file is "Blessed" with the ability to boot from a certain file.


Additionally, the platform initialization procedure of UEFI firmware is standardized. This allows UEFI firmware to be extended in a vendor-neutral way.
=== Tools o configure the beast ===
&lt;biased mode!&gt;UEFI is an anti-standard, meaning that it's promoters invented it so that it looked like they were creating a more flexible technology, while in reality they just managed to create something even more fragile than the legacy BIOSes ever managed to be.&lt;/biased mode!&gt;


====Boot mechanism====
In practise the right tool to configure the beast depends on your specific system. What there is is the following:
A legacy BIOS loads a 512 byte flat binary blob from the MBR of the boot device into memory at physical address 7C00 and jumps to it. The bootloader cannot return back to BIOS. UEFI firmware loads an arbitrary sized UEFI application (a relocatable PE executable file) from a FAT partition on a GPT or MBR partitioned boot device to some address selected at run-time. Then it calls that application's main entry point. The application can return control to the firmware, which will continue searching for another boot device or bring up a diagnostic menu.
* '''efibootmgr''', Linux command line utility that configs UEFI boot entries using efivarfs kernel module. Type ''efibootmgr'' to see current entries.
* '''bless''' utility, which chooses the "blessed" file in Machintosh systems.
* '''UEFI Shell''', which is built in some firmwares but not in others. The shell command used to configure boot entries is "bcfg". Type ''bcfg boot dump -v'' in shell to see current entries.
* Others


====System discovery====
=== And what about a nice boot menu provided by the firmware? ===
A legacy bootloader scans memory for structures like the [[EBDA]], [[SMBIOS]], and [[ACPI]] tables. It uses PIO to talk to the root [[PCI]] controller and scan the PCI bus. It is possible that redundant tables may be present in memory (for example, the [[MP_Specification|MP]] table in the SMBIOS contains information that's also present in the ACPI [[DSDT]] and [[MADT]]) and the bootloader can choose which to use.
At first I thought that this menu existed. However, in rEFInd Boot Manager's website there is the explanation you were waiting for:


When UEFI firmware calls a UEFI application's entry point function, it passes a "System Table" structure, which contains pointers to all of the system's ACPI tables, memory map, and other information relevant to an OS. Legacy tables (like MP tables) may not be present in memory.
In theory, EFI implementations should provide boot managers. Unfortunately, in practice
these boot managers are often so poor as to be useless. The worst I've personally
encountered is on Gigabyte's Hybrid EFI, which provides you with no boot options whatsoever,
beyond choosing the boot device (hard disk vs. optical disc, for instance). I've heard of
others that are just as bad. For this reason, a good EFI boot manager — either standalone or
as part of a boot loader — is a practical necessity for multi-booting on an EFI computer.
That's where rEFIt and rEFInd come into play.
http://www.rodsbooks.com/refind/


====Convenience functions====
== Using GNU toolchain for compiling and debugging EFI applications ==
A legacy BIOS hooks a variety of interrupts which a bootloader can trigger to access system resources like disks and the screen. These interrupts are not standardized, except by historical convention. Each interrupt uses a different register passing convention.


UEFI firmware establishes many callable functions in memory, which are grouped into sets called "protocols" and are discoverable through the System Table. The behavior of each function in each protocol is defined by specification. UEFI applications can define their own protocols and persist them in memory for other UEFI applications to use. Functions are called with a standardized, modern calling convention supported by many C compilers.
GNU development toolchain consist of binutils package (assembler, linker, various utilities for manipulating executable images), GCC compiler, GNU make and GDB debugger. Advantages are obvious - build system is very flexible, the tools have very rich functionality, they are free and open source, your EFI application code can be easily integrated to any project. Most of open source software prefers this way.


====Development environment====
The first step is to compile your tools with required options. Firstly you need to compile binutils package. Obtain the latest from http://ftp.gnu.org/gnu/binutils/. You will need to enable "i386-efi-pe" target to have support for EFI PE+ executable format. If you use the same toolchain for compiling something else in your project (e.g. OS kernel) you can also specify required target (e.g. x86_64-myOS-freebsd) Note that it is BFD target, not the target platform. If you want to develop for 64-bits platform add "--enable-64-bit-bfd" options to "configure" script.
Legacy bootloaders can be developed in any environment that can generate flat binary images: NASM, GCC, etc. UEFI applications can be developed in any language that can be compiled and linked into a [[PE]] executable file and supports the calling convention used to access functions established in memory by the UEFI firmware. In practice this means one of three development environments: EDK2, GNU-EFI or POSIX-UEFI.
<source lang="bash">
../src/configure --prefix=$PREFIX --target=$TARGET --disable-nls --build=$BUILD --enable-64-bit-bfd \
--enable-targets=i386-efi-pe,x86_64-phoenix-freebsd
make all
make install
</source>
Here and below $PREFIX variable points to your prefix where you want to install your build tools (e.g. "export PREFIX=/home/John/projects/myOS/build-tools") and $TARGET is you target platform (e.g. "x86_64-myOS-elf").


[[EDK2]] is a large and complex, yet feature filled environment with its own build system. It can be configured to use GCC, LLVM, MinGW, Microsoft Visual C++, etc. as a cross-compiler. Not only can it be used to compile UEFI applications, but it can also be used to compile UEFI firmware to be flashed to a BIOS ROM.


[[GNU-EFI]] is a set of libraries and headers for compiling UEFI applications with a system's native GCC (does not work with LLVM CLang). It can't be used to compile UEFI firmware. Since it's just a couple of libraries against which a UEFI application can be linked, it is much easier to use than TianoCore ( EDK2 ).
If your build machine has not the same architecture as target platform you will need a cross compiler. There are [[GCC_Cross-Compiler|instructions about compiling GCC]] for cross platform development. Just use binutils compiled above with these instructions.


[[POSIX-UEFI]] is very similar to GNU-EFI, but it is distributed mainly as a source, not as a binary library, has ANSI C like names and works with GCC as well as LLVM CLang. It's shipped with a Makefile that sets up the compiler flags for you.
You will need a debugger for debugging your applications. Obtain it from http://ftp.gnu.org/gnu/gdb/ and compile:
<source lang="bash">
./configure --prefix=$PREFIX --target=$TARGET --enable-64-bit-bfd
</source>


====Emulation====
Now you are ready to compile your first EFI application. Download gnu-efi package from https://sourceforge.net/projects/gnu-efi/ and read its README files. Follow the instructions there, check Makefiles are pointing to your build tools and have correct architecture specified. Run "make" command and you will get several sample applications in "apps" directory. We will describe how to run them a bit later but for now you need to examine the build log and notice all commands which where executed and all required options for them. This will be a basis for your Makefile if you will wish to integrate EFI application to your project. Several advices:
* If you build a single binary in your project (e.g. OS loader) you will not need to make static libraries like it is done in the gnu-efi package. Just compile all required C and Assembler file and link them together in the final executable file.
* If you project has only one target platform you can throw away unnecessary source files. Just select for gnu-efi build log all files which where compiled and throw away all the others.
[http://ast-phoenix.git.sourceforge.net/git/gitweb.cgi?p=ast-phoenix/ast-phoenix;a=tree;f=kernel/boot;h=646835063c1362732f209c1312ce0d2ba7b558a5;hb=HEAD Here] is an example of the package integration. Pay attention to the Makefile. We will touch some its aspects later.


[[OVMF]], a popular open source UEFI firmware, has been ported to the QEMU (but not Bochs) emulated machine. Because it implements the UEFI specification, it behaves very similarly to commercial UEFI firmware on real machines. (OVMF itself is built with TianoCore, and its source ship with is as well, but pre-built images are available.)
Now we need to run the resulted application(s) and somehow debug it. Qemu virtual machine is a good choice because of its rich built-in debugging functionality.


===Legacy bootloader or UEFI application?===
Download and compile it. Specify your target platform. You can use "--enable-kvm" option to significantly increase emulation speed if you have Linux kernel and kvm package installed.
If you are targeting legacy systems for which UEFI is not available or is not reliable, you should develop a legacy bootloader. This requires intimate knowledge of 16-bit addressing and the backwards-compatibility features of an x86 or x86-64 CPU. If you are targeting modern systems you should develop a UEFI application. Many UEFI firmwares can be configured to emulate a legacy BIOS, but there is even more variation among these emulated environments than among real legacy BIOS.
<source lang="bash">
./configure --prefix=$PREFIX --target-list=x86_64-softmmu --enable-kvm
make
make install
</source>
Qemu does not have EFI firmware so you need to download it separately. You can use OVMF firmware based on TianoCore from http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=OVMF. Download 32 or 64 bits version depending on your target platform. Create some directory $YOUR_PREFIX/share/qemu/myOS and place there "vgabios-cirrus.bin" and "OVMF.fd" binaries from the OVMF package.


Although there is a slight learning curve to become familiar with the UEFI development environments, using the System Table, and accessing UEFI-provided protocols (functions), there are far fewer "gotchas" than trying to remain compatible with the wide variety of quickly-becoming-obsolete legacy BIOSs on real machines. UEFI is the standard for all modern PCs.
The last thing left is a disk image with EFI system partition and our application there. EFI requires [[GPT|GUID partitions table]] and FAT32 filesystem for EFI system partition. We will need gdisk utility from http://sourceforge.net/projects/gptfdisk/ for GUID partitions table creation.


===UEFI class 0-3 and CSM===
Select some reasonable size of your disk. Below I am assuming that $BYTES_PER_SECTOR is number of bytes per sector on your disk (typically 512) and $NUM_SECTORS is total disk size expressed in sectors. Firstly create disk image initial file filled with zeroes:
PCs are categorized as UEFI class 0, 1, 2, or 3. A class 0 machine is a legacy system with a legacy BIOS; i.e. not a UEFI system at all.
<source lang="bash">
export filename=$PREFIX/share/qemu/myOS/myOS.disk
dd if=/dev/zero of=$filename bs=$BYTES_PER_SECTOR count=$NUM_SECTORS
</source>


A class 1 machine is a UEFI system that runs exclusively in Compatibility Support Module (CSM) mode. CSM is a specification for how UEFI firmware can emulate a legacy BIOS. UEFI firmware in CSM mode loads legacy bootloaders. A class 1 UEFI system may not advertise UEFI support at all, since it isn't exposed to the bootloader. It's only UEFI "within" the BIOS.
After that create partition table by gdisk:
<source lang="bash">
gdisk $filename
</source>
It has interface similar to fdisk utility. Use "o" command to create new partition table, "n" for new partition with default parameters to use the whole disk (partition type "ef00" for EFI system partition), "w" to write all changes and exit. Now you have disk image with [[GPT|GUID partition table]] on it but the partition is still unformatted. We will use Linux loopback device to access the file as block device. We need to know exact position of the partition on the disk:
<source lang="bash">
gdisk -l $filename
</source>
This command will output list of partitions and their first and last sectors indexes. If you have several partitions then just use numbers for your one. Let's assume $START is the first sector index * bytes per sector and $SIZE size is the partition size in bytes. Associate your image file with the loopback device:
<source lang="bash">
losetup --offset $start --sizelimit $size /dev/loop0 $filename
</source>
So now we can create filesystem there. We need FAT for EFI system partition. You can use FAT12 if your partition size is small to prevent from big space overhead from larger FAT filesystems:
<source lang="bash">
mkdosfs -F 12 /dev/loop0
</source>
Now you can mount your partition to some mount point:
<source lang="bash">
mkdir /tmp/myOsDisk
mount /dev/loop0 /tmp/myOsDisk
</source>
Just copy all files you need there (e.g. your EFI application executable image). You can create "stratup.nsh" script which will be executed automatically if no other options are configured in EFI built-in boot-manager. Script could contain just your file launching command with required parameters, e.g. "fs0:\\efi\\boot\\myOsLoader some parameters here". According to specification you can create "/EFI/BOOT/BOOTx64.EFI" file which will be loaded automatically.
Finally unmount the partition and release the loopback device:
<source lang="bash">
umount /tmp/myOsDisk
losetup -d /dev/loop0
</source>
Your disk image is ready and you can finally launch emulation. Obviously creating disk image could be easily automated in order to not execute these actions manually each time. Automation example can be found [http://ast-phoenix.sourceforge.net/doc/doku.php?id=athena:project:phoenix:dev_env#disk_image there].


A class 2 machine is a UEFI system that can launch UEFI applications but also includes the option to run in CSM mode. The majority of modern PCs are UEFI class 2 machines. Sometimes the choice to run UEFI applications vs. CSM is a one-or-the-other setting in the BIOS configuration, and other times the BIOS will decide which to use after selecting the boot device and checking whether it has a legacy bootloader or a UEFI application.
There are some advices for emulation running:
* Use "-serial" option to have serial console available for the virtual machine. You will have console logs in your terminal and a possibility to use simple ports writing to output debug tracing to serial console.
* Use "-s" option to enable built-in GDB stab which will wait for connection on TCP port 1234.
Launch Qemu providing path to the directory where your firmware binaries are located:
<source lang="bash">
$PREFIX/bin/qemu-system-x86_64 -L $PREFIX/share/qemu/myOS -bios OVMF.fd -m 768 -cpu kvm64 \
-vga cirrus -monitor stdio -serial tcp::666,server -s -hdb $PREFIX/share/qemu/myOS/myOS.disk -enable-kvm
</source>


A class 3 machine is a UEFI system that does not support CSM. UEFI class 3 machines only run UEFI applications and do not implement CSM for backwards compatibility with legacy bootloaders.
Qemu will start and wait for incoming connection to serial console. In the example above it waits on TCP port 666. You can use, for example, socat utility to connect:
<source lang="bash">
socat -,raw,echo=0 tcp4:localhost:666
</source>
Once connected the emulation will start. You can use EFI shell command to navigate through filesystems, output system information or launch your application. It has help for all commands so refer to it for details.


===Secure Boot===
The next important question is the application debugging. The first moment is that the EFI application should be stopped at some point and wait for debugger. The simplest way to do this is to insert some endless loop in your application. The loop can be enclosed in the block which is executed, for example, when your application receives "--debug" option in its arguments. Let's assume you have inserted such code:
Secure Boot is a digital signature scheme for UEFI applications that consists of four components:
<source lang="c">
* '''PK''': Platform Key
EFI_STATUS
* '''KEK''': Key Exchange Keys
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
* '''db''': Whitelist database
{
* '''dbx''': Blacklist database
EFI_LOADED_IMAGE *loaded_image = NULL;
EFI_STATUS status;


UEFI firmware that supports Secure Boot is always in one of three states:
InitializeLib(image, systab);
* Setup mode, Secure Boot ''off''
status = uefi_call_wrapper(systab->BootServices->HandleProtocol,
* User mode, Secure Boot ''off''
3,
* User mode, Secure Boot ''on''
image,
&LoadedImageProtocol,
(void **)&loaded_image);
if (EFI_ERROR(status)) {
Print(L"handleprotocol: %r\n", status);
}


In setup mode, any UEFI application can change or delete the PK, add/remove keys from the KEK, and add/remove whitelist or blacklist entries from the db or dbx.
Print(L"Image base: 0x%lx\n", loaded_image->ImageBase);


In user mode, regardless of whether Secure Boot is on or off:
int wait = 1;
* The PK may only be changed or deleted by a UEFI application that already has the current PK.
while (wait) {
* Keys can only be added/removed from the KEK by a UEFI application that has the PK.
__asm__ __volatile__("pause");
* Whitelist and blacklist entries can only be added/removed from the db and dbx by a UEFI application that has any one of the keys in the KEK.
}


Finally, in user mode with Secure Boot ''on'', UEFI applications must meet one of the following four requirements to be launched:
return EFI_SUCCESS;
* Signed, with signature in db and not in dbx
}
* Signed by a key in db and not in dbx
</source>
* Signed by a key in the KEK
When this code will be executed "pause" instruction will be executed in the loop.
* Unsigned, but a hash of the application is in db and not in dbx


Note that UEFI applications are not signed by the PK, unless the PK also happens to be in the KEK.
The next thing required for GDB is executable image with symbols. If you carefully examined build log and Makefiles you should note that when EFI executable is created from ELF shared object file only limited set of sections are copied to the resulted image:
<source lang="text">
.text .sdata .data .dynamic .dynsym .rel .rela .reloc
</source>
For having debug symbols we need additionally these sections (in case you have compiled files with "-ggdb" option):
<source lang="text">
.debug_info .debug_abbrev .debug_loc .debug_aranges .debug_line .debug_macinfo .debug_str
</source>
But if you create EFI binary which additionally contains these sections the EFI firmware will be unable to launch it. Fortunately, we do not need the file with debug symbols on the target machine since we will use remote debugging anyway. So what you need is to create two EFI binaries - one with only required sections to upload it to target system and another one with debug symbols to use it with GDB. Actually you just need to run objcopy utility twice with different set of sections to copy and different output files. See Makefile example [http://ast-phoenix.git.sourceforge.net/git/gitweb.cgi?p=ast-phoenix/ast-phoenix;a=tree;f=kernel/boot;h=646835063c1362732f209c1312ce0d2ba7b558a5;hb=HEAD there].


Not all UEFI firmwares support Secure Boot, although it is a requirement for Windows 8. Some UEFI firmwares support Secure Boot and there's no option to be disabled, which poses a problem for independent developers that do not have access to the PK or any of the keys in the KEK, and therefore can't install their own key or application signature or hash to the whitelist database. Independent developers should develop on systems that either do not support Secure Boot or has an option for Secure Boot to be turned off.
Now you can launch GDB. You need to specify some file to use as the target binary - you can specify EFI binary with debug symbols but it will have no sense for debugging because the image will be relocated to different address. Note that in the example code above the actual image base address is output. It is required to properly load file with symbols. Let's say after you have launched your application it provided this output:
<source lang="text">
Image base: 0x2EE30000
</source>
So now you need to start GDB, connect to local TCP port 1234 where Qemu is waiting for GDB connection and load image with symbols to relocated address. We need to specify relocated addresses for .text and .data sections. Their addresses in non-relocated binary should be added to image base which is provided in the output above:
<source lang="text">
# gdb myOS.efi
GNU gdb (GDB) 7.3
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/John/myOS/source/kernel/boot/build/DEBUG/myOS.efi...(no debugging symbols found)...done.
(gdb) info files
Symbols from "/home/John/myOS/source/kernel/boot/build/DEBUG/myOS.efi".
Local exec file:
`/home/John/myOS/source/kernel/boot/build/DEBUG/myOS.efi', file type pei-x86-64.
Entry point: 0x3000
0x0000000000003000 - 0x000000000000b9ce is .text
0x000000000000b9ce - 0x000000000000b9d8 is .reloc
0x000000000000c000 - 0x000000000000e148 is .data
0x000000000000f000 - 0x000000000000f0f0 is .dynamic
0x0000000000010000 - 0x0000000000011098 is .rela
0x0000000000012000 - 0x0000000000013788 is .dynsym
(gdb) file
No executable file now.
No symbol file now.
(gdb) add-symbol-file debug.myOS.efi 0x2EE33000 -s .data 0x2EE3c000
add symbol table from file "debug.myOS.efi" at
.text_addr = 0x2ee33000
.data_addr = 0x2ee3c000
(y or n) y
Reading symbols from /home/John/myOS/source/kernel/boot/build/DEBUG/debug.myOS.efi...done.
(gdb) set architecture i386:x86-64:intel
The target architecture is assumed to be i386:x86-64:intel
(gdb) target remote :1234
Remote debugging using :1234
WaitDebugger () at loader/main.c:80
80 while (wait) {
(gdb) set variable wait = 0
</source>
We need to unload executable binary by "file" command after sections layout is displayed because otherwise its symbols will override debug symbols loaded by "add-symbol-file" command (at least for data section). You do not need to load it each time because sections addresses will change only after next recompilation. Alternatively "objdump" utility can be used to dump sections. As you can see after setup is done you can normally debug your application using whole power of the GDB. Set your "wait" variable to zero and you will exit from endless loop. Set breakpoints/watchpoints, continue execution, enjoy debugging!


An easy way out to use a loader that is signed by Microsoft, and allows you to load another binary signed by a key and certificate owned by you (called MOK, Machine Owner's Key). Such a loader is [https://github.com/rhboot/shim shim], used by RedHat, Fedora, Suse, Ubuntu, Arch and many other distros to load GRUB. The filename of the EFI executable is hardwired in shim, but if you rename your loader to GRUBX64.EFI (or GRUBIA32.EFI), you sign it with your MOK key and certificate using [https://github.com/imedias/sbsigntool sbsigntool], then you can load any loader in Secure Boot you want.
== Binary Format ==


===How to use UEFI===
(U)EFI generally uses the PE-executable format, with its very own subtypes. Every (U)EFI application is basically a DLL without symbol tables et al, and another subtypes:
Traditional operating systems like Windows and Linux have an existing software architecture and a large code base to perform system configuration and device discovery. With their sophisticated layers of abstraction they don't directly benefit from UEFI. As a result, their UEFI bootloaders do little but prepare the environment for them to run.


An independent developer may find more value in using UEFI to write feature-full UEFI applications, rather than viewing UEFI as a temporary start-up environment to be jettisoned during the boot process. Unlike legacy bootloaders, which typically interact with BIOS only enough to bring up the OS, a UEFI application can implement sophisticated behavior with the help of UEFI. In other words, an independent developer shouldn't be in a rush to leave "UEFI-land".
* (U)EFI application (10).
* (U)EFI boot service driver (11).
* (U)EFI run-time driver (12).


A good starting point is writing a UEFI application that uses the System Table to fetch a memory map, and uses the "File" protocol to read files from FAT-formatted disks. The next step might be to use the System Table to locate ACPI tables.


==Developing with POSIX-UEFI==
== Calling Conventions ==
:{{Main|POSIX-UEFI}}


One option to compile UEFI applications on POSIX like systems is POSIX-UEFI. It provies a [[libc]]-like API for your EFI application, and ships with a Makefile that can detect and set up the toolchain for you. It can use GCC or LLVM, and defaults to using the host compiler, but a cross compiler is still recommended.
The EFI specifications specify the calling conventions for 32-bit 80x86 and Itanium. The (later) UEFI specifications define the calling conventions for 32-bit 80x86, Itanium and 64-bit 80x86


It uses POSIX style typedefs (like ''uintn_t'' instead of ''UINTN''), and it does not ship with the standard EFI headers. You can still get interfaces not covered by POSIX-UEFI (such as GOP) by installing the EFI headers from GNU-EFI or EDK2. Also, it compiles with the MS ABI, meaning that UEFI services can be called natively (i.e., without uefi_call_wrapper) so long as your apps are compiled with it as well.
For 32-bit 80x86, the calling convention used is the standard C calling convention. For Itanium, the calling convention is defined in the "Intel(R) Itanium(R) System Abstraction Layer Specification".
The traditional "Hello, world" UEFI program goes like this.
<syntaxhighlight lang="c">
#include <uefi.h>
int main (int argc, char **argv)
{
printf("Hello, world!\n");
return 0;
}
</syntaxhighlight>
Makefile looks like this:
<syntaxhighlight lang="make">
TARGET = main.efi
include uefi/Makefile
</syntaxhighlight>
Run make to build it. The result of this process is a PE executable file ''main.efi''.


==Developing with GNU-EFI==
For 64-bit 80x86, Microsoft's x64 calling convention is used. This calling convention requires the stack be aligned on a 16-byte boundary (from the callee's perspective), and that a 32-byte "shadow space" be reserved on the stack (immediately above the return RIP). The shadow space is not described by the UEFI specifications. This can lead to frustrated assembly language programmers (who are writing code based on the UEFI specifications alone and don't know about the shadow space, and therefore waste '''hours''' trying to figure out why their stack gets trashed by some EFI functions)...
:{{Main|GNU-EFI}}


GNU-EFI can be used to develop both 32-bit and 64-bit UEFI applications. This section will address 64-bit UEFI applications only, and assumes that the development environment itself is running on an x86_64 system, so that no cross-compiler is needed. For a more thorough walk-through of a proper (non-gnu-efi) development environment, see [[UEFI App Bare Bones]].
GNU-EFI includes four things:
* '''crt0-efi-x86_64.o''': A CRT0 (C runtime initialization code) that provides an entry point that UEFI firmware will call when launching the application, which will in turn call the "efi_main" function that the developer writes.
* '''libgnuefi.a''': A library containing a single function (''_relocate'') that is used by the CRT0.
* '''elf_x86_64_efi.lds''': A linker script used to link ELF binaries into UEFI applications.
* '''efi.h''' and other headers: Convenience headers that provide structures, typedefs, and constants improve readability when accessing the System Table and other UEFI resources.
* '''libefi.a''': A library containing convenience functions like CRC computation, string length calculation, and easy text printing.
* '''efilib.h''': Header for ''libefi.a''.


At a bare minimum, a 64-bit UEFI application will need to link against ''crt0-efi-x86_64.o'' and ''libgnuefi.a'' using the ''elf_x86_64_efi.lds'' linker script. Most likely you will want to use the provided headers and the convenience library as well, and this section will assume that going forward.
== Example in C ==


The traditional "Hello, world" UEFI program is shown below.
Below is an example of an EFI application written in C that displays: "Hello World".
<syntaxhighlight lang="c">

<source lang="c">
#include <efi.h>
#include <efi.h>
#include <efilib.h>
#include <efilib.h>

EFI_STATUS
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
EFIAPI
efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
{
SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
InitializeLib(ImageHandle, SystemTable);
InitializeLib(ImageHandle, SystemTable);
Print(L"Hello, world!\n");
conout = SystemTable->ConOut;
uefi_call_wrapper(conout->OutputString, 2, conout, (CHAR16 *)L"Hello World\n\r");
return EFI_SUCCESS;
return EFI_SUCCESS;
}
}
</syntaxhighlight>
</source>


A few notes:
== Example in FASM ==
* efi.h is included so we can use types like EFI_STATUS, EFI_HANDLE, and EFI_SYSTEM_TABLE.
* When creating a 32-bit UEFI application, EFIAPI is empty; GCC will compile the "efi_main" function using the standard C calling convention. When creating a 64-bit UEFI application, EFIAPI expands to "__attribute__((ms_abi))" and GCC will compile the "efi_main" function using Microsoft's x64 calling convention, as specified by UEFI. Only functions that will be called directly from UEFI (including main, but also callbacks) need to use the UEFI calling convention.
* "InitializeLib" and "Print" are convenience functions provided by libefi.a with prototypes in efilib.h. "InitializeLib" lets libefi.a store a reference to the ImageHandle and SystemTable provided by BIOS. "Print" uses those stored references to print a string by reaching out to UEFI-provided functions in memory. (Later on we will see how to find and call UEFI-provided functions manually.)


This program is compiled and linked as below.
Below is an example of an EFI application written in x86 assembly (to be assembled with FASM) that displays: "Hello World".
<syntaxhighlight lang="bash">
$ gcc main.c \
-c \
-fno-stack-protector \
-fpic \
-fshort-wchar \
-mno-red-zone \
-I /path/to/gnu-efi/headers \
-I /path/to/gnu-efi/headers/x86_64 \
-DEFI_FUNCTION_WRAPPER \
-o main.o


$ ld main.o \
<source lang="asm">
/path/to/crt0-efi-x86_64.o \
format pe64 dll efi
-nostdlib \
entry main
-znocombreloc \
-T /path/to/elf_x86_64_efi.lds \
-shared \
-Bsymbolic \
-L /path/to/libs \
-l:libgnuefi.a \
-l:libefi.a \
-o main.so


$ objcopy -j .text \
section '.text' code executable readable
-j .sdata \
-j .data \
-j .dynamic \
-j .dynsym \
-j .rel \
-j .rela \
-j .reloc \
--target=efi-app-x86_64 \
main.so \
main.efi
</syntaxhighlight>


The result of this process is a 44 kB PE executable file ''main.efi''. On a real project you'll probably want to use make or another build tool, and may need to build a cross-compiler. The way GNU-EFI works is a bit contrived: you are wrapping an ELF file built by your normal compiler into PE.
include 'efi.inc'


==Emulation with QEMU and OVMF==
main:
Any recent version of QEMU with a recent version of OVMF will be sufficient to run a UEFI application. QEMU binaries are available for many platforms, and a binary OVMF image (OVMF.fd) can be found on the [http://www.tianocore.org/ovmf/ TianoCore] website. QEMU (without any boot disk) can be invoked as below. (To prevent recent versions of QEMU from attempting a PXE (network) boot when no boot disk is found, use <code>-net none</code>.
sub rsp, 32 ; Reserve shadow space
mov [Handle], rcx ; ImageHandle
mov [SystemTable], rdx ; Pointer to SystemTable.
lea rdx, [_hello]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]
call [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString]
add rsp, 32 ; Free shadow space
mov eax, EFI_SUCCESS
retn


The recommended way to use OVMF (for QEMU 1.6 or newer) is with a <code>pflash</code> parameter. The instructions below assume you have an OVMF image split into separate CODE and VARS sections.
section '.data' data readable writeable


<syntaxhighlight lang="bash">
Handle dq ?
$ qemu-system-x86_64 -cpu qemu64 \
SystemTable dq ?
-drive if=pflash,format=raw,unit=0,file=path_to_OVMF_CODE.fd,readonly=on \
_hello db 'Hello World',13,10,0
-drive if=pflash,format=raw,unit=1,file=path_to_OVMF_VARS.fd \
-net none
</syntaxhighlight>


If you prefer to work on a terminal without a display, or via SSH/telnet, you will want to run QEMU without graphics support, using the <code>-nographic</code> flag.
section '.reloc' fixups data discardable

</source>
If OVMF does not find a boot disk with a properly named UEFI application (more on this later) it will drop into a UEFI shell.
Instead of efi.inc, you can use our own [[uefi.inc]] which is a lot more friendlier, and aimed at boot loader purposes.

<source lang="asm">
[[file:OVMF_shell.png]]

You can find a list of shell commands [http://www.sysadminshare.com/2012/01/efi-shell-commands.html here] or you can type ''help'' at the shell.

===Creating disk images===
:{{Main|Bootable Disk}}

To launch a UEFI application you will need to create a disk image and present it to QEMU. UEFI firmware expects UEFI applications to be stored in a FAT12, FAT16, or FAT32 file system on a [[GPT]] or [[MBR]]-partitioned disk. Many firmwares only support FAT32, so that's what you'll want to use. Depending on your platform, there are several different ways to create a disk image containing your UEFI application, but they all start by creating a zeroed disk image file. The minimum FAT32 partition size is 33,548,800 bytes, plus you will need space for the primary and secondary GPT tables, plus some slack space so the partition can be aligned correctly. Throughout these examples we will be creating a 48,000,000 byte (93750 512-byte sectors, or 48 MB) disk image.
<syntaxhighlight lang="bash">
$ dd if=/dev/zero of=/path/to/uefi.img bs=512 count=93750
</syntaxhighlight>

====uefi-run helper application====
The uefi-run application is useful for quick testing. It creates a temporary FAT image containing your EFI application and starts qemu.

<syntaxhighlight lang="bash">
$ uefi-run -b /path/to/OVMF.fd -q /path/to/qemu app.efi -- <extra_qemu_args>
</syntaxhighlight>

uefi-run is not currently packaged for any distribution. You can install it using cargo (the Rust package manager) though ("cargo install uefi-run").

====Linux, root required====
This approach requires root privileges and uses '''gdisk''', '''losetup''', and '''mkdosfs'''. First, use gdisk to create a GPT partition table with a single [[EFI System Partition]].
<syntaxhighlight lang="bash">
$ gdisk /path/to/uefi.img
GPT fdisk (gdisk) version 0.8.10

Partition table scan:
MBR: not present
BSD: not present
APM: not present
GPT: not present

Creating new GPT entries.

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y

Command (? for help): n
Partition number (1-128, default 1): 1
First sector (34-93716, default = 2048) or {+-}size{KMGTP}: 2048
Last sector (2048-93716, default = 93716) or {+-}size{KMGTP}: 93716
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef00
Changed type of partition to 'EFI System'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to uefi.img.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.
</syntaxhighlight>

Now you have disk image with [[GPT|GUID partition table]] on it and an unformatted EFI partition starting at sector 2048. Unless you deviated from the commands shown above, the disk image will use 512-byte sectors, so the EFI partition starts at byte 1,048,576 and is 46,934,528 bytes in length. Use losetup to present the partition to Linux on a loopback device.

<syntaxhighlight lang="bash">
losetup --offset 1048576 --sizelimit 46934528 /dev/loop0 /path/to/uefi.img
</syntaxhighlight>

(If /dev/loop0 is already in use you will need to select a different loopback device.)

Format the partition for FAT32 with mkdosfs.
<syntaxhighlight lang="bash">
mkdosfs -F 32 /dev/loop0
</syntaxhighlight>

The partition can now be mounted, so that we can copy files to it. In this example we use the "/mnt" directory, but you could also create a local directory for temporary use.
<syntaxhighlight lang="bash">
mount /dev/loop0 /mnt
</syntaxhighlight>

Copy any UEFI applications you want to test to the file system.
<syntaxhighlight lang="bash">
$ cp /path/to/main.efi /mnt/
$ ...
</syntaxhighlight>

Finally, unmount the partition and free the loopback device.
<syntaxhighlight lang="bash">
$ umount /mnt
$ losetup -d /dev/loop0
</syntaxhighlight>

''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT32 file system, containing one or more UEFI applications.

====Linux, root not required====
This approach uses '''parted''', '''mformat''', and '''mcopy''' and can be performed with user privileges. First, use parted to create primary and secondary GPT headers, and a single EFI partition spanning the same range as the approach above.
<syntaxhighlight lang="bash">
$ parted /path/to/uefi.img -s -a minimal mklabel gpt
$ parted /path/to/uefi.img -s -a minimal mkpart EFI FAT16 2048s 93716s
$ parted /path/to/uefi.img -s -a minimal toggle 1 boot
</syntaxhighlight>

Now create a new temporary image file that will contain the EFI partition data and use mformat to format it with FAT16.
<syntaxhighlight lang="bash">
dd if=/dev/zero of=/tmp/part.img bs=512 count=91669
mformat -i /tmp/part.img -h 32 -t 32 -n 64 -c 1
</syntaxhighlight>

Use mcopy to copy any UEFI applications you want to test to the file system.
<syntaxhighlight lang="bash">
$ mcopy -i /tmp/part.img /path/to/main.efi ::
$ ...
</syntaxhighlight>

Finally, write the partition image into the main disk image.
<syntaxhighlight lang="bash">
$ dd if=/tmp/part.img of=/path/to/uefi.img bs=512 count=91669 seek=2048 conv=notrunc
</syntaxhighlight>

''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications.

====FreeBSD, root required====
This approach requires root privileges and uses '''mdconfig''', '''gpart''', '''newfs_msdos''', and '''mount_msdosfs'''. First, create a device node that presents the zeroed disk image as a block device. This will let us work on it using standard partitioning and formatting tools.
<syntaxhighlight lang="bash">
$ mdconfig -f /path/to/uefi.img
md0
</syntaxhighlight>

In this example the new block device is ''md0''. Now create the empty primary and secondary GPT tables on the device.
<syntaxhighlight lang="bash">
$ gpart create -s GPT md0
md0 created
</syntaxhighlight>

Now we can add a partition to the disk. We'll specify an "EFI" partition, which just means that GPT will set that partition's GUID to the special "EFI" type. Not all BIOSs require this, and the partition will still be able to be mounted and browsed normally on Linux, FreeBSD, and Windows.
<syntaxhighlight lang="bash">
$ gpart add -t efi md0
md0p1 added
</syntaxhighlight>

Next, create a FAT16 file system on the new partition. You can specify various parameters for the file system if you'd like, but it isn't necessary. Ideally you would create a FAT32 partition for best firmware compatibility but FreeBSD seems to create FAT32 partitions that OVMF can't read.
<syntaxhighlight lang="bash">
$ newfs_msdos -F 16 md0p1
newfs_msdos: trim 2 sectors to adjust to a multiple of 9
/dev/md2p1: 93552 sectors in 11694 FAT16 clusters (4096 bytes/cluster)
BytesPerSec=512 SecPerClust=8 ResSectors=1 FATs=2 RootDirEnts=512 Media=0xf0 FATsecs=46 SecPerTrack=9 Heads=16 HiddenSecs=0 HugeSectors=93681
</syntaxhighlight>

The partition can now be mounted, so that we can copy files to it. In this example we use the ''/mnt'' directory, but you could also create a local directory for temporary use.
<syntaxhighlight lang="bash">
$ mount_msdosfs /dev/md0p1 /mnt
</syntaxhighlight>

Copy any UEFI applications you want to test to the file system.
<syntaxhighlight lang="bash">
$ cp /path/to/main.efi /mnt/
$ ...
</syntaxhighlight>

Finally, unmount the partition and free the block device.
<syntaxhighlight lang="bash">
$ umount /mnt
$ mdconfig -d -u md0
</syntaxhighlight>

''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications.

====Mac OS (root not required)====
Mac OS has a single tool (hdiutil) that creates the disk image and copy files at same time.

Let's say that you're creating a UEFI boot for x86_64. By definition the file name should be BOOTX64.EFI and this file should be located in the /EFI/BOOT folder.

First, let's create a temporary folder that will contains all files and folders required for booting UEFI.
<syntaxhighlight lang="bash">
$ mkdir -p diskImage/EFI/BOOT
</syntaxhighlight>

Secondly, let's copy the boot application to the required location:

<syntaxhighlight lang="bash">
$ cp bootx64.efi diskImage/EFI/BOOT/BOOTX64.EFI
</syntaxhighlight>

Finally, let's create a disk image partitioned with GPT, formatted with fat32 (-fs fat32), overriding destination file if needed (-ov), define disk size (-size 48m), define volume name (-volname NEWOS), the file format which the disk will be encoded (-format UDTO - the same used for DVDs/CDs) and the source folder containing the files that will be copied to the new disk:

<syntaxhighlight lang="bash">
$ hdiutil create -fs fat32 -ov -size 48m -volname NEWOS -format UDTO -srcfolder diskImage uefi.cdr
</syntaxhighlight>

uefi.cdr should be ready to be used by QEMU.

===Launching UEFI applications===
Once your disk image is ready, you can invoke QEMU as below.
<syntaxhighlight lang="bash">
$ qemu-system-x86_64 -cpu qemu64 -bios /path/to/OVMF.fd -drive file=uefi.disk,if=ide
</syntaxhighlight>

When OVMF drops into the UEFI shell, you will see an additional entry in the "Mapping table", labeled "FS0". This indicates that the firmware detected the disk, discovered the partition, and was able to mount the file system. You can explore the file system by switching to it using the DOS-style syntax "FS0:", as illustrated below.

[[File:OVMF_browse_fs.png]]

You can launch a UEFI application by entering its name.

[[File:OVMF_run_app.png]]

Notice that the UEFI shell resumed once the application terminated. Of course if this was a proper bootloader it would never resume, but rather launch an OS.

Some commercial UEFI firmware provides UEFI shells or the capability to launch user-selected UEFI applications, such as the firmware that ships with the HP EliteBook line of laptops. Most, however, do not expose this functionality to the end-user.

===Debugging===
OVMF can be built in debug mode, and it will output logging messages to IO port 0x402.
You can use some flags like the ones below to capture the output.

<code>-debugcon file:uefi_debug.log -global isa-debugcon.iobase=0x402</code>

Note that release builds will not output debug messages, or will have reduced output.

See [[Debugging UEFI applications with GDB]].

==Running on real hardware==
===NVRAM variables===
{{Main|UEFI NVRAM}}
A UEFI firmware will present most of its configuration options through a text or graphical configuration menu, just like a legacy BIOS. Selections made in these menus are stored in the NVRAM chip between reboots. Unlike legacy BIOS, however, the firmware developer has the option to expose some or all of these "NVRAM variables" to the OS and end-user via convenience functions made resident in RAM by the firmware at boot.

The Linux '''efivarfs''' kernel module will use these functions to list NVRAM variables in the ''/sys/firmware/efi/efivars'' file. NVRAM variables can also be dumped from within the UEFI shell itself with the '''dmpstore''' command. Device boot order is always accessible via NVRAM variables. The Linux program '''efibootmgr''' works specifically with the boot order NVRAM variables. The UEFI shell offers the '''bcfg''' command for the same purpose.

===Bootable UEFI applications===
The boot order NVRAM variables determine where firmware will look for UEFI applications to be launched at boot. Although this can be changed (for example, an OS installer might customize the boot entry for the hard drive to which it was installed) firmware typically looks for a UEFI application named "BOOTIA32.efi" (for 32-bit applications) or "BOOTX64.efi" (for 64-bit applications) stored in the "/EFI/BOOT" path in the boot device's file system. This is the default path and name for OVMF.

Unlike a UEFI application launched from the shell, if a bootable UEFI application returns BIOS will continue searching for other boot devices.

===Exposed functionality===
Real PCs vary in the amount of UEFI capability they expose to the user. For example, even a class 3 machine may not make any mention of UEFI in its BIOS configuration and may not offer a UEFI shell. Additionally, some BIOS vendors make their UEFI firmware configuration screens look identical to their legacy BIOS configuration screens. Class 2 machines may present somewhat confusing boot menus and configuration options. For example, one laptop manufacturer includes a configuration option to enable/disable UEFI (i.e. switch between UEFI and CSM behavior), named "OS: Windows 8". Another laptop, if it fails to find a bootable UEFI application on the selected boot device (or if that application returns a status other than EFI_SUCCESS) will fall back to CSM behavior and then complain that the drive has a corrupted MBR. With time, and with the emergence of class 3 machines, clarity of UEFI boot behavior will improve.

To make testing on real hardware easier, you can install a bootable UEFI application to the internal hard drive of the system that provides a boot menu, such as [http://www.rodsbooks.com/refind/ rEFInd]. This may also be convenient for multi-boot scenarios.

===PC firmware developers===
On x86 and x86-64 platforms, the following BIOS developers offer UEFI firmware:
* AMI (Aptio).
* Phoenix (SecureCore, TrustedCore, AwardCore).
* Insyde (InsydeH20).

== UEFI applications in detail ==
=== Binary Format ===
UEFI executables are regular PE32 / PE32+ (Windows x32 / x64) images, with a specific '''subsystem'''.
Every UEFI application is basically a windows EXE (or DLL) without symbol tables.

{| class="wikitable"
|+ Types of UEFI images
|-
! Type
! Description
! Subsystem
|-
| Applications || OS loaders and other utility programs. || 10
|-
| Boot service driver || Drivers used by the firmware when booting (e.g. disk drivers, network drivers). || 11
|-
| Runtime driver || Drivers which may stay loaded even after the OS loads and exits the boot services. || 12
|}

UEFI images must also specify the type of machine code they contain. A UEFI loader will refuse to boot an incompatible image.

{| class="wikitable"
|+ Types of machines
|-
! Name / arch
! Value
|-
|x86||0x014c
|-
|x86_64||0x8664
|-
|Itanium x64||0x0200
|-
|UEFI Byte Code||0x0EBC
|-
|ARM<sup>1</sup>||0x01C2
|-
|AArch (ARM x64)||0xAA64
|-
|RISC-V x32||0x5032
|-
|RISC-V x64||0x5064
|-
|RISC-V x128||0x5128
|}

[1] ARM means you can use Thumb / Thumb 2 instructions, but UEFI interfaces are in ARM mode.

==== Initialization ====
Applications must either load an OS and exit boot services, or return from the main function (in which case the boot loader will look for the next app to load).

Drivers must initialize and then return 0 on success, or an error code. A computer might fail to boot if a required driver fails to load.

==== Memory ====
The memory map returned by UEFI will mark the memory areas which drivers use.

Once your OS loader finished, your kernel is allowed to reuse the memory where the boot loader was loaded.

The memory types are <code>Efi{Loader/BootServices/RuntimeServices}{Code/Data}</code>.

After exiting the boot services, you may reuse whatever non-read-only memory the boot drivers used.

However, memory used by the runtime drivers must '''never''' be touched - the runtime drivers stay active and loaded for as long as the computer runs.

One way to see a breakdown of a PE file containing a UEFI application is by<syntaxhighlight lang="bash">$ objdump --all-headers /path/to/main.efi</syntaxhighlight>
Its output is quite long. Among other things, it shows the '''subsystem''', that is the type of the UEFI image, mentioned earlier.

=== Calling Conventions ===
UEFI specifies the following calling conventions:
* '''cdecl''' for x86 UEFI functions
* '''Microsoft's 64-bit calling convention''' for x86-64 UEFI functions
* '''SMC''' for ARM UEFI functions

This has two impacts on UEFI application developers:
* A UEFI application's main entry point must expect to be called with the corresponding calling convention.
* Any UEFI-provided functions that a UEFI application calls must be called with the corresponding calling convention.
Note that functions strictly internal to the application can use whatever calling convention the developer chooses.

==== POSIX-UEFI, GNU-EFI and GCC ====
{{Main|GNU-EFI}}
cdecl is the standard calling convention used by GCC, so no special attributes or modifiers are needed for writing the main entry point or calling UEFI functions in an x86 UEFI application developed with GNU-EFI. For x86-64, however, the entry point function must be declared with the "___attribute___((ms_abi))" modifier and all calls to UEFI-provided functions must be made through the "uefi_call_wrapper" thunk. This thunk is called with cdecl, but then translates to the Microsoft x86-64 calling convention before calling the requested UEFI function. This is necessary because older releases of GCC do not support specifying calling conventions for function pointers.

For [[POSIX-UEFI]], which also uses GCC, your entry point looks like the standard main(), and no special ABI is required. Also the build environment takes care of the compiler flags for you, so you can simply call UEFI functions without "uefi_call_wrapper", no matter if you're using the host gcc or a cross-compiler.

For developer convenience, both POSIX-UEFI and GNU-EFI provides the "EFIAPI" macro, which expands to "cdecl" when targeting x86 and "__attribute__(ms_abi))" when targeting x86-64. Additionally, the "uefi_call_wrapper" thunk will simply pass the call through on x86. This allows the same source code to target x86 and x86-64. For example, the following main function will compile with the correct calling convention on both x86 and x86-64 and the call through the "uefi_call_wrapper" thunk will select the correct calling convention to use when calling the UEFI function (in this case, printing a string).
<syntaxhighlight lang="c">
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
EFI_STATUS status = uefi_call_wrapper(SystemTable->ConOut->OutputString,
2,
SystemTable->ConOut,
L"Hello, World!\n");
return status;
}
</syntaxhighlight>

=== Language binding ===
UEFI applications are typically written in C, although bindings could be written for any other language that compiles to machine code. Assembly is also an option; a [[uefi.inc]] is available for [[FASM]] that allows UEFI applications to be written as below.
<syntaxhighlight lang="asm">
format pe64 dll efi
format pe64 dll efi
entry main
entry main
Line 318: Line 595:
section '.data' data readable writeable
section '.data' data readable writeable


_hello db 'Hello World',13,10,0
_hello du 'Hello World',13,10,0


section '.reloc' fixups data discardable
section '.reloc' fixups data discardable
</syntaxhighlight>
</source>

As a UEFI application contains normal x86 or x86-64 machine code, inline assembly is also an option in compilers that support it.

=== EFI Byte Code===
UEFI also includes a virtual machine specification based on a byte code format called EFI Byte Code (EBC), which can be used to write platform-independent device drivers, but not UEFI applications. As of 2015 there has been limited use of EBC.

== Common Problems ==

=== My UEFI application hangs/resets after about 5 minutes ===


When control is handed to your UEFI application by firmware, it sets a watchdog timer for 5 minutes, after which the firmware is reinvoked as it assumes your application has hung. The firmware in this case will normally try to reset the system (although the OVMF firmware in VirtualBox simply causes the screen to go black and hang). To counteract this, you are required to refresh the watchdog timer before it times out. Alternatively, you can disable it completely with code like <syntaxhighlight lang="C">SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);</syntaxhighlight>Obviously this is not a problem for most bootloaders, but can cause an issue if you have an interactive loader which waits for user input. Also note that you are required to disable the watchdog timer if you exit back to the firmware.
== Known Firmware Bugs ==


=== My bootloader hangs if I use user defined EFI_MEMORY_TYPE values ===
For the memory management functions in EFI, an OS is meant to be able to use "memory type" values above 0x80000000 for its own purposes. In the OVFM EFI firmware release "r11337" (for Qemu, etc) there is a bug where the firmware assumes the memory type is within the range of values defined for EFI's own use, and uses the memory type as an array index. The end result is an "array index out of bounds" bug; where the higher memory type values (e.g. perfectly legal values above 0x80000000) cause the 64-bit version of the firmware to crash (page fault), and cause incorrect "attribute" values to be reported by the 32-bit version of the firmware. This same bug is also present in whatever version of the EFI firmware VirtualBox uses (which looks like an older version of OVFM); and I suspect (but don't know) that the bug may be present in a wide variety of firmware that was derived from the tianocore project (not just OVFM). [[User:Brendan|Brendan]] 15:30, 29 July 2011 (UTC)


For the memory management functions in EFI, an OS is meant to be able to use "memory type" values above 0x80000000 for its own purposes. In the OVMF EFI firmware release "r11337" (for Qemu, etc) there is a bug where the firmware assumes the memory type is within the range of values defined for EFI's own use, and uses the memory type as an array index. The end result is an "array index out of bounds" bug; where the higher memory type values (e.g. perfectly legal values above 0x80000000) cause the 64-bit version of the firmware to crash (page fault), and cause incorrect "attribute" values to be reported by the 32-bit version of the firmware. This same bug is also present in whatever version of the EFI firmware VirtualBox uses (which looks like an older version of OVMF); and I suspect (but don't know) that the bug may be present in a wide variety of firmware that was derived from the tianocore project (not just OVMF).


== See also ==
== See also ==
=== Wikipedia ===
=== Articles ===
* [[UEFI Bare Bones]]
* [[Wikipedia:Extensible_Firmware_Interface|EFI.]]
* [[EFI System Partition]]
* [[PE]] file format
* [[EDK2]]
* [[POSIX-UEFI]]
* [[GNU-EFI]]
* [https://github.com/nebulaeonline/nebulae/tree/UefiBarebones Uefi Barebones MSVC/Clang/Visual Studio]


=== External Links ===
=== External Links ===
* [http://www.intel.com/technology/efi Intel EFI specifications et al.]
* [https://uefi.org/specifications UEFI specifications et al.]
* [http://www.uefi.org/home UEFI specifications et al.]
* [http://www.tianocore.org/ Intel TianoCore EDK2]
* [https://github.com/tianocore/tianocore.github.io/wiki/OVMF OVMF firmware images] for use with [[QEMU]]
* [http://x86asm.net/articles Several articles about UEFI]
* [https://web.archive.org/web/20181028201454/http://wiki.phoenix.com/wiki/index.php/Main_Page Phoenix UEFI Wiki]
* [http://www.microsoft.com/whdc/system/platform/firmware/pecoff.mspx PE specification covering the (U)EFI binary format]
* [http://uefi.blogspot.com/ Blog about UEFI, with bits about UEFI development]
* [http://x86asm.net/articles/others/index.html Several articles about UEFI]
* [https://www.microsoft.com/en-us/download/details.aspx?id=19509 PE specification covering the (U)EFI binary format]
* [http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=UDK2010 UEFI Development Kit 2010]
* [https://uefi.blogspot.com/ Blog about UEFI, with bits about UEFI development]
* [http://internshipatdell.wikispaces.com/file/view/How+to+build+an+UEFI+application.pptx Presentation guiding through simple UEFI application setup]
* [https://web.archive.org/web/20160316192235/http://internshipatdell.wikispaces.com/file/view/How+to+build+an+UEFI+application.pptx Presentation guiding through simple UEFI application setup]
* [https://uefi.org/sites/default/files/resources/UEFI-Plugfest-WindowsBootEnvironment.pdf Presentation giving an overview of windows uefi booting]
* [https://gitlab.com/bztsrc/posix-uefi POSIX-UEFI] documentation and source
* [[Wikipedia:Extensible_Firmware_Interface|Wikipedia Article on EFI]]


[[Category:X86]]
[[Category:x86]]
[[Category:X86-64]]
[[Category:x86-64]]
[[Category:IA-64]]
[[Category:ARM]]
[[Category:ARM]]
[[Category:Firmware]]
[[Category:Firmware]]
[[Category:UEFI]]
[[de:Unified_Extensible_Firmware_Interface]]

Latest revision as of 15:54, 9 June 2024

(U)EFI or (Unified) Extensible Firmware Interface is a specification for x86, x86-64, ARM, and Itanium platforms that defines a software interface between the operating system and the platform firmware/BIOS. The original EFI was developed in the mid-1990s by Intel for use developing firmware/BIOS for Itanium platforms. In 2005 Intel transitioned the specification to a new working group called the Unified EFI Forum, consisting of companies such as AMD, Microsoft, Apple, and Intel itself. All modern PCs ship with UEFI firmware and UEFI is widely supported by both commercial and open source operating systems. Backwards compatibility is provided for legacy operating systems.

UEFI basics

Downloading UEFI images

If you choose VirtualBox for virtualization, then UEFI is already included, no need to download the image manually. You just have to enable it in the VM's preferences by clicking "Settings" / "Systems" / "Enable EFI (special OSes only)" checkbox.

Otherwise for emulation and virtual machines, you'll need an OVMF.fd firmware image. It might be tricky to find, so here are some alternative download links too:

Under Linux, you can also install these with your distro's package manager, for example:

Debian / Ubuntu

# apt-get install ovmf

RedHat / CentOS

# yum install ovmf

MacOS

Use the OVMF-blobs repo.

Windows

Use the OVMF-blobs repo or download the RPM version, then use 7-Zip File Manager to extract the OVMF.fd file from the downloaded archive.

UEFI vs. legacy BIOS

A common misconception is that UEFI and BIOS are mutually exclusive. In reality, both legacy motherboards and UEFI-based motherboards both include BIOS ROMs. The differences are in where they find the bootloader/OS, how they prepare the system before executing it, and what convenience functions they provide.

Platform initialization

On a legacy system, BIOS performs all the usual platform initialization (memory controller configuration, PCI bus configuration and BAR mapping, graphics card initialization, etc.), but then drops into a backwards-compatible real mode environment. The bootloader must enable the A20 gate, configure a GDT and an IDT, switch to protected mode, and for x86-64 CPUs, configure paging and switch to long mode.

UEFI firmware performs those same steps, but it also enables the A20 gate and prepares either protected mode environment with flat segmentation (for 32-bit x86 processors) or a long mode environment with identity-mapped paging (for x86-64 processors).

Additionally, the platform initialization procedure of UEFI firmware is standardized. This allows UEFI firmware to be extended in a vendor-neutral way.

Boot mechanism

A legacy BIOS loads a 512 byte flat binary blob from the MBR of the boot device into memory at physical address 7C00 and jumps to it. The bootloader cannot return back to BIOS. UEFI firmware loads an arbitrary sized UEFI application (a relocatable PE executable file) from a FAT partition on a GPT or MBR partitioned boot device to some address selected at run-time. Then it calls that application's main entry point. The application can return control to the firmware, which will continue searching for another boot device or bring up a diagnostic menu.

System discovery

A legacy bootloader scans memory for structures like the EBDA, SMBIOS, and ACPI tables. It uses PIO to talk to the root PCI controller and scan the PCI bus. It is possible that redundant tables may be present in memory (for example, the MP table in the SMBIOS contains information that's also present in the ACPI DSDT and MADT) and the bootloader can choose which to use.

When UEFI firmware calls a UEFI application's entry point function, it passes a "System Table" structure, which contains pointers to all of the system's ACPI tables, memory map, and other information relevant to an OS. Legacy tables (like MP tables) may not be present in memory.

Convenience functions

A legacy BIOS hooks a variety of interrupts which a bootloader can trigger to access system resources like disks and the screen. These interrupts are not standardized, except by historical convention. Each interrupt uses a different register passing convention.

UEFI firmware establishes many callable functions in memory, which are grouped into sets called "protocols" and are discoverable through the System Table. The behavior of each function in each protocol is defined by specification. UEFI applications can define their own protocols and persist them in memory for other UEFI applications to use. Functions are called with a standardized, modern calling convention supported by many C compilers.

Development environment

Legacy bootloaders can be developed in any environment that can generate flat binary images: NASM, GCC, etc. UEFI applications can be developed in any language that can be compiled and linked into a PE executable file and supports the calling convention used to access functions established in memory by the UEFI firmware. In practice this means one of three development environments: EDK2, GNU-EFI or POSIX-UEFI.

EDK2 is a large and complex, yet feature filled environment with its own build system. It can be configured to use GCC, LLVM, MinGW, Microsoft Visual C++, etc. as a cross-compiler. Not only can it be used to compile UEFI applications, but it can also be used to compile UEFI firmware to be flashed to a BIOS ROM.

GNU-EFI is a set of libraries and headers for compiling UEFI applications with a system's native GCC (does not work with LLVM CLang). It can't be used to compile UEFI firmware. Since it's just a couple of libraries against which a UEFI application can be linked, it is much easier to use than TianoCore ( EDK2 ).

POSIX-UEFI is very similar to GNU-EFI, but it is distributed mainly as a source, not as a binary library, has ANSI C like names and works with GCC as well as LLVM CLang. It's shipped with a Makefile that sets up the compiler flags for you.

Emulation

OVMF, a popular open source UEFI firmware, has been ported to the QEMU (but not Bochs) emulated machine. Because it implements the UEFI specification, it behaves very similarly to commercial UEFI firmware on real machines. (OVMF itself is built with TianoCore, and its source ship with is as well, but pre-built images are available.)

Legacy bootloader or UEFI application?

If you are targeting legacy systems for which UEFI is not available or is not reliable, you should develop a legacy bootloader. This requires intimate knowledge of 16-bit addressing and the backwards-compatibility features of an x86 or x86-64 CPU. If you are targeting modern systems you should develop a UEFI application. Many UEFI firmwares can be configured to emulate a legacy BIOS, but there is even more variation among these emulated environments than among real legacy BIOS.

Although there is a slight learning curve to become familiar with the UEFI development environments, using the System Table, and accessing UEFI-provided protocols (functions), there are far fewer "gotchas" than trying to remain compatible with the wide variety of quickly-becoming-obsolete legacy BIOSs on real machines. UEFI is the standard for all modern PCs.

UEFI class 0-3 and CSM

PCs are categorized as UEFI class 0, 1, 2, or 3. A class 0 machine is a legacy system with a legacy BIOS; i.e. not a UEFI system at all.

A class 1 machine is a UEFI system that runs exclusively in Compatibility Support Module (CSM) mode. CSM is a specification for how UEFI firmware can emulate a legacy BIOS. UEFI firmware in CSM mode loads legacy bootloaders. A class 1 UEFI system may not advertise UEFI support at all, since it isn't exposed to the bootloader. It's only UEFI "within" the BIOS.

A class 2 machine is a UEFI system that can launch UEFI applications but also includes the option to run in CSM mode. The majority of modern PCs are UEFI class 2 machines. Sometimes the choice to run UEFI applications vs. CSM is a one-or-the-other setting in the BIOS configuration, and other times the BIOS will decide which to use after selecting the boot device and checking whether it has a legacy bootloader or a UEFI application.

A class 3 machine is a UEFI system that does not support CSM. UEFI class 3 machines only run UEFI applications and do not implement CSM for backwards compatibility with legacy bootloaders.

Secure Boot

Secure Boot is a digital signature scheme for UEFI applications that consists of four components:

  • PK: Platform Key
  • KEK: Key Exchange Keys
  • db: Whitelist database
  • dbx: Blacklist database

UEFI firmware that supports Secure Boot is always in one of three states:

  • Setup mode, Secure Boot off
  • User mode, Secure Boot off
  • User mode, Secure Boot on

In setup mode, any UEFI application can change or delete the PK, add/remove keys from the KEK, and add/remove whitelist or blacklist entries from the db or dbx.

In user mode, regardless of whether Secure Boot is on or off:

  • The PK may only be changed or deleted by a UEFI application that already has the current PK.
  • Keys can only be added/removed from the KEK by a UEFI application that has the PK.
  • Whitelist and blacklist entries can only be added/removed from the db and dbx by a UEFI application that has any one of the keys in the KEK.

Finally, in user mode with Secure Boot on, UEFI applications must meet one of the following four requirements to be launched:

  • Signed, with signature in db and not in dbx
  • Signed by a key in db and not in dbx
  • Signed by a key in the KEK
  • Unsigned, but a hash of the application is in db and not in dbx

Note that UEFI applications are not signed by the PK, unless the PK also happens to be in the KEK.

Not all UEFI firmwares support Secure Boot, although it is a requirement for Windows 8. Some UEFI firmwares support Secure Boot and there's no option to be disabled, which poses a problem for independent developers that do not have access to the PK or any of the keys in the KEK, and therefore can't install their own key or application signature or hash to the whitelist database. Independent developers should develop on systems that either do not support Secure Boot or has an option for Secure Boot to be turned off.

An easy way out to use a loader that is signed by Microsoft, and allows you to load another binary signed by a key and certificate owned by you (called MOK, Machine Owner's Key). Such a loader is shim, used by RedHat, Fedora, Suse, Ubuntu, Arch and many other distros to load GRUB. The filename of the EFI executable is hardwired in shim, but if you rename your loader to GRUBX64.EFI (or GRUBIA32.EFI), you sign it with your MOK key and certificate using sbsigntool, then you can load any loader in Secure Boot you want.

How to use UEFI

Traditional operating systems like Windows and Linux have an existing software architecture and a large code base to perform system configuration and device discovery. With their sophisticated layers of abstraction they don't directly benefit from UEFI. As a result, their UEFI bootloaders do little but prepare the environment for them to run.

An independent developer may find more value in using UEFI to write feature-full UEFI applications, rather than viewing UEFI as a temporary start-up environment to be jettisoned during the boot process. Unlike legacy bootloaders, which typically interact with BIOS only enough to bring up the OS, a UEFI application can implement sophisticated behavior with the help of UEFI. In other words, an independent developer shouldn't be in a rush to leave "UEFI-land".

A good starting point is writing a UEFI application that uses the System Table to fetch a memory map, and uses the "File" protocol to read files from FAT-formatted disks. The next step might be to use the System Table to locate ACPI tables.

Developing with POSIX-UEFI

Main article: POSIX-UEFI

One option to compile UEFI applications on POSIX like systems is POSIX-UEFI. It provies a libc-like API for your EFI application, and ships with a Makefile that can detect and set up the toolchain for you. It can use GCC or LLVM, and defaults to using the host compiler, but a cross compiler is still recommended.

It uses POSIX style typedefs (like uintn_t instead of UINTN), and it does not ship with the standard EFI headers. You can still get interfaces not covered by POSIX-UEFI (such as GOP) by installing the EFI headers from GNU-EFI or EDK2. Also, it compiles with the MS ABI, meaning that UEFI services can be called natively (i.e., without uefi_call_wrapper) so long as your apps are compiled with it as well. The traditional "Hello, world" UEFI program goes like this.

#include <uefi.h>
 
int main (int argc, char **argv)
{
  printf("Hello, world!\n");
  return 0;
}

Makefile looks like this:

TARGET = main.efi
include uefi/Makefile

Run make to build it. The result of this process is a PE executable file main.efi.

Developing with GNU-EFI

Main article: GNU-EFI

GNU-EFI can be used to develop both 32-bit and 64-bit UEFI applications. This section will address 64-bit UEFI applications only, and assumes that the development environment itself is running on an x86_64 system, so that no cross-compiler is needed. For a more thorough walk-through of a proper (non-gnu-efi) development environment, see UEFI App Bare Bones.

GNU-EFI includes four things:

  • crt0-efi-x86_64.o: A CRT0 (C runtime initialization code) that provides an entry point that UEFI firmware will call when launching the application, which will in turn call the "efi_main" function that the developer writes.
  • libgnuefi.a: A library containing a single function (_relocate) that is used by the CRT0.
  • elf_x86_64_efi.lds: A linker script used to link ELF binaries into UEFI applications.
  • efi.h and other headers: Convenience headers that provide structures, typedefs, and constants improve readability when accessing the System Table and other UEFI resources.
  • libefi.a: A library containing convenience functions like CRC computation, string length calculation, and easy text printing.
  • efilib.h: Header for libefi.a.

At a bare minimum, a 64-bit UEFI application will need to link against crt0-efi-x86_64.o and libgnuefi.a using the elf_x86_64_efi.lds linker script. Most likely you will want to use the provided headers and the convenience library as well, and this section will assume that going forward.

The traditional "Hello, world" UEFI program is shown below.

#include <efi.h>
#include <efilib.h>

EFI_STATUS
EFIAPI
efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
  InitializeLib(ImageHandle, SystemTable);
  Print(L"Hello, world!\n");
  return EFI_SUCCESS;
}

A few notes:

  • efi.h is included so we can use types like EFI_STATUS, EFI_HANDLE, and EFI_SYSTEM_TABLE.
  • When creating a 32-bit UEFI application, EFIAPI is empty; GCC will compile the "efi_main" function using the standard C calling convention. When creating a 64-bit UEFI application, EFIAPI expands to "__attribute__((ms_abi))" and GCC will compile the "efi_main" function using Microsoft's x64 calling convention, as specified by UEFI. Only functions that will be called directly from UEFI (including main, but also callbacks) need to use the UEFI calling convention.
  • "InitializeLib" and "Print" are convenience functions provided by libefi.a with prototypes in efilib.h. "InitializeLib" lets libefi.a store a reference to the ImageHandle and SystemTable provided by BIOS. "Print" uses those stored references to print a string by reaching out to UEFI-provided functions in memory. (Later on we will see how to find and call UEFI-provided functions manually.)

This program is compiled and linked as below.

$ gcc main.c                             \
      -c                                 \
      -fno-stack-protector               \
      -fpic                              \
      -fshort-wchar                      \
      -mno-red-zone                      \
      -I /path/to/gnu-efi/headers        \
      -I /path/to/gnu-efi/headers/x86_64 \
      -DEFI_FUNCTION_WRAPPER             \
      -o main.o

$ ld main.o                         \
     /path/to/crt0-efi-x86_64.o     \
     -nostdlib                      \
     -znocombreloc                  \
     -T /path/to/elf_x86_64_efi.lds \
     -shared                        \
     -Bsymbolic                     \
     -L /path/to/libs               \
     -l:libgnuefi.a                 \
     -l:libefi.a                    \
     -o main.so

$ objcopy -j .text                \
          -j .sdata               \
          -j .data                \
          -j .dynamic             \
          -j .dynsym              \
          -j .rel                 \
          -j .rela                \
          -j .reloc               \
          --target=efi-app-x86_64 \
          main.so                 \
          main.efi

The result of this process is a 44 kB PE executable file main.efi. On a real project you'll probably want to use make or another build tool, and may need to build a cross-compiler. The way GNU-EFI works is a bit contrived: you are wrapping an ELF file built by your normal compiler into PE.

Emulation with QEMU and OVMF

Any recent version of QEMU with a recent version of OVMF will be sufficient to run a UEFI application. QEMU binaries are available for many platforms, and a binary OVMF image (OVMF.fd) can be found on the TianoCore website. QEMU (without any boot disk) can be invoked as below. (To prevent recent versions of QEMU from attempting a PXE (network) boot when no boot disk is found, use -net none.

The recommended way to use OVMF (for QEMU 1.6 or newer) is with a pflash parameter. The instructions below assume you have an OVMF image split into separate CODE and VARS sections.

$ qemu-system-x86_64 -cpu qemu64 \
  -drive if=pflash,format=raw,unit=0,file=path_to_OVMF_CODE.fd,readonly=on \
  -drive if=pflash,format=raw,unit=1,file=path_to_OVMF_VARS.fd \
  -net none

If you prefer to work on a terminal without a display, or via SSH/telnet, you will want to run QEMU without graphics support, using the -nographic flag.

If OVMF does not find a boot disk with a properly named UEFI application (more on this later) it will drop into a UEFI shell.

You can find a list of shell commands here or you can type help at the shell.

Creating disk images

Main article: Bootable Disk

To launch a UEFI application you will need to create a disk image and present it to QEMU. UEFI firmware expects UEFI applications to be stored in a FAT12, FAT16, or FAT32 file system on a GPT or MBR-partitioned disk. Many firmwares only support FAT32, so that's what you'll want to use. Depending on your platform, there are several different ways to create a disk image containing your UEFI application, but they all start by creating a zeroed disk image file. The minimum FAT32 partition size is 33,548,800 bytes, plus you will need space for the primary and secondary GPT tables, plus some slack space so the partition can be aligned correctly. Throughout these examples we will be creating a 48,000,000 byte (93750 512-byte sectors, or 48 MB) disk image.

$ dd if=/dev/zero of=/path/to/uefi.img bs=512 count=93750

uefi-run helper application

The uefi-run application is useful for quick testing. It creates a temporary FAT image containing your EFI application and starts qemu.

$ uefi-run -b /path/to/OVMF.fd -q /path/to/qemu app.efi -- <extra_qemu_args>

uefi-run is not currently packaged for any distribution. You can install it using cargo (the Rust package manager) though ("cargo install uefi-run").

Linux, root required

This approach requires root privileges and uses gdisk, losetup, and mkdosfs. First, use gdisk to create a GPT partition table with a single EFI System Partition.

$ gdisk /path/to/uefi.img
GPT fdisk (gdisk) version 0.8.10

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y

Command (? for help): n
Partition number (1-128, default 1): 1
First sector (34-93716, default = 2048) or {+-}size{KMGTP}: 2048
Last sector (2048-93716, default = 93716) or {+-}size{KMGTP}: 93716
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef00
Changed type of partition to 'EFI System'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to uefi.img.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.

Now you have disk image with GUID partition table on it and an unformatted EFI partition starting at sector 2048. Unless you deviated from the commands shown above, the disk image will use 512-byte sectors, so the EFI partition starts at byte 1,048,576 and is 46,934,528 bytes in length. Use losetup to present the partition to Linux on a loopback device.

losetup --offset 1048576 --sizelimit 46934528 /dev/loop0 /path/to/uefi.img

(If /dev/loop0 is already in use you will need to select a different loopback device.)

Format the partition for FAT32 with mkdosfs.

mkdosfs -F 32 /dev/loop0

The partition can now be mounted, so that we can copy files to it. In this example we use the "/mnt" directory, but you could also create a local directory for temporary use.

mount /dev/loop0 /mnt

Copy any UEFI applications you want to test to the file system.

$ cp /path/to/main.efi /mnt/
$ ...

Finally, unmount the partition and free the loopback device.

$ umount /mnt
$ losetup -d /dev/loop0

uefi.img is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT32 file system, containing one or more UEFI applications.

Linux, root not required

This approach uses parted, mformat, and mcopy and can be performed with user privileges. First, use parted to create primary and secondary GPT headers, and a single EFI partition spanning the same range as the approach above.

$ parted /path/to/uefi.img -s -a minimal mklabel gpt
$ parted /path/to/uefi.img -s -a minimal mkpart EFI FAT16 2048s 93716s
$ parted /path/to/uefi.img -s -a minimal toggle 1 boot

Now create a new temporary image file that will contain the EFI partition data and use mformat to format it with FAT16.

dd if=/dev/zero of=/tmp/part.img bs=512 count=91669
mformat -i /tmp/part.img -h 32 -t 32 -n 64 -c 1

Use mcopy to copy any UEFI applications you want to test to the file system.

$ mcopy -i /tmp/part.img /path/to/main.efi ::
$ ...

Finally, write the partition image into the main disk image.

$ dd if=/tmp/part.img of=/path/to/uefi.img bs=512 count=91669 seek=2048 conv=notrunc

uefi.img is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications.

FreeBSD, root required

This approach requires root privileges and uses mdconfig, gpart, newfs_msdos, and mount_msdosfs. First, create a device node that presents the zeroed disk image as a block device. This will let us work on it using standard partitioning and formatting tools.

$ mdconfig -f /path/to/uefi.img
md0

In this example the new block device is md0. Now create the empty primary and secondary GPT tables on the device.

$ gpart create -s GPT md0
md0 created

Now we can add a partition to the disk. We'll specify an "EFI" partition, which just means that GPT will set that partition's GUID to the special "EFI" type. Not all BIOSs require this, and the partition will still be able to be mounted and browsed normally on Linux, FreeBSD, and Windows.

$ gpart add -t efi md0
md0p1 added

Next, create a FAT16 file system on the new partition. You can specify various parameters for the file system if you'd like, but it isn't necessary. Ideally you would create a FAT32 partition for best firmware compatibility but FreeBSD seems to create FAT32 partitions that OVMF can't read.

$ newfs_msdos -F 16 md0p1
newfs_msdos: trim 2 sectors to adjust to a multiple of 9
/dev/md2p1: 93552 sectors in 11694 FAT16 clusters (4096 bytes/cluster)
BytesPerSec=512 SecPerClust=8 ResSectors=1 FATs=2 RootDirEnts=512 Media=0xf0 FATsecs=46 SecPerTrack=9 Heads=16 HiddenSecs=0 HugeSectors=93681

The partition can now be mounted, so that we can copy files to it. In this example we use the /mnt directory, but you could also create a local directory for temporary use.

$ mount_msdosfs /dev/md0p1 /mnt

Copy any UEFI applications you want to test to the file system.

$ cp /path/to/main.efi /mnt/
$ ...

Finally, unmount the partition and free the block device.

$ umount /mnt
$ mdconfig -d -u md0

uefi.img is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications.

Mac OS (root not required)

Mac OS has a single tool (hdiutil) that creates the disk image and copy files at same time.

Let's say that you're creating a UEFI boot for x86_64. By definition the file name should be BOOTX64.EFI and this file should be located in the /EFI/BOOT folder.

First, let's create a temporary folder that will contains all files and folders required for booting UEFI.

$ mkdir -p diskImage/EFI/BOOT

Secondly, let's copy the boot application to the required location:

$ cp bootx64.efi diskImage/EFI/BOOT/BOOTX64.EFI

Finally, let's create a disk image partitioned with GPT, formatted with fat32 (-fs fat32), overriding destination file if needed (-ov), define disk size (-size 48m), define volume name (-volname NEWOS), the file format which the disk will be encoded (-format UDTO - the same used for DVDs/CDs) and the source folder containing the files that will be copied to the new disk:

$ hdiutil create -fs fat32 -ov -size 48m -volname NEWOS -format UDTO -srcfolder diskImage uefi.cdr

uefi.cdr should be ready to be used by QEMU.

Launching UEFI applications

Once your disk image is ready, you can invoke QEMU as below.

$ qemu-system-x86_64 -cpu qemu64 -bios /path/to/OVMF.fd -drive file=uefi.disk,if=ide

When OVMF drops into the UEFI shell, you will see an additional entry in the "Mapping table", labeled "FS0". This indicates that the firmware detected the disk, discovered the partition, and was able to mount the file system. You can explore the file system by switching to it using the DOS-style syntax "FS0:", as illustrated below.

You can launch a UEFI application by entering its name.

Notice that the UEFI shell resumed once the application terminated. Of course if this was a proper bootloader it would never resume, but rather launch an OS.

Some commercial UEFI firmware provides UEFI shells or the capability to launch user-selected UEFI applications, such as the firmware that ships with the HP EliteBook line of laptops. Most, however, do not expose this functionality to the end-user.

Debugging

OVMF can be built in debug mode, and it will output logging messages to IO port 0x402. You can use some flags like the ones below to capture the output.

-debugcon file:uefi_debug.log -global isa-debugcon.iobase=0x402

Note that release builds will not output debug messages, or will have reduced output.

See Debugging UEFI applications with GDB.

Running on real hardware

NVRAM variables

Main article: UEFI NVRAM

A UEFI firmware will present most of its configuration options through a text or graphical configuration menu, just like a legacy BIOS. Selections made in these menus are stored in the NVRAM chip between reboots. Unlike legacy BIOS, however, the firmware developer has the option to expose some or all of these "NVRAM variables" to the OS and end-user via convenience functions made resident in RAM by the firmware at boot.

The Linux efivarfs kernel module will use these functions to list NVRAM variables in the /sys/firmware/efi/efivars file. NVRAM variables can also be dumped from within the UEFI shell itself with the dmpstore command. Device boot order is always accessible via NVRAM variables. The Linux program efibootmgr works specifically with the boot order NVRAM variables. The UEFI shell offers the bcfg command for the same purpose.

Bootable UEFI applications

The boot order NVRAM variables determine where firmware will look for UEFI applications to be launched at boot. Although this can be changed (for example, an OS installer might customize the boot entry for the hard drive to which it was installed) firmware typically looks for a UEFI application named "BOOTIA32.efi" (for 32-bit applications) or "BOOTX64.efi" (for 64-bit applications) stored in the "/EFI/BOOT" path in the boot device's file system. This is the default path and name for OVMF.

Unlike a UEFI application launched from the shell, if a bootable UEFI application returns BIOS will continue searching for other boot devices.

Exposed functionality

Real PCs vary in the amount of UEFI capability they expose to the user. For example, even a class 3 machine may not make any mention of UEFI in its BIOS configuration and may not offer a UEFI shell. Additionally, some BIOS vendors make their UEFI firmware configuration screens look identical to their legacy BIOS configuration screens. Class 2 machines may present somewhat confusing boot menus and configuration options. For example, one laptop manufacturer includes a configuration option to enable/disable UEFI (i.e. switch between UEFI and CSM behavior), named "OS: Windows 8". Another laptop, if it fails to find a bootable UEFI application on the selected boot device (or if that application returns a status other than EFI_SUCCESS) will fall back to CSM behavior and then complain that the drive has a corrupted MBR. With time, and with the emergence of class 3 machines, clarity of UEFI boot behavior will improve.

To make testing on real hardware easier, you can install a bootable UEFI application to the internal hard drive of the system that provides a boot menu, such as rEFInd. This may also be convenient for multi-boot scenarios.

PC firmware developers

On x86 and x86-64 platforms, the following BIOS developers offer UEFI firmware:

  • AMI (Aptio).
  • Phoenix (SecureCore, TrustedCore, AwardCore).
  • Insyde (InsydeH20).

UEFI applications in detail

Binary Format

UEFI executables are regular PE32 / PE32+ (Windows x32 / x64) images, with a specific subsystem. Every UEFI application is basically a windows EXE (or DLL) without symbol tables.

Types of UEFI images
Type Description Subsystem
Applications OS loaders and other utility programs. 10
Boot service driver Drivers used by the firmware when booting (e.g. disk drivers, network drivers). 11
Runtime driver Drivers which may stay loaded even after the OS loads and exits the boot services. 12

UEFI images must also specify the type of machine code they contain. A UEFI loader will refuse to boot an incompatible image.

Types of machines
Name / arch Value
x86 0x014c
x86_64 0x8664
Itanium x64 0x0200
UEFI Byte Code 0x0EBC
ARM1 0x01C2
AArch (ARM x64) 0xAA64
RISC-V x32 0x5032
RISC-V x64 0x5064
RISC-V x128 0x5128

[1] ARM means you can use Thumb / Thumb 2 instructions, but UEFI interfaces are in ARM mode.

Initialization

Applications must either load an OS and exit boot services, or return from the main function (in which case the boot loader will look for the next app to load).

Drivers must initialize and then return 0 on success, or an error code. A computer might fail to boot if a required driver fails to load.

Memory

The memory map returned by UEFI will mark the memory areas which drivers use.

Once your OS loader finished, your kernel is allowed to reuse the memory where the boot loader was loaded.

The memory types are Efi{Loader/BootServices/RuntimeServices}{Code/Data}.

After exiting the boot services, you may reuse whatever non-read-only memory the boot drivers used.

However, memory used by the runtime drivers must never be touched - the runtime drivers stay active and loaded for as long as the computer runs.

One way to see a breakdown of a PE file containing a UEFI application is by

$ objdump --all-headers /path/to/main.efi

Its output is quite long. Among other things, it shows the subsystem, that is the type of the UEFI image, mentioned earlier.

Calling Conventions

UEFI specifies the following calling conventions:

  • cdecl for x86 UEFI functions
  • Microsoft's 64-bit calling convention for x86-64 UEFI functions
  • SMC for ARM UEFI functions

This has two impacts on UEFI application developers:

  • A UEFI application's main entry point must expect to be called with the corresponding calling convention.
  • Any UEFI-provided functions that a UEFI application calls must be called with the corresponding calling convention.

Note that functions strictly internal to the application can use whatever calling convention the developer chooses.

POSIX-UEFI, GNU-EFI and GCC

Main article: GNU-EFI

cdecl is the standard calling convention used by GCC, so no special attributes or modifiers are needed for writing the main entry point or calling UEFI functions in an x86 UEFI application developed with GNU-EFI. For x86-64, however, the entry point function must be declared with the "___attribute___((ms_abi))" modifier and all calls to UEFI-provided functions must be made through the "uefi_call_wrapper" thunk. This thunk is called with cdecl, but then translates to the Microsoft x86-64 calling convention before calling the requested UEFI function. This is necessary because older releases of GCC do not support specifying calling conventions for function pointers.

For POSIX-UEFI, which also uses GCC, your entry point looks like the standard main(), and no special ABI is required. Also the build environment takes care of the compiler flags for you, so you can simply call UEFI functions without "uefi_call_wrapper", no matter if you're using the host gcc or a cross-compiler.

For developer convenience, both POSIX-UEFI and GNU-EFI provides the "EFIAPI" macro, which expands to "cdecl" when targeting x86 and "__attribute__(ms_abi))" when targeting x86-64. Additionally, the "uefi_call_wrapper" thunk will simply pass the call through on x86. This allows the same source code to target x86 and x86-64. For example, the following main function will compile with the correct calling convention on both x86 and x86-64 and the call through the "uefi_call_wrapper" thunk will select the correct calling convention to use when calling the UEFI function (in this case, printing a string).

EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
  EFI_STATUS status = uefi_call_wrapper(SystemTable->ConOut->OutputString,
                                        2,
                                        SystemTable->ConOut,
                                        L"Hello, World!\n");
  return status;
}

Language binding

UEFI applications are typically written in C, although bindings could be written for any other language that compiles to machine code. Assembly is also an option; a uefi.inc is available for FASM that allows UEFI applications to be written as below.

format pe64 dll efi
entry main

section '.text' code executable readable

include 'uefi.inc'

main:
    ; initialize UEFI library
    InitializeLib
    jc @f

    ; call uefi function to print to screen
    uefi_call_wrapper ConOut, OutputString, ConOut, _hello

@@: mov eax, EFI_SUCCESS
    retn

section '.data' data readable writeable

_hello                                  du 'Hello World',13,10,0

section '.reloc' fixups data discardable

As a UEFI application contains normal x86 or x86-64 machine code, inline assembly is also an option in compilers that support it.

EFI Byte Code

UEFI also includes a virtual machine specification based on a byte code format called EFI Byte Code (EBC), which can be used to write platform-independent device drivers, but not UEFI applications. As of 2015 there has been limited use of EBC.

Common Problems

My UEFI application hangs/resets after about 5 minutes

When control is handed to your UEFI application by firmware, it sets a watchdog timer for 5 minutes, after which the firmware is reinvoked as it assumes your application has hung. The firmware in this case will normally try to reset the system (although the OVMF firmware in VirtualBox simply causes the screen to go black and hang). To counteract this, you are required to refresh the watchdog timer before it times out. Alternatively, you can disable it completely with code like

SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);

Obviously this is not a problem for most bootloaders, but can cause an issue if you have an interactive loader which waits for user input. Also note that you are required to disable the watchdog timer if you exit back to the firmware.

My bootloader hangs if I use user defined EFI_MEMORY_TYPE values

For the memory management functions in EFI, an OS is meant to be able to use "memory type" values above 0x80000000 for its own purposes. In the OVMF EFI firmware release "r11337" (for Qemu, etc) there is a bug where the firmware assumes the memory type is within the range of values defined for EFI's own use, and uses the memory type as an array index. The end result is an "array index out of bounds" bug; where the higher memory type values (e.g. perfectly legal values above 0x80000000) cause the 64-bit version of the firmware to crash (page fault), and cause incorrect "attribute" values to be reported by the 32-bit version of the firmware. This same bug is also present in whatever version of the EFI firmware VirtualBox uses (which looks like an older version of OVMF); and I suspect (but don't know) that the bug may be present in a wide variety of firmware that was derived from the tianocore project (not just OVMF).

See also

Articles

External Links