UEFI入门学习笔记(详细版,持续更新)

UEFI入门学习笔记

(一)UEFI构建编译流程

一、Build流程框架

Build Process Overview
Build的三个阶段:

1)Autogen

解析meta-data文件,dsc文件,.inf文件,.fdf文件,.dec文件,生成Autogen.c 、Autougen.h 、.depex、 Makefiles等文件

2)Make

主要是来处理source文件并通过Make生成符合EFI格式的PE32/PE32+/COFF 文件。

3)ImageGen

主要是来处理binary文件和EFI格式的文件,产生最终的UEFI Flash Images, Capsules,Applications和PCI Option ROMs

二、编译构建步骤:

1. 安装依赖工具

在开始编译之前,需要确保所有依赖工具已经安装并配置好,主要包括:
1)编译工具链(如 GCC、Clang、MSVC、ARMCC 等)
2)构建工具(如 BaseTools 中的 GenFds、GenFw 等工具)
3)Python(用于运行构建脚本)
4)NASM(汇编器)

2. 初始化构建环境

通过运行 edksetup.sh 或 edksetup.bat(视操作系统而定)来初始化构建环境。

3. 配置工具链和目标

Conf 目录中配置构建参数。主要的配置文件包括:

1)target.txt:定义构建目标模式(DEBUG、RELEASE)、工具链标签(如 GCC5、VS2019 等)、目标架构(如 X64、IA32、AARCH64 等)。

2)tools_def.txt:定义每种工具链的路径和编译选项。

3)build_rule.txt:定义如何编译和链接各个文件类型。

4. 定义平台配置

每个平台有其对应的 .dsc 文件(平台描述文件)和 .fdf 文件(固件描述文件),用来定义要构建的平台、所包含的模块、库、驱动程序,以及固件映像的布局。.dsc 文件是编译每个模块的主要参考,而 .fdf 文件是生成固件映像时的重要文件。

5. 构建并编译

使用 build 命令来启动构建过程。该命令会读取 target.txt 和 .dsc 文件中的配置信息,调用相应的编译器和工具链来构建模块。

在构建过程中,build.py 脚本会执行以下步骤:
1)解析配置文件:解析 .dsc 文件中的平台配置和模块定义,确定需要编译的模块和库

2)依赖分析:分析模块之间的依赖关系,确保各个模块按正确的顺序编译

3)编译源码:根据工具链配置调用编译器(如 gcc、clang 等)编译源文件(.c、.cpp、.asm 等),生成目标文件(.obj)

4)链接生成模块:将生成的目标文件通过链接器(如 ld、link 等)链接为可执行的二进制文件(如 .efi 文件)

5)生成固件映像:根据 .fdf 文件定义的固件布局,使用 GenFds 工具将各个二进制模块打包成固件映像文件(如 .fd、.rom、.bin 等)。生成的映像文件通常位于 Build/ 目录中,常见的格式有:

.fd:Firmware Descriptor(固件描述文件),是完整的固件映像文件。

.rom:BIOS ROM 文件。

.bin:可执行的固件映像文件。

三、uefi-tools编译edk2实践

使用 uefi-tools 编译 UEFI 固件的流程与传统的 EDK2 构建有所不同,它通过封装更简化了编译过程。

1. 克隆EDK2项目

git clone https://github.com/tianocore/edk2.git

然后,进入 EDK2 目录并获取所需的子模块:

cd edk2
git submodule update --init

2. 构建并编译

../uefi-tools/edk2-build.sh <project>

project:指定构建目标或配置的名称。

-e :指定 EDK2 的路径(edk2/)

-n :指定非开源或额外组件的路径( edk2-non-osi/)

-p:指定平台描述文件路径

-a:指定目标架构(如 X64、IA32)

-t:指定工具链标签

-b:指定构建模式(DEBUG 或 RELEASE)

(二)UEFI工程文件

一、常用工程文件

1、INF 文件

.inf 文件用于描述特定的模块或驱动程序的详细信息和构建参数。它定义了模块的构建方式,包括模块的源文件、包含文件、库依赖关系和构建选项等。.inf 文件是构建过程中的主要输入文件,告诉构建系统如何编译和链接一个特定的模块。

.inf 文件通常包含以下信息:
1)模块定义:模块的名称、版本等。

2)源文件:模块的源代码文件(例如 .c 文件)和头文件(例如 .h 文件)。

