Navigation

  1. Home
  2. Projects
  3. Blog
  4. Music
  5. Photos
  6. Contact
  1. Internal

efiboot - An UEFI payload for coreboot

This is the project homepage for efiboot, a project that aims to build an UEFI payload for coreboot. The website describes the various components that make up efiboot and also how an UEFI payload can be built and packaged for use with coreboot. So far, the project has not been tested on real hardware but instead, QEMU has been used for development and testing.

Last updated: $Date: 2013/01/22 20:58:32 $.

Updates

Jan 22nd, 2013

Fixed the output from the DXE Core and updated the "screenshot".

Jan 21st, 2013

Add a new "screenshot" and information on how to interpret it.

Jan 20th, 2013

Re-work the build instructions to reflect recent check-outs of the TianoCore tree. Also, attempt to document some parts of the CorebootPkg.

Jan 5th, 2013

HTML changes related to my home-grown "CMS."

Architecture

Overview

A three layer approach is taken. The bottom layer is made up of coreboot in an unmodified version. Its task is to initialize and gather information about the hardware in the system. When coreboot has completed execution, it hands off control to a payload, the middle layer of the architecture.

The payload is efiload, a loader for the upper layer. The loader detects and parses the coreboot tables and converts the information encoded in the tables into a form understood by the UEFI components. Then, it loads and starts the UEFI components.

Illustration of the Architecture Fig. 1: Architecture

The third layer is composed of the UEFI components. The design and architecture of the UEFI components is dictated by the UEFI specification and the PI architecture.

Figure 1 above illustrates the architecture on a high level. It should be noted that the DXE Core presents the UEFI interfaces to any upper layers, e.g. an operating system, and completely hides the bottom layers. In fact, when the DXE Core has been started, the bottom layers do not exist anymore.

The corebooot layer

Coreboot performs platform initialization and starts a so-called "payload" after the initialization phase. The payload needs to be a standard ELF file, which is loaded at its linked address and then executed. Coreboot passes information about the system, like e.g. memory size, to the payload in the coreboot tables.

The efiload layer

The UEFI components expect that some basic information about the system is passed in a list of Hand-off Blocks (HOBs). The HOB list must be created by the efiload component from the information found in the coreboot tables. Most imporantly, information about the memory layout and the location of the UEFI Firmware Volume must be encoded in the HOBs.

The UEFI layer

The UEFI components implement the DXE phase of the PI architecture. In the DXE phase, the so-called DXE core starts several independent DXE drivers which implement the UEFI Boot Services and the UEFI Runtime Services. When all DXE drivers in the UEFI Firmware Volume have been executed, UEFI drivers and applications are started. Those drivers and applications make use of the UEFI services to fully initialize the system. As drivers execute, new hardware may be discovered. Information about hardware is passed to the DXE core via the UEFI Boot Services. The DXE core takes that information and builds a device tree representation of the system hardware. When a boot loader or an operating system is started, it may query said information through the UEFI system table.

Implementation

... of coreboot

Coreboot is a separate and independent project. No changes to coreboot are required for efiboot.

... of efiload

The efiload component is build with the help of libpayload which is a library part of the coreboot project. The library implements a limited set of the standard C library functions. It also implements start-up code so that an application can be written to start at its main() function instead of worrying about the low-level details. Efiload makes use of those facilities and uses libpayload data structures to locate and parse the coreboot tables. This latter task can be regarded as the input path of efiload.

The output path of efiload uses data structures implemented as part of the TianoCore EDK2 project. Those structures are documented in the UEFI and PI specifications and define how a Hand-off Block (HOB) should look like. Efiload transforms the coreboot tables into a HOB list.

The HOB list produced by efiload contains the following HOBs:

When efiload is done with the data transformation, it locates the DXE core in a UEFI Firmware Volume (FV). For ease of implementation and testing, the FV is appended as a binary section to the efiload binary. This allows the efiload code to address the size and location of the FV using symbols, i.e. simple C pointers. The code in efiload that searches the FV also makes use of data structures implemented in the TianoCore EDK2 project.

... of the UEFI layer

