1. Introduction
In the EDK II project, INF, DSC, DEC and FDF are important configuration files related to modules, platforms and packages. There can be confusion about their relationship
- How does the library dependency declared in INF finally find its specific implementation?
- Why can DSC not only decide which modules to compile, but also configure PCD and library instances?
- How are the GUID, Protocol and PCD defined by DEC used by modules?
- How does an ordinary C file eventually become
.efiFile? .efiHow are the files packaged into a firmware volume (FV) and finally generated as a programmable file?.fdMirrored?
To understand these issues, you should not just focus on a certain configuration file, but should start from the overall design of EDK II and understand the relationship between module organization, dependency resolution, code generation, compilation and linking, and firmware packaging.
This article uses a simple UEFI driver HelloDriver For example, starting from the code structure, we will gradually analyze the complete construction process of EDK II.
2. Code organization structure of EDK II
The code organization of EDK II mainly involves three concepts: Module, Package and Platform.
2.1 Module: the smallest building unit
Module is the basic building unit in EDK II and can be:
- UEFI Application
- UEFI Driver
- DXE Driver
- PEIM
- Library Instance
- …
A module usually consists of source files and corresponding INF files.
For example:
MyBoardPkg/
└── Drivers/
└── HelloDriver/
├── HelloDriver.inf
└── HelloDriver.c
in,HelloDriver.c stores the actual C code,HelloDriver.inf Then describe the metadata of the module, including:
- Module name, GUID, and type
- Source file list
- The dec of Pkg used
- Dependent Library Class
- PCD, Protocol and GUID used
- Module entry points and dependency expressions, etc.
INF can be understood as the module’s specification. It is how the build system understands what a module is, what it requires, and how it should be involved in the build.
2.2 Package: a collection of related modules and public interfaces
Package is used to organize function-related modules and usually contains a DEC file.
For example:
MyBoardPkg/
├── MyBoardPkg.dec
├── Include/
└── Drivers/
└── HelloDriver/
├── HelloDriver.inf
└── HelloDriver.c
DEC mainly describes the public definitions provided by Package, such as:
- Public header file directory
- Library Class declaration
- GUID
- Protocol
- PCD declarations and their default values and type information
For example, a Package can declare a PCD through DEC for reference by other modules.
It should be noted that DEC is mainly responsible for declaration and metadata description, and is not responsible for selecting the final library implementation for the module, nor is it responsible for determining the layout of the entire firmware image.
2.3 Platform: final build and integration goals
Platform describes a specific platform build, usually defined by a DSC file, and can be used with an FDF file to generate a complete firmware image.
For example:
MyBoardPkg/
├── MyBoardPkg.dec
├── MyBoardPkg.dsc
├── MyBoardPkg.fdf
└── Drivers/
└── HelloDriver/
├── HelloDriver.inf
└── HelloDriver.c
DSC is responsible for defining the core information required to build the platform:
- What modules are included in this build?
- Which library instance is used by each Library Class
- Platform level PCD value
- Build options and related platform configurations
FDF further describes how the firmware image is organized, such as the layout of the Firmware Volume (FV), which modules are included in the FV, and how these contents are placed into the Firmware Device (FD).
Therefore, it can be summarized in one sentence:
INF describes the module, DEC describes the public definition of the package, DSC describes how the platform is built, and FDF describes how the firmware image is organized.
3. INF, DSC, DEC, FDF
Understanding the responsibilities of these four types of files is the key to understanding the entire build process.
3.1 INF: What does a module need?
INF describes the build metadata of an individual module.
For example,HelloDriver.inf can be written as:
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = HelloDriver
FILE_GUID = 5f8c9a1e-2b3d-4e6f-8a9b-0c1d2e3f4a5b
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = HelloDriverEntryPoint
[Sources]
HelloDriver.c
[Packages]
MdePkg/MdePkg.dec
MyBoardPkg/MyBoardPkg.dec
[LibraryClasses]
UefiDriverEntryPoint
DebugLib
UefiLib
[Protocols]
gEfiDriverBindingProtocolGuid
[Pcd]
gMyBoardPkgTokenSpaceGuid.PcdHelloMessage
-
MODULE_TYPE = DXE_DRIVERIndicates that the module is a DXE driver type. -
ENTRY_POINTDeclares the logical entry function of the module. -
[LibraryClasses]What is stated inAbstract library classes rather than concrete library implementations。
For example:
[LibraryClasses]
DebugLib
This does not mean that the module has determined which DebugLib implementation to use. The real mapping is usually done by DSC.
also,[Packages] Declare the Package that the module depends on. Relevant public definitions used by the module need to be provided through appropriate DEC declarations.
3.2 DEC: Where is the public interface defined?
DEC is used to declare the public definition provided by the Package.
For example:
[Defines]
DEC_SPECIFICATION = 0x00010005
PACKAGE_NAME = MyBoardPkg
PACKAGE_GUID = 9e8d7c6b-5a49-3827-1605-f4e3d2c1b0a9
PACKAGE_VERSION = 0.1
[Includes]
Include
[Guids]
gMyBoardPkgHelloGuid = { ... }
[PcdsFixedAtBuild]
gMyBoardPkgTokenSpaceGuid.PcdHelloMessage|L"Default hello"|VOID*|0x00010001
The GUID and PCD here are only used to illustrate the configuration relationship. In actual projects, definitions that comply with the specifications must be used and the consistency of the relevant GUID, Token Space and Token Number must be ensured.
For example, DEC states PcdHelloMessagethe module can reference it in the appropriate INF, and the platform configures the actual value for it through DSC.
Note: The statement in DEC does not mean that a certain PCD will be compiled into the final firmware. It also needs to satisfy requirements such as module dependencies and platform build configurations.
3.3 DSC: What is the final choice of the platform?
DSC is the core of the platform build configuration.
For example:
[Defines]
DSC_SPECIFICATION = 0x00010005
PLATFORM_NAME = MyBoardPkg
PLATFORM_GUID = 1a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d
PLATFORM_VERSION = 0.1
OUTPUT_DIRECTORY = Build/MyBoardPkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS = DEBUG|RELEASE
SKUID_IDENTIFIER = DEFAULT
[LibraryClasses]
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
[PcdsFixedAtBuild]
gMyBoardPkgTokenSpaceGuid.PcdHelloMessage|L"Hello from MyBoardPkg"
[Components]
MyBoardPkg/Drivers/HelloDriver/HelloDriver.inf
when HelloDriver.inf declare need DebugLib the build system can find the corresponding library instance based on the mapping in DSC.
This process can be understood as:
HelloDriver.inf
|
| 声明需要 DebugLib
|
DSC [LibraryClasses]
|
| 将抽象库类映射到具体实例
|
BaseDebugLibSerialPort.inf
|
|
编译并链接对应库代码
This is the flexibility provided by EDK II’s library class mechanism: modules rely on abstract interfaces, and the platform determines the actual implementation.
Note that the library instances in the examples must be compatible with the actual module’s architecture, module types, and dependencies. Library mappings for real platforms are often more complex than this simplified example.
in addition,[Components] Determine the set of modules in the platform build. Library instances referenced by other modules are usually included in the build through dependency resolution and do not necessarily need to be listed as independent components.
3.4 FDF: How is the final firmware image organized?
FDF mainly describes the layout and packaging rules of firmware images.
A typical structure is:
FD(Firmware Device)
└── FV(Firmware Volume)
├── FFS File:HelloDriver
│ ├── PE32 Image
│ ├── UI Section
│ └── DEPEX Section
└── 其他固件模块
FD represents the firmware device image, which usually corresponds to a piece of image data to be written to Flash.
FV is a firmware volume that organizes a set of firmware files. Each firmware file can contain Sections such as PE32, DXE_DEPEX, and UI.
FDF can specify:
- FD size, base address and layout
- Properties of FV
- What modules are included in FV
- What packaging rules are used for different module types?
For example:
[FV.FVMAIN]
FvNameGuid = 7b9a1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d
INF MyBoardPkg/Drivers/HelloDriver/HelloDriver.inf
This configuration indicates that the specified module will be included FVMAIN firmware volume build.
However, FDF is not responsible for compiling C code on behalf of the compiler, nor is it responsible for determining the dependencies of all platform modules. It mainly consumes the module products generated in the previous construction phase and organizes them according to firmware layout rules.
In addition, the GUID, Flash address, and layout parameters in the example are for demonstration purposes only and cannot be directly used on real hardware. The actual configuration needs to comply with the target platform’s Flash capacity, FV constraints, and boot firmware layout.
4. EDK II complete compilation process
- From a C file to the final firmware image, the EDK II build process can be summarized as:
- Describe the source files, types and dependencies of the module through INF.
- The public interface and related metadata are provided through DEC.
- Use DSC to decide which modules to build for the platform, which library instances to select, and configure platform-level parameters.
- BaseTools parses metadata, resolves dependencies and generates AutoGen files and build rules.
- The compiler compiles source files and automatically generated code into object files.
- The linker generates the corresponding intermediate image, and GenFw processes the relevant image to form the EFI module product.
- When a complete firmware image is required, GenFds organizes FFS, FV, and FD according to the rules of FDF, and finally generates a firmware image file.
4.1 Step 1: Analyze platform and module configuration
Execute a command similar to the following:
build -p MyBoardPkg/MyBoardPkg.dsc -a X64 -b DEBUG -t GCC
The build system first reads the DSC and determines the configuration for this build based on the specified architecture, build target, and tool chain.
Subsequently, the system reads [Components] module INF in the module, and parse the DEC, Library Class, PCD and other information referenced by the module.
For example:
MyBoardPkg.dsc
|
+-- HelloDriver.inf
| |
| +-- MdePkg.dec
| +-- MyBoardPkg.dec
| +-- DebugLib
| +-- UefiLib
|
+-- 平台级 PCD 配置
|
+-- 工具链与构建选项
At this point, the build system needs to determine the module’s dependencies, concrete library instances, and valid configurations.
Two things need to be distinguished here:
- DEC provides public definitions and declaration information.
- DSC provides platform-level selection and configuration.
The library mapping relationship and PCD configuration in DSC will affect subsequent code generation and compilation results.
4.2 Step 2: Parse library dependencies
AutoGen is a very important part of the EDK II building process.
For modules, INF declares abstract library classes. For example:
[LibraryClasses]
UefiDriverEntryPoint
DebugLib
UefiLib
The build system finds specific library instances INF based on the mapping relationship of DSC, and then recursively parses the dependencies required by these library instances.
The build system then generates the appropriate AutoGen files based on the module, library instance, and platform configuration.
Typical products include:
AutoGen.c
AutoGen.h
AutoGen files are not business codes manually written by developers, but auxiliary codes automatically generated by the build system based on metadata.
They can contain or participate in generating:
- Wrapper code related to module entry point
- Library Constructor and Destructor call list
- PCD accesses the instantiated data required
- Related configurations of modules and library instances
- Auto-generated content for other dependent module types and build configurations
For example, when a module depends on multiple library classes, the build system may organize the constructors of related library instances into the generated ProcessLibraryConstructorList() middle.
It is important to emphasize that the actual generated content will vary depending on the module type, library instance and build configuration, and it cannot be assumed that every module will get the exact same AutoGen file.
4.3 Step 3: Compile C files and AutoGen files
After metadata parsing and code generation are complete, the build system calls the actual compiler.
For example:
HelloDriver.c
AutoGen.c
|
v
编译器
|
v
HelloDriver.obj
AutoGen.obj
Library instances are also compiled into target files according to their respective build configurations.
Here is an important realization:
BaseTools is responsible for parsing metadata, generating build rules and coordinating the tool chain, and it is the selected native compiler that actually compiles C code into machine instructions.
Therefore, EDK II is not a re-implementation of a C compiler, but a metadata-driven build system suitable for UEFI firmware development based on traditional compilation and linking tools.
4.4 Step 4: Link to generate PE/COFF image
Compiler generated .obj Afterwards, the build system will perform further linking.
For common X64 UEFI modules, the final product usually uses the PE32+ format and belongs to the PE/COFF image system.
The process can be simplified to:
HelloDriver.obj
AutoGen.obj
Library objects
|
v
Linker
|
v
PE/COFF image
However, the intermediate products and naming of different tool chains are not exactly the same.
For example, when using the MSVC tool chain it may occur .dll intermediate format; when using the GCC tool chain, ELF may be used as the intermediate format. The specific process is determined by the tool chain configuration of EDK II and the corresponding build rules.
Therefore, it is not appropriate to understand “first generate the DLL and then convert it by GenFw” as a fixed step that all EDK II builds must go through.
4.5 Step 5: GenFw processes the EFI module
GenFw is an important tool in EDK II BaseTools responsible for processing firmware module images.
It can perform format conversion, inspection and processing on the relevant images generated by the build, and generate module products required for subsequent firmware builds.
For example:
中间 PE/COFF 镜像
|
v
GenFw
|
v
HelloDriver.efi
Need to pay attention to,.efi It is not an independent executable file format completely unrelated to PE/COFF. UEFI applications and drivers usually use PE/COFF image format..efi is a common file extension.
At this point, an independent UEFI module already has the main executable image product.
But if the goal is to generate a complete firmware image, you still need to continue building FV and FD.
4.6 Step 6: GenFds generates FV and FD
When the platform is configured with FDF and the build target needs to generate a firmware image, the build system further performs the firmware packaging phase.
GenFds organizes the previously generated module products according to the definitions in FDF.
In this process, several different concepts need to be distinguished.
EFI moduleIs an executable image, such as HelloDriver.efi。
FFS fileIt is a file in the firmware file system, usually containing the module image and related Sections.
FV Is a firmware volume that internally organizes one or more FFS files.
FD Is a firmware device image that describes the organization and layout of the final firmware data throughout the device image.
Therefore, GenFds does not simply combine all .efi The files are spliced together. It needs to build the corresponding data according to the firmware file system format, FV structure and FD layout rules.
Additionally, whether the NVRAM variable store is included in a certain FD, and how its actual contents are initialized, depends on the platform-specific FDF and other firmware configuration. All variables generated during runtime in NVRAM cannot be understood as data directly packaged during construction.
5. AutoGen and module runtime entry
Previously we introduced the process from source code to .efi construction process. Next, let’s further analyze what happens when a module is loaded and executed.
For UEFI modules, declared in INF ENTRY_POINT It is the logical entry of the module.
For example:
[Defines]
MODULE_TYPE = DXE_DRIVER
ENTRY_POINT = HelloDriverEntryPoint
In a typical EDK II module build, the actual entry may be provided by the corresponding library entry packaging layer, and then the packaging layer calls the entry function of the module itself.
For example, use UefiDriverEntryPoint The module of the library, its entry call and initialization logic will involve the corresponding library implementation and automatically generated code.
For modules that contain related library initialization mechanisms, you can see an execution sequence similar to the following:
// Call constructors for libraries.
ProcessLibraryConstructorList (ImageHandle, SystemTable);
// Call the module's entry point.
Status = ProcessModuleEntryPointList (ImageHandle, SystemTable);
// Process destructors for libraries.
ProcessLibraryDestructorList (ImageHandle, SystemTable);
This code expresses three logical steps:
- Constructor that executes related libraries.
- The Entry Point of the calling module.
- Execute the Destructor of the relevant library.
However, a boundary must be made clear here: the above code is a diagram used to illustrate the entry packaging mechanism. The specific calling method, return value processing, exception path and destruction timing shall be subject to the actual implementation of the corresponding module type and entry library.
Especially for DXE drivers, after the entry function returns, the driver usually does not end the entire life cycle immediately just because the entry function returns. It may have registered protocols, installed services, or completed event registration. Subsequent behavior is determined by the UEFI firmware environment and the driver’s own logic.
Therefore, the life cycle of all UEFI modules cannot be simply understood as “Constructor-Business Function-Destructor, and then the program ends”.
5.1 Where do Constructor and Destructor come from?
Library instances can declare Constructor and Destructor functions in their INF.
When a module depends on these library instances, the build system will generate a corresponding call list based on the dependencies and the relevant metadata of the library.
For example:
HelloDriver
|
+-- UefiLib
|
+-- DebugLib
|
+-- OtherLibrary
Assuming that some of these library instances declare a Constructor, the generated AutoGen code can organize the corresponding constructor calls.
This also explains why AutoGen does more than simply generate a few variables: it also takes on the job of converting declarative dependencies into module initialization code.
As for whether and when Destructor is called, and how to clean up resources when the module is unloaded, it depends on the module type, the entry library used and its runtime management mechanism.
6. Match the four types of documents with the entire process
INF, DSC, DEC and FDF were introduced previously. The relationship between them can now be reorganized from the perspective of the construction phase.
| document | Core responsibilities | Main stages of impact |
|---|---|---|
| INF | Describe module types, source files and dependencies | Module analysis, dependency analysis, compilation |
| DEC | Declare the public interface and metadata of the Package | Analysis of header files, GUID, Protocol, PCD, etc. |
| DSC | Define platform components, library instance mappings, and platform configurations | Dependency analysis, AutoGen, compilation configuration |
| FDF | Describe FV, FD and firmware packaging rules | Firmware file organization, FV/FD generation |
There is another thing worth noting here: FDF does not have the same focus as the first three types of files.
INF, DEC and DSC are mainly involved in the construction description of modules and platforms; FDF is mainly responsible for organizing the built modules into images that conform to the firmware format.
Likewise, how a PCD is declared, configured, and ultimately stored is not done independently by a single file:
- DEC provides PCD declaration information.
- INF indicates the module’s use of the associated PCD.
- DSC provides platform configuration and participates in PCD instantiation at build time.
- Additional build rules and firmware configuration may be involved for scenarios where specific data needs to be organized in the firmware image.
Therefore, only by observing these files in the entire build system can we understand the division of labor between them.
7. Design philosophy of EDK II declarative construction
The cool thing about EDK II is that not only how it generates .efi files, but also how it manages complex dependencies.
Suppose a driver depends on multiple libraries, and these libraries depend on other libraries. If all handwritten Makefiles are used, developers need to maintain dependencies, compilation order, build options, and implementation differences on different platforms.
EDK II splits this information into different levels:
- INF describes the module’s build requirements.
- DEC describes public definitions.
- DSC describes platform selection and configuration.
- FDF describes how a firmware image is organized.
BaseTools reads these metadata, parses dependencies, generates AutoGen files and corresponding build rules, and then calls the external tool chain to complete compilation and linking.
This design allows the same module to be reused in different platforms without having to directly bind all library implementations and configurations of a certain platform.
For example, a driver only needs to depend on the abstract DebugLibthe platform can choose serial port output, empty implementation or other suitable DebugLib implementation according to needs.
Similarly, the platform can also change the configuration of the module through PCD without having to modify the module source code for each configuration variant.
This is the core idea of EDK II to decouple module reuse, platform customization and firmware integration.
Steady Pregress!