There is a long and complex series of steps that turn your EFI source code into bits sitting on a SPI part. Ever wonder how that works? I couldn't find one place that described the process from start to finish, so this post tries to do that. I am going to coalesce information from the following sources:
- UEFI Platform Initialization Specification
- EDK II Build Specification
- EDK II Flash Description (FDF) Specification
This is not intended to be a deep-dive, but rather a beginning-to-end description of the process, with references to where you can find the details, if necessary.
Conceptual Hierarchy
To start, we need to master the vocabulary used in UEFI/PI to refer to the regions that make up a firmware image. The terms here come from the PI Spec, specifically Vol. 3, §2. Look at the following diagram, which we'll deconstruct below.
Working from outside in:
- Firmware Device (blue): a persistent physical repository that contains firmware code and/or data. It is typically a SPI NOR flash chip, but may be some other type of persistent storage. A single physical firmware device may be divided into smaller pieces to form multiple logical firmware devices. Similarly, multiple physical firmware devices may be aggregated into one larger logical firmware device.
- Firmware Volume (green): a logical firmware device. The basic storage repository for data and/or code is the firmware volume. A firmware file system (FFS) describes the organization of files and free space within a firmware volume.
- Firmware File (black): Firmware files are code and/or data stored in firmware volumes.
- Firmware File Encapsulation Section (pink): Firmware File Sections are separate discrete “parts” within certain file types. Encapsulation sections are containers that hold other sections. The sections contained within an encapsulation section are known as child sections, and the encapsulation section is known as the parent section. Encapsulation sections may have multiple children.
- Firmware File Leaf Section (orange): Firmware File Sections are separate discrete “parts” within certain file types. Unlike encapsulation sections, leaf sections directly contain data and do not contain other sections.
As the diagram illustrates:
- Leaf Sections do not necessarily have to be inside an Encapsulation Section
- Encapsulation Sections can be nested
EDK2 Build Process
The EDK2's job is to take source code and transform it into the various firmware objects discussed above. The EDK2 Build Process executes in three major stages:
- Pre-build or AutoGen stage: parse meta-data files to generate some C source code files and Makefiles
- Build or $(MAKE) stage: process source code files to create PE32/PE32+/COFF images that are modified to EFI format using NMAKE (Microsoft compiler-linker) or make (Linux compiler-linker)
- Post-build or ImageGen stage: take the binary EFI format files, and create EFI flash images
This chart summarizes that process:
Of particular note, the ImageGen stage takes the output of the compilation stage, i.e. the .efi files generated by the C compiler/linker, and converts the files into EFI section files using the GenSec tool. The next step combines the section files into FFS files using the GenFfs tool. Once the FFS files have been generated, they are combined into an FV image file using the GenFv tool. FV image files are combined into FD image files by the GenFds tool, which also controls all of the other steps in this stage. Again, to summarize:
ref: EDK II Build Specification, §4, §8, §9, §10
Flash Description File (.FDF) Files
The final piece of the puzzle is defining how the compiled code is organized and placed into the firmware containers discussed so far. This is where the .FDF files come in. The build system uses .FDF files to specify construction of the FDs, FVs and FFS files, as well as how to construct the different EFI Sections, i.e., what content is put into each section.
This process is managed by a domain-specific language defined in the FDF Spec. It wouldn't make sense to copy and paste the whole syntax of the FDF Spec here, so suffice it to say that FDs are defined by [FD] sections, FVs are defined by [FV] sections, modules are referenced by INF directives, etc. Refer to: EDK II Flash Description (FDF) Specification for the details.
However, for illustration purposes, what we can do is explore a firmware image using Nikolaj Schlej's excellent UEFITool tool. I've written about this tool before, and you can find more information here: UEFITool. What we'll do is look to the EDK2 and examine the .FDF file that defines the EmulatorPkg module: edk2\EmulatorPkg\EmulatorPkg.fdf. Let's compare the .FDF to the binary firmware image that gets built to see how the former defined the latter.
I've built EmulatorPkg at the stable tag edk2-stable202402 which generates the output file FV_RECOVERY.fd. Loading this FD into UEFITool I see:
Looking at the main FV I see:
You can walk back this FV to the source in the .FDF:
There is also this NVRAM volume:
Where did this come from? Check out the DATA directive in the .FDF source and you'll see how they match up:
You get the idea—to grok these concepts, it is a good exercise to walk through comparing an .FDF file to how its binary output is represented in UEFITool.
Post a Comment
Be sure to select an account profile (e.g. Google, OpenID, etc.) before typing your comment!