如何使用 UEFI Shell 执行 Hello World 程序
如何创建一个 UEFI 应用程序在之前的文章中曾详细介绍了 EDKII 开发环境的搭建以及 OVMF 固件的编译过程。并且使用 QEMU 虚拟机来执行编译好的 OVMF 固件。我们知道在 Linux 终端中可以在命令行中执行编译好的应用程序UEFI 也有 shell如下图所示。我们能够在 shell 中执行编译好的 UEFI Application。本文以简单的 Hello World 程序为例来介绍 UEFI 应用程序的编译执行过程和各个文件的作用。1. 编译并执行一个 Hello World 程序在 EDKII 目录下创建文件 HelloWorldPkg创建文件 HelloWorld.c#include Uefi.h#include Library/UefiLib.hEFI_STATUSEFIAPIUefiMain (IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable) {Print(LHello, World!\n);return EFI_SUCCESS;}创建文件 HelloWorld.infGUID 可通过网站产生https://guidgen.com/[Defines]INF_VERSION 0x00010006BASE_NAME HelloWorldFILE_GUID 69ea2943-dbdd-404c-a3bf-6ef3fdfdf0a1MODULE_TYPE UEFI_APPLICATIONVERSION_STRING 1.0ENTRY_POINT UefiMain[Sources]HelloWorld.c[Packages]MdePkg/MdePkg.dec[LibraryClasses]UefiApplicationEntryPointUefiLib创建文件 HelloWorldPkg.dsc[Defines]PLATFORM_NAME HelloWorldPkgPLATFORM_GUID 0adf0da5-100e-49a9-9f87-76215486216dPLATFORM_VERSION 0.1DSC_SPECIFICATION 0x00010005SUPPORTED_ARCHITECTURES X64BUILD_TARGETS DEBUG|RELEASE[LibraryClasses]UefiLib|MdePkg/Library/UefiLib/UefiLib.infUefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.infPrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.infPcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.infMemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.infDebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.infBaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.infBaseLib|MdePkg/Library/BaseLib/BaseLib.infUefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.infDevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.infUefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.infRegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.infDebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf[Components]HelloWorldPkg/HelloWorld.inf编译为.efi文件打开终端cd /home/ayuan/src/edk2source edksetup.sh编译 HelloWorldPkg打开文件./Conf/target.txt修改如下项ACTIVE_PLATFORM HelloWorldPkg/HelloWorldPkg.dscTARGET DEBUGTARGET_ARCH X64TOOL_CHAIN_TAG GCC5回到终端执行命令build生成的 efi 文件的路径如下/home/ayuan/src/edk2/Build/HelloWorldPkg/DEBUG_GCC5/X64/HelloWorld.efi在 QEMU 中打开 OVMF 固件然后在 UEFI Shell 中执行刚才编译的 HelloWorld.efi 文件qemu-system-x86_64 -bios /home/ayuan/run-ovmf/OVMF.fd -drive formatraw,filefat:rw:/home/ayuan/run-ovmf/hda-contents -m 512M# 或者qemu-system-x86_64 -m 512M -drive ifpflash,formatraw,readonlyon,file/home/ayuan/run-ovmf/OVMF.fd -drive formatraw,filefat:rw:/home/ayuan/run-ovmf/hda-contents除了.c源文件之外我们还涉及到INF, DSC, DEC三个重要的文件。三个文件分别用于描述“模块”“平台”和“包”。他们的关系如下所示平台 (Platform)└── 由多个 模块 (Module) 组成├── 来自 包A (Package A)├── 来自 包B (Package B)└── 来自 包C (Package C)1. 包Package是“资源提供者”一个包就是一个功能或主题相关的“大仓库”。 例如MdePkg最基础的库、头文件、通用协议MdeModulePkg通用驱动如控制台、文件系统、USB 等OvmfPkg专用于 QEMU/Ovmf 虚拟机的平台包ShellPkgUEFI Shell 相关模块包通过 .DEC 文件对外声明我提供了哪些头文件、哪些库、哪些 GUID、哪些 PCD。2. 模块Module是“可构建单元”模块是真正会被编译的东西.efi、.lib。 每个模块必须属于某个包它的 .INF 文件第一件事就是通过 [Packages] 节声明自己属于哪些包从而获得头文件和定义。 一个 ConOutDxe.inf控制台输出驱动属于 MdeModulePkg 这个包。3. 平台Platform是“最终产品组装者”平台负责决定“我这个主板/产品要用哪些模块” 它通过 .DSC 文件的 [Components] 节把来自不同包的各种模块“挑选”进来并配置 PCD 值、库映射关系等。 最终通过构建命令生成完整的固件映像。三个文件的相互引用关系如下在 .INF 文件中必须说明引用包就是当前模块的源码使用了哪些包定义的函数或者接口[Packages]MdePkg/MdePkg.decMdeModulePkg/MdeModulePkg.dec在 .DSC 文件中需要引用模块就是需要将那些模块编译进该平台如[Components]MdeModulePkg/Universal/Console/ConOutDxe/ConOutDxe.infOvmfPkg/QemuVideoDxe/QemuVideoDxe.inf读者可能注意到我们在 HelloWorldPkg 中并没有创建 DEC 文件这是因为没有其他模块使用到我们自定义的这个包所以不创建也没什么问题。2. INF 文件说明INF 文件是单个模块Module的“身份证”。一个模块可以是驱动Driver、库Library、应用Application或 PEI/DXE 模块等。它告诉构建系统这个模块由哪些源文件组成依赖哪些包、库、协议、GUID模块的类型、入口点、输出文件名是什么编译时需要哪些特殊选项等。没有 INF 文件模块就无法被构建。每个 .inf 文件对应一个独立的、可独立构建的单元最终生成 .efi 或 .lib。INF 通常放在模块目录下如 MdeModulePkg/Universal/Console/ConOutDxe/ConOutDxe.inf。INF 文件的常用组成[Defines]模块基本信息版本、GUID、类型、入口点等。[Packages]依赖哪些包提供头文件和 PCD。[Sources]源代码文件列表。[LibraryClasses]需要链接哪些库类。[Protocols] / [Guids] / [Ppis]使用的协议/GUID/PPI 及使用方式BY_START、PRODUCES 等。[BuildOptions]特定编译器选项。[Depex]可选DXE 依赖表达式。例如[Defines]INF_VERSION 1.27BASE_NAME HelloWorldFILE_GUID 12345678-ABCD-1234-ABCD-123456789ABCMODULE_TYPE UEFI_APPLICATION # 或 DXE_DRIVER、BASE 等VERSION_STRING 1.0ENTRY_POINT UefiMain # 入口函数名[Packages]MdePkg/MdePkg.decMdeModulePkg/MdeModulePkg.dec[Sources]HelloWorld.cHelloWorld.h[LibraryClasses]UefiLibUefiApplicationEntryPointDebugLib[Protocols]gEfiShellProtocolGuid ## CONSUMES[Guids]gEfiMdeModulePkgTokenSpaceGuid ## SOMETIMES_PRODUCES3. DSC 文件说明DSC 为平台描述文件是整个平台Platform的“构建蓝图”。它定义了这个平台要包含哪些模块INF 文件库类LibraryClass如何映射到具体实现PCDPlatform Configuration Database值如何覆盖平台整体的架构、构建目标、输出目录等。一个平台通常只有一个主 DSC 文件如 OvmfPkg/OvmfPkgX64.dsc 或 PlatformPkg/Platform.dsc。DSC 不负责包的内容声明那是 DEC也不负责 Flash 布局那是 FDF但会引用 FDF 来生成最终固件映像。DSC 文件常用组成[Defines]平台名称、GUID、支持架构、构建目标等。[LibraryClasses]库类 → 具体 INF 的映射全局生效。[Pcds]覆盖包中声明的 PCD 默认值FixedAtBuild、Dynamic 等。[Components]列出所有要构建的模块 INF 文件支持条件编译。[Components.IA32]/[Components.X64]等架构特定节。例如[Defines]PLATFORM_NAME MyPlatformPLATFORM_GUID 87654321-ABCD-1234-ABCD-123456789ABCPLATFORM_VERSION 1.0DSC_SPECIFICATION 1.28OUTPUT_DIRECTORY Build/MyPlatformSUPPORTED_ARCHITECTURES IA32|X64BUILD_TARGETS DEBUG|RELEASESKUID_IDENTIFIER DEFAULT[LibraryClasses]DebugLib| MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.infUefiLib| MdePkg/Library/UefiLib/UefiLib.inf# ... 其他库映射[PcdsFixedAtBuild]gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes| 5 | UINT32 | 0x40000005[Components]# 核心模块MdeModulePkg/Universal/Console/ConOutDxe/ConOutDxe.infMyPkg/HelloWorld/HelloWorld.inf # 引用上面的 INF[Components.X64]# 只在 X64 下构建的模块OvmfPkg/QemuVideoDxe/QemuVideoDxe.inf4. DEC 文件说明DEC 是包Package的“目录索引”。一个包是一组相关模块、库、头文件、GUID、协议、PCD 的集合如 MdePkg、MdeModulePkg、OvmfPkg。DEC 文件的作用是声明包对外提供什么GUID、Protocol、PPI、LibraryClass、PCD指定头文件包含路径[Includes]让其他模块的 INF 文件可以通过 [Packages] 引用这个包从而获得头文件和 PCD 定义。没有 DEC模块就无法知道这个包里有哪些可用的接口和配置。DEC 文件常用组成[Defines]包名称、GUID、版本。[Includes]头文件目录支持架构特定。[LibraryClasses]包提供的库类及其头文件路径。[Guids]/[Protocols]/[Ppis]声明 GUID/协议/PPI带注释说明用途。[Pcds]声明所有 PCDFeatureFlag、FixedAtBuild、Dynamic 等及其默认值、类型、Token。例如[Defines]DEC_SPECIFICATION 1.27PACKAGE_NAME MdePkgPACKAGE_GUID 1E0A9C1A-5A9C-4C9A-9B7A-5A9C1E0A9C1APACKAGE_VERSION 1.05[Includes]IncludeInclude/Ia32 # 架构特定[LibraryClasses]## libraryclass 基础内存操作库BaseMemoryLib| Include/Library/BaseMemoryLib.h[Guids]## Include/Guid/MdePkgTokenSpace.hgEfiMdePkgTokenSpaceGuid { 0x1E0A9C1A, 0x5A9C, 0x4C9A, {0x9B, 0x7A, 0x5A, 0x9C, 0x1E, 0x0A, 0x9C, 0x1A} }[PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]