3)库依赖:模块需要的库和其他模块。

4)构建选项:编译器和链接器选项。

5)输出文件:构建输出的文件类型(例如 .efi 文件)。

必须块:

[Defines] 定义本模块的属性变量以及其他变量

[Sources] 列出本模块的所有源文件以及资源文件

[Packages] 列出本模块引用到的所有包的包声明文件

[LibraryClasses] 列出本模块要链接的库模块

非必须块:

[Protocol] 列出本模块用到的Protocol

[Guids] 列出本模块用到的GUID

[BuildOptions] 指定编译和链接选项

[Pcd] 用于列出本模块用到的Pcd变量

实例:

[Defines]
INF_VERSION                = 0x00010005
MODULE_TYPE                = DXE_DRIVER
ENTRY_POINT                = InitializeMyModule

[Sources]
MyModule.c
MyModule.h

[Packages]
MdePkg/MdePkg.dec

[LibraryClasses]
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf

[Protocols]
gMyProtocolGuid|MyProtocol.inf

2、DEC 文件

dec 文件(Declaration File)主要用于定义库、PPI、PROTOCOL、GUID 等模块的公共接口和依赖项。通常用于模块或库的声明部分,向其他模块公开接口信息,类似于头文件的作用

作用:

1)定义模块可以使用的 GUID、PPI、PROTOCOL。

2)声明模块对外公开的函数、全局变量等接口。

3)描述库类模块的依赖关系和接口定义。

必须区块: [Defines]

非必须块: [Includes] [LibraryClasses] [Guids] [Protocols] [Ppis] [PCD]

实例:

[Defines]
PACKAGE_NAME = MyModulePkg

[Guids]
gMyProtocolGuid = {0xabcdef01, 0x2345, 0x6789, {0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}}

[Protocols]
gMyProtocolGuid|MyProtocol.inf

[LibraryClasses]
DebugLib

3、DSC文件

.dsc 文件全称为 Description File,它是整个固件包或模块的构建描述文件,包含了构建的配置、依赖关系和构建选项,通常用于构建 UEFI 固件映像时的配置管理。库的构建依赖dsc文件

作用:

1)定义要构建的模块、库和驱动程序。

2)指定不同的构建目标、工具链和平台设置。

3)配置固件各个模块的编译选项、依赖关系。

必须块:

[Defines] [LibraryClasses] [Components]

非必须块:

[PCD] [BuildOptions]

实例:

[Defines]
PLATFORM_NAME = MyPlatformPkg
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/MyPlatform

[Components]
MyPlatformPkg/Drivers/MyDriver/MyDriver.inf

[LibraryClasses]
MyLibraryClass|MyPlatformPkg/Library/MyLibraryClassLib.inf

4、FDF文件

Flash Description File (固件描述文件)定义了固件的布局、包含的模块和其他与映像生成相关的内容。
作用:.fdf 文件用于指定固件映像的布局,包括:固件区域的定义、模块和驱动程序的链接位置、固件的启动设置。GenFds 工具用于将 FDF 文件转换为 FD 文件。

5、FD

FD (Firmware Descriptor)文件通常包含多个固件区域,如启动区、驱动程序区、应用程序区等。一个FD由一组FV组成,它将这些固件卷组合成一个完整的固件映像,准备被烧录或加载到设备上。
**作用:**FD 文件用于将多个固件组件打包成一个单一的固件映像。它定义了固件映像中各个部分的位置和大小。
**生成:**在 UEFI 固件构建过程中,FD 文件是通过工具(如 GenFds)从 FDF 文件生成的。它表示固件的最终映像格式,包含所有需要的固件模块和区域。

6、FV

FV (Firmware Volume)是固件卷,它是固件映像中的一个逻辑区域,用于组织和存储固件组件。每个 FV 文件包含一个或多个 FFS 文件 ,构成固件映像中的不同区域(如启动区域、驱动区域)。在 .fdf 文件中,FV 是一个逻辑区块,实际存储多个固件文件。

作用: FV 提供了一个结构化的方式来组织固件组件,使得不同的固件模块可以按需加载和管理。FV 通常用于 UEFI 固件映像的打包和管理。FV通过GUID(全局唯一标识符)来标识和访问其中的模块
** 生成:** 在固件构建过程中,FV 文件通过将多个 FFS 文件组合在一起生成,形成固件卷。