The UEFI Layer is implemented using the code provided by the TianoCore EDK II project. The EDK II code implements all three Platform Initialization (PI) phases SEC, PEI and DXE mandated by the UEFI-related Platform Initialization Specification. For the efiboot project, only the DXE phase is relevant. All other code is not used.

The code contained in the EDK II itself can probably be used without modifications. However, the CorebootPkg has been added to package and enhance the EDK II code. Also, to aid debugging, the EDK II build tools have been modified to work with ELF images instead of PE32+ binaries. By default, the TianoCore tools will work with ELF images, but convert them to a PE32+ binary before placing them into the firmware flash.

Boot Flash Layout

Coreboot expects the payload to be known at compile time. This is required because the coreboot build process compresses the payload and packages it along with the coreboot code in a flash image.

The UEFI specification and the implementation of the UEFI components dictate that all of the UEFI components are stored in an UEFI Firmware Volume (FV), a special format for a kind of file system in the firmware flash.

Illustration of the Boot Flash Layout Fig. 2: Boot Flash Layout

In order to fulfill both somewhat contradicting requirements, the UEFI Firmware Volume is encapsuled in the coreboot payload at compile time. At runtime, the efiload layer extracts the UEFI Firmware Volume and shadows it to a memory location. This means, the coreboot build process is used to assemble a bootable flash image and hence the boot flash layout follows the coreboot guidelines. Also, the UEFI components will always work on a UEFI Firmware Volume shadowed into system memory.

Screenshots

Because coreboot, efiload and the UEFI code produce console only output, the "screenshot" is presented in text-only format.

Your browser apparently cannot display embedded frames. Try viewing the "screenshot" directly here.

The first line printed by efiload is:

UEFI Loader started [0x100000-0x24c06f]

Everything printed before that line is emitted by coreboot.

Control is handed over to the DXE core after efiload printed the following line:

Handing control to DXE core...

All output after that comes from the EDK II code. As you can see, the DXE core starts up and eventually aborts due to a failed assertion. This is because the Firmware Volume (FV) used in the test cycle does not contain any other modules than the DXE core itself. Consequently, the DXE core aborts because modules that are mandatory in order to provide all boot services demanded by UEFI are missing.

There are a lot of messages from the elf_lookup_symbol() method like the one below. Those seem to be harmless. They disappear if the output ELF image isn't stripped as part of the build. To do that, adjust Conf/build_rule.txt and remove the --strip-unneeded parameter from the objdump call.

elf_lookup_symbol(): Failed to look up symbol 1!

In the previous "screenshot" published here, output from the EDK II code was not shown. This was because a few variables controlling debug output weren't set properly during the build.

A remote GDB session however revealed that the DXE core does in fact run to the point where it attempts to jump into the "Boot Device Selection" protocol:

(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x003155fe in CpuDeadLoop ()
at MdePkg/Library/BaseLib/CpuDeadLoop.c:37
37 for (Index = 0; Index == 0;);
(gdb) bt
#0 0x003155fe in CpuDeadLoop ()
at MdePkg/Library/BaseLib/CpuDeadLoop.c:37
#1 0x00301077 in DxeMain (HobStart=0x3eb4010)
at MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c:481
#2 0x003005c8 in ProcessModuleEntryPointList (HobStart=0x207000)
at Build/Coreboot/DEBUG_ELFGCC/IA32/MdeModulePkg/Core/Dxe/DxeMain/DEBUG/AutoGen.c:380
#3 0x00300019 in _ModuleEntryPoint ()
#4 0x00103e31 in ?? ()
#5 0x00100d73 in ?? ()
#6 0x00100051 in ?? ()
#7 0x820fd045 in ?? ()
#8 0x00000000 in ?? ()
(gdb)

To start QEMU in a way that lets you attach the debugger, use the following command (on Ubuntu 12.10) in the coreboot build directory:

$ qemu-system-i386 -L . -bios coreboot.rom -nographic -m 64 -S -s

The debugger can be attached like this:

$ gdb -ex "target remote localhost:1234"

Download

The source code of coreboot, libpayload and TianoCore EDK2 can be obtained from the respective project webpages.

Unfortunately, the efiload source code as well as the CorebootPkg cannot be distributed at this time due to legal obligations.

Hacking

Because the source code for this project cannot be released at this time, the information contained in this section is a little sparse and probably not too interesting for the greater public.

The efiboot code is built in a sandbox w/ the following directory structure:

efiboot/
efiboot/coreboot
efiboot/libpayload
efiboot/edk2
efiboot/efiload

The CorebootPkg

Check out the CorebootPkg and place it in efiboot/edk2/CorebootPkg. No further adjustments are required.

The CorebootPkg contains a platform description file (*.dsc), a package declaration file (*.dec), a flash description file (*.fdf) and an implementation of the PeCoffLib interface which operates on ELF images instead of PE32+ files.

The platform description file determines what actual implementation backs which library interface. It also determines which modules are built for this platform. Finally, it contains the definitions (as opposed to the declarations) of the PCD values known at compile time. The platform description file (*.dsc) format is specified by the TianoCore EDK II. The CorebootPkg is very similar to all other packages in the EDK II tree. The one notable difference is that it maps the PeCoffLib library interface to its own implementation.

When modifying the CorebootPkg, either by adding new modules or when updating to a newer TianoCore code base, it is frequently required to find out which implementations of a given library interface are available. A command like this one, issued from the edk2/ directory can be used for this task:

$ find . -name "*.inf" | xargs grep -e "LIBRARY_CLASS *= *PeCoffLib"

The package declaration file must declare (as opposed to define) all PCD values known at build time and referenced in the package. The package declaration file (*.dec) format is specified by the TianoCore EDK II. Previously, those values had been declared in the platform description file, too, but have apparently been moved to a separate file in more recent version of the TianoCore EDK II source tree. Failing to declare the PCDs will yield in errors from the build toolchain.

The flash description file (*.fdf) format is specified by the TianoCore EDK II as well. The file contains meta-data about the flash devices and images the build system deals with. Specifically, it defines what firmware volumes will be build and what modules they will contain. The CorebootPkg defines one firmware volume named "FvRecovery" containing all required modules. The minimum requirements are the DXE Core plus those modules that install the "architectural protocols", i.e. implement the UEFI boot and runtime services referenced from the system table.

UEFI components

Place the EDK II source code in the efiboot/edk2 directory. Go to the BaseTools subdirectory and build the toolchain for the EDK II code. On Ubuntu, make sure the uuid-dev package is installed (along with the compiler, of course). In order to work with ELF images only, replace BaseTools/Source/C/GenFw/GenFw.c. The replacement tool must accept the same parameters as the original one. It's only task is to copy the input file to a "branded" output file: The build process passes the "-e" parameter when invoking the GenFw utility, indicating what kind of module the binary is (e.g. UEFI_DRIVER, DXE_CORE, ...). This information is used at runtime to e.g. find the DXE Core module in the firmware volume. The e_flags field in the ELF header can be used to store that data.

Source in the edksetup.sh script from the edk2 directory in order to set-up the required workspace files such as e.g. Conf/tools_def.txt.

Then adjust the file Conf/tools_def.txt so the tool chain produces executable files instead of shared objects: Look for the ELFGCC tag and remove the --shared parameter from the linker flags. Instead add -Ttext 0x00 to the linker flags. The linker flags have two effects: First, the linker will fail if the output file has unresolved symbols. Second, linking the binary at address 0x00 will make it easier to do the math, should you attempt to attach a debugger and load a symbol file. Depending on your compiler, you may also need to add -fno-stack-protector to the compiler flags.

Adjust the file Conf/target.txt so the active target is the CorebootPkg and the toolchain tag is ELFGCC.

Finally, run build.

The file BuildNotes2.txt, also part of the EDK II source tree, contains further information about the build process and tweaking it.

efiload

Store the efiload source code in efiboot/efiload. Create a symbolic link named FVRECOVERY.Fv that points to ../edk2/Build/Coreboot/DEBUG_ELFGCC/FV/FVRECOVERY.Fv. Compile the code via make. The output of the build process is the file efiloader.elf which is used as the coreboot payload.

coreboot

Place the coreboot source code in the efiboot/coreboot directory. The coreboot Build HOWTO contains further information on this topic. When configuring coreboot via make menuconfig, make sure the payload points to the efiloader.elf file that contains the efiload code.

Links

Here are a few links to related projects.

Contact

Contact the author Philip Schulz by Email.

Share