Sorry, you need to enable JavaScript to visit this website.

The Linux EFI Boot Stub

 Historically a Linux kernel image required a UEFI Linux bootloader to load and execute it. Since release 3.3 the Linux x86 kernel can be executed directly by UEFI firmware. The code that makes this possible is known as the EFI Boot Stub and this article discusses its rationale, design and the pros and cons of using the EFI boot stub.

The bzImage file format

The Linux x86 kernel uses a binary file format known as "bzImage" [1]. A bzImage file is made up of a file header, code, data and a compressed kernel image. For those of you thinking "Huh? The bzImage contains a second kernel image?", the explanation goes like this; the kernel image that is loaded by a bootloader (bzImage) isn't the same as the image that's running at the point you launch your web browser (vmlinux). The two images have completely different jobs and run in very different environments. The bzImage is executed by a bootloader, is very small, and runs in protected mode with paging disabled. The bzImage code has the responsibility of switching 64-bit capable CPUs into long mode (IA32e), setting up the page tables, relocating the bzImage into a place safe to decompress the embedded vmlinux, performing the decompression and finally jumping to the entry point of the vmlinux code. The bzImage doesn't even include code to access the disk; everything runs from
memory. vmlinux on the other hand is the kernel proper. It contains everything you would normally describe as the domain of the kernel, e.g. device drivers, file systems, memory management, the scheduler, etc.
Consequently, it's much larger than a bzImage file.

Since the UEFI firmware does not know how to interpret the bzImage file format, the kernel image is typically loaded by a Linux bootloader, such as Grub [2] or Syslinux [3]. These bootloaders are compiled as UEFI
applications, and are executed automatically by the EFI Boot Manager. The bootloader contains code to parse the bzImage header, and jump to its entry point.

Now this all seems fine, so you may wonder why we'd ever want to take the bootloader out of the equation.

Skipping the bootloader

The main reason is that rarely are bootloaders and the Linux kernel developed in lockstep. Usually support for some new feature is first added to the kernel, then at a later date a new bootloader version makes
use of it. This adds a noticeable delay between the time that the kernel merges the changes and an end user benefits, and this applies equally to bug fixes.

The Linux kernel developers realised that the design for the UEFI interface between the bootloader and kernel was likely to change as more consumer hardware started shipping with UEFI by default. Since the UEFI
boot process has two very distinct phases, namely before and after the call to ExitBootServices(), and since some information and services are not available after, if the bootloader fails to gather all information
required by the kernel, there's no second chance. It became clear that the best place to gather information about the machine was in fact in the bzImage and the solution was to call ExitBootServices() from there.

With most of the early boot work achievable from the bzImage it would be possible to skip the bootloader stage completely if only the firmware knew how to load a bzImage file.

Constructing the PE/COFF file headers

Since UEFI firmware only understands how to run executable PE/COFF files the bzImage header needs to be modified to look like a UEFI application. Fortunately, there are only two fields in a PE/COFF executable image
header that are at mandatory offsets,

1. Offset 0x0, must contain the magic string "MZ"
2. Offset 0x3c, must contain the offset to a PE header

In a bzImage file, offset 0x0 is a legacy entry point used when booting directly from floppy disk,

Direct floppy boot is not supported.
Use a boot loader program instead.

Remove disk and press any key to reboot ...

That code is never needed on UEFI, so we can simply write the "MZ" magic string at offset 0. By rearranging the code and data it is also possible to write the offset to the PE header at 0x3c. The PE header can be
placed anywhere in the file that is convenient, so it's actually fairly trivial to build a file that has both a valid bzImage and PE/COFF header.

Along with the PE and COFF headers it is necessary to build a section table. Three sections re used for EFI boot stub support, ".setup", ".reloc" and ".text". The ".reloc" section is required by some UEFI implementations because they refuse to load an executable without at least one relocation. The ".setup" section contains bzImage data used by the EFI boot stub (which will be described shortly). For example, there
are fields in the ".setup" section that indicate the preferred memory address to relocate the bzImage to before decompression, the amount of memory required for decompression, etc. The ".text" section contains all
the bzImage code, including the EFI boot stub.

The EFI Boot Stub

As mentioned previously, the EFI boot stub contains much of the code that would usually be found in a Linux bootloader, and basically acts as a transition layer between the firmware and the rest of the bzImage
code, performing all the jobs that a bootloader would usually do.

The UEFI firmware passes a command line argument string to all UEFI applications that it invokes, whether through the Boot Manager or from the EFI shell. If no arguments are provided the argument string is
empty. The EFI boot stub must parse this UCS-2 argument string and build a 'struct boot_params' object from it. This object includes pointers to an ASCII representation of the argument string, the address in memory
where the firmware loaded the bzImage, and other information about the machine and runtime environment.

The EFI boot stub runs before ExitBootServices(), it is therefore responsible for storing information about devices that is not available after Boot Services. This includes things like the attributes of the
graphics frame buffer device, extracting the ROM images from PCI devices, and so on.

Next, the EFI boot stub must relocate the bzImage into a region of memory that is safe to decompress the embedded vmlinux image. The actual decompression isn't performed by the EFI boot stub, but is instead done
by some of the bzImage code that runs after the EFI boot stub.

Once all of the above is done the EFI boot stub must build a copy of the memory map to pass to the decompressed vmlinux. The data structure used is based on the legacy E820 map, with a couple of extensions for representing more than 128 entries. Finally, ExitBootServices() is invoked and the firmware transitions from the Boot Services phase to Runtime Services.

Conclusion

The EFI boot stub is not a complete replacement for all bootloaders and it doesn't include things like graphical menus, or file system drivers. However, it has allowed very small bootloaders to be built which
leverage the fact that the firmware knows how to load and execute the bzImage. One such bootloader is Gummiboot [4], which provides the usual features of a bootloader (config files, graphical menus) that are
missing from the EFI boot stub without the traditional baggage required to parse the bzImage.

A limitation of the EFI boot stub scheme is that the bzImage must reside on a partition type that the firmware understands. This essentially restricts the partition type to FAT; while it's technically possible for
systems to ship with a non-FAT file system driver, none seem to have done so. This is contrary to the traditional approach of storing the bzImage on a Linux file system. This restriction isn't inherent in the
design of the EFI boot stub (the bzImage doesn't care where it lives), rather it's a consequence of the fact that the firmware, instead of a Linux bootloader, is loading the bzImage.

The EFI boot stub is a particularly good solution if you wish to simply boot a Linux kernel as quickly as possible, as it cuts out the bootloader stage of the traditional boot process. Furthermore, performing the majority of the boot work in the bzImage code has proven to be a very maintainable solution - changes have been necessary as the number of UEFI implementations have grown, but due to the rapid development cycle of the Linux kernel the EFI boot stub has become the most robust UEFI boot solution.

References

[1] http://en.wikipedia.org/wiki/Vmlinux#bzImage

[2] http://www.gnu.org/software/grub/

[3] http://www.syslinux.org

[4] http://freedesktop.org/wiki/Software/gummiboot/

Matt Fleming is a software engineer at Intel's Open Source Technology Center and the Linux kernel (U)EFI maintainer.