7、FFS

FFS (Firmware File System)是固件文件系统中的一个单独的文件,它可以是一个模块、驱动程序、库、或应用程序(如 .efi 文件)。FFS 文件是固件卷(FV)中的基本组成部分。
作用: FFS 文件用于存储和组织固件中的不同模块和驱动程序。每个 FFS 文件都有一个头部,描述其类型、大小和其他属性。
生成: FFS 文件在固件构建过程中由编译器和链接器生成,然后被打包到 FV 文件中。

二、一些工程文件之间的关联

1、DEC与DSC的联系

.dec 文件提供了模块的“接口”和“声明”(类似于头文件的作用),而 .dsc 描述平台的配置、构建设置和模块的组织结构。

.dec文件中的定义通常在 .dsc 文件中被引用。具体来说,.dsc 文件会使用 .dec 文件中声明的Library Class、模块路径、 GUID 等资源。

2、FD、FV 、FFS和section的联系

FD、FV 、FFS、section之间存在密切的层级关系,从大到小依次构成整个固件。
FD是FV的上层容器,FV是FD中存储固件组件的一种结构。在 UEFI 固件的构建过程中,这些文件格式协同工作,以确保固件映像的正确组织和打包。GenFds 工具用于将 FDF 文件转换为 FD 文件,FD 文件再包含多个 FV 和 FFS 文件,FFS由section组成,以生成最终的固件映像。

3、FDF和INF的联系

在FDF文件中,FV 会引用通过 INF 文件定义的模块,将它们打包到固件卷中。通常,FDF文件会指向多个由 INF 文件生成的模块,并将它们组织到一个或多个 FV 中。

(三)EDK II 模块

一、模块和包的概述

模块是一个最小的可编译单元,Package是最小的对外发布的单元

模块放在里面的。

模块包括LibraryDriverApplication等类型。每一个模块都有一个INF文件。

1、Library模块

Library和Library之间可以互相依赖。

[LibraryClasses.common]
	## <LibraryClassName>|<LibraryInstancePathToInf/Name.inf>
	DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf

(p.s. 可以用Doxygen根据代码生成CHM文件,方便查找Libary的功能)

MdePkg(Specs) —>SerialPort(Class)—>DebugLibSerialPort(Instance)—>DebugLib

LibraryClass应的是头文件, Library Intance对应的是一个模块(INF)

LibraryClass主要在include/Library/目录下面找,Libray Instance主要在Libary目录下找

2、Application模块

作用: 调试device、平台分析、工具开发、显示变量、显示设备等

(os loader 是一种特殊的application,执行完成后不会return或者exit,相反会调用EFI boot service gBS->ExitBootServices()来将控制权从fireware 传递给os)

3、Driver模块

Driver可以依赖Library,Driver可以生产protocols,主要用于支持硬件。

4、Application和Driver的区别

Application相当于是一次性的,一执行完就结束,而Driver是一直存在的。App是被UEFI loader加载的,不会装protocols。

二、EDK II 实现UEFI Application

实现一个简单的HelloWorld应用程序

1)HelloWorld.inf
[Defines]
  INF_VERSION = 0x00010005
  BASE_NAME = HelloWorld
  FILE_GUID = 67A6DE6D-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  MODULE_TYPE = UEFI_APPLICATION
  VERSION_STRING = 1.0
  ENTRY_POINT = HelloWorldEntry

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[Guids]

[Ppis]

[Protocols]
2)HelloWorld.c
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>

EFI_STATUS
EFIAPI
HelloWorldEntry(
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)
{
  Print (L" HelloWorld!\n");
  return EFI_SUCCESS;
}

三、EDK II 实现UEFI Driver

1)MyDriver.inf

实现一个简单的MyDriver驱动

[Defines]
  INF_VERSION = 0x00010005
  BASE_NAME = MyDriver
  FILE_GUID = XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  MODULE_TYPE = UEFI_DRIVER
  VERSION_STRING = 1.0
  ENTRY_POINT = MyDriverEntry

[Sources]
  MyDriver.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint

[Guids]

[Ppis]

[Protocols]
2)MyDriver.c
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>

EFI_STATUS
EFIAPI
MyDriverEntry(
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)
{
  //在此实现内容
  return EFI_SUCCESS;
}

(四)PCD相关

一、基本概念

在 UEFI中,PCD(Platform Configuration Database)是一种用于管理平台配置数据的机制。PCD 允许固件和操作系统在运行时读取和修改配置数据,以支持系统的定制和优化。

目的:

为了把代码中可配置的信息抽象出来成PCD,使得Module和Platform容易做定制化,不用修改源码,容易维护,便于改动。三个方面:

1)不仅可以在Build-time中可以通过编译选项配置,

2)在Run-time(boot过程)中也可以配置,

3)在Binary中也可以进行配置

二、PCD类型

(对应的数据类型都是基本数据类型)

1、FixedAtBuild

作用域在一个模块中(模块级的)。用于控制Feature,在build之后不能修改(相当于一个宏)。不同模块可以配置不同的值。只支持静态设置。

2、FeatureFlag

和FixedAtBuild是一样的作用,但是是Boolean类型。(同1的作用)

3、PatchableInModule

作用域在一个模块中(模块级的),可以在Binary Level进行修改。

4、Dynamic

作用域在整个系统中(系统级的)。动态的(在整个启动过程中可以set、get)。PCD的值存在memory里面,下次启动时,上次更改的值丢失了,每次启动都是从default值开始。

5、DynamicEx

与DynamicEx类似。区别是:
如果platform是从源码build出来的,没有binary在里面的时候,PCD用的都是Dynamic这种类型;
如果在BIOS里面有一些模块是binary方式集成进来的而这些binary又需要用到PCD(用于Binary Release),那么这些Binary集成的要用到的PCD就必须要设置为DynamicEx类型。

6、DynamicHii

作用域在整个系统中(系统级的)。动态的(在整个启动过程中可以set、get)。PCD的值和EFI Variable关联在一起(可能是non-volatile的,下次作启动还起效)

7、DynamicVpd

作用域在整个系统中(系统级的)。动态的(在整个启动过程中可以set、get)。是存在VPD空间的(在FLASH上,只读),一般是出厂配置。

三、PCD的使用

1、在.DEC中声明

[PcdsFixedAtBuild]
  gEmulatorPkgTokenSpaceGuid.PcdEmuFlashNvStorageVariableBase|0x0|UINT64|0x00001014

PCD的唯一性是通过GuidName和TokenNum(0x00001014)决定。GuidName、TokenNum不能修改了,Value可以修改。

2、在.INF中引用

在Pcd section中列出来,示例如下:

[Pcd]
  gEfiMdeModulePkgTokenSpaceGuid.PcdConOutRow                          ## PRODUCES
  gEfiMdeModulePkgTokenSpaceGuid.PcdConOutColumn                       ## PRODUCES
  gEfiMdeModulePkgTokenSpaceGuid.PcdVideoHorizontalResolution          ## PRODUCES

3、在.DSC中配置

设置PCD的值,示例如下:

[PcdsFixedAtBuild]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x0f
  gEfiMdePkgTokenSpaceGuid.PcdReportStatusCodePropertyMask|0x06
  gEfiMdeModulePkgTokenSpaceGuid.PcdMaxSizeNonPopulateCapsule|0x0

4、在.c中使用

通过PCB Library进行操作,最常用的是:PCDGetXX()PCDSetXX()
(其中:XX = 8/16/32/Size/Ptr/Boolean),示例如下:

PcdStatus = PcdSet16S(PcdPlatformBootTimeOut, GetFrontPageTimeoutFromQemu);
Timeout = PcdGet16(PcdPlatformBootTimeOut);

(五)内存服务

一、内存服务概况

本文主要针对系统内存。在SEC阶段,只有栈上的空间可用,没有可用的内存服务。内存服务在PEI(Pre-EFI Initialization)阶段才开始提供。

1、PEI阶段

分为物理内存初始化之前、物理内存初始化之后,在内存准备好之前,memory空间是非常少的。

CreateHob:CreateHob 是 UEFI 中用于创建 HOB(Hand-Off Block)的函数。HOB 是一种用于在引导过程中传递信息的结构体,主要在 PEI(Pre-EFI Initialization)阶段使用。
AllocatePool:轻量级的,不支持内存类型(都是Boot Service Data类型),没有FreePool API,不能释放。
AllocatePages/FreePages:不支持内存分配类型(都是Boot Service Data类型)

2、DXE阶段(系统内存)

物理内存都是存在,可以使用的。DXE阶段管理的是整个系统的memory,管理的空间较大。

AllocatePool/FreePool(UEFI的Boot Services Table提供):用于分配指定大小的内存块。所有的pool都有额外的信息标识起始地址,有一定的损耗
AllocatePages/FreePages(UEFI的Boot Services Table提供):用于释放之前通过 AllocatePool 分配的内存。用链表结构管理。

3、SMM阶段

在 SMM(系统管理模式)阶段,仅支持运行时数据和代码。SMM 在 SMRAM(系统管理 RAM)中有自己的专用内存空间,其他区域是不可访问的。

AllocatePool/FreePool:只支持runtime数据和代码
AllocatePages/FreePages:只支持runtime数据和代码

二、HOB概述

1、为什么在PEI阶段要引入HOB?

HOB(Hand-Off Block)主要用于在不同的阶段共享数据。由于Flash是只读的,直接修改全局变量是无效的。因此,在PEI(Pre-EFI Initialization)阶段,需要使用HOB来共享全局信息。HOB提供了一块临时内存,这块内存是CPU缓存的一部分,可以进行读写操作,从而在不同的阶段之间传递和共享信息。

2、HOB的类型

1) EFI_HOB_TYPE_HANDOFF:主要用于保存和传递在PEI阶段生成的信息,这些信息会被DXE阶段的驱动程序或其他组件使用。

2) EFI_HOB_TYPE_MEMORY_ALLOCATION:用于描述内存分配的信息。它通常用于传递有关分配的内存区域的元数据,包括内存的起始地址、大小和类型。

3) EFI_HOB_TYPE_RESOURCE_DESCRIPTION:用于描述系统资源的详细信息。这种类型的HOB包含有关系统资源的布局和分配信息,如内存区域、I/O端口、以及其他硬件资源的描述。

4) EFI_HOB_TYPE_GUID_EXTENTION:用于共享pre-memory和after-memory之间的数据。

5) EFI_HOB_TYPE_FV:用于描述固件卷(Firmware Volume)的信息。(是给DXE阶段用的)

6) EFI_HOB_TYPE_CPU:用于描述CPU的信息。

7) EFI_HOB_TYPE_MEMORY_POOL:提供有关内存池的配置和管理信息,帮助系统在不同的初始化阶段正确识别和利用内存池资源。

三、MEMORY类型

1、EfiLoaderCode/EfiLoaderData:

用于的UEFI 应用程序代码/数据的内存区域

2、EfiBootServicesCode/EfiBootServicesData:

UEFI/PEI/DXE 驱动

3、EfiRuntimeServicesCode/EfiRuntimeServicesData:

Runtime驱动(不能被OS使用)

4、EfiReservedMemoryType:

BIOS预留下来的一段内存,通常用于固件或其他系统组件在启动过程中的内部用途。(不能被OS使用)

5、EfiACPIReclaimMemory:

用于标记ACPI需要的内存区域

6、EfiACPIMemoryNVS:

用于标记 ACPI非易失性存储 (Non-Volatile Storage) 区域的内存。这种类型的内存用于存储需要在系统休眠和恢复过程中保持不变的数据。(不能被OS使用)

四、内存分布

1、PEI内存分布

1)在系统中,HOB(Hand-Off Block)内存分配是从低地址向高地址进行的。

2)HOB 和 AllocatePool 都位于低地址区域。

3)而 AllocatedPage 是从高地址向低地址分配的,位于高地址区域。

4)HOB的长度都是8字节对齐的

5)Top要大于Bottom

PEI Memory Layout

2、DXE内存分布

1)内存分配的方向从高往低

2)EFI_MEMORY_TYPE_INFORMATION 机制确保相同类型的内存尽可能集中分配。初次分配时需估算预留空间,并在启动前收集实际使用的内存信息保存为 EFI 变量。之后的启动可以使用更准确的预留值,若不够再从Remaining内存中分配。

3)ACPI、Reserved、Runtime Memory:是OS不可以回收的

4)将BIOS阶段预留的内存分配尽可能放置在高地址区域,以便操作系统能够使用连续的内存空间。

5)Pool 和 Page 在相同的内存空间中。申请 Pool 时,首先分配 Page,然后从 Page 中提供给 Pool 使用。Pool 和 Page 的管理都通过链表实现。
DXE Memory Layout

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值