找回密码
 立即注册
首页 业界区 业界 如何使用 UEFI Shell 执行 Hello World 程序

如何使用 UEFI Shell 执行 Hello World 程序

剽达崖 昨天 17:03
如何创建一个 UEFI 应用程序

在之前的文章中曾详细介绍了 EDKII 开发环境的搭建以及 OVMF 固件的编译过程。并且使用 QEMU 虚拟机来执行编译好的 OVMF 固件。我们知道在 Linux 终端中可以在命令行中执行编译好的应用程序,UEFI 也有 shell,如下图所示。我们能够在 shell 中执行编译好的 UEFI Application。本文以简单的 Hello World 程序为例来介绍 UEFI 应用程序的编译执行过程和各个文件的作用。
1.png
1. 编译并执行一个 Hello World 程序


  • 在 EDKII 目录下创建文件 HelloWorldPkg
    2.png

  • 创建文件 HelloWorld.c
    1. #include <Uefi.h>
    2. #include <Library/UefiLib.h>
    3. EFI_STATUS
    4. EFIAPI
    5. UefiMain (
    6.     IN EFI_HANDLE ImageHandle,
    7.     IN EFI_SYSTEM_TABLE *SystemTable
    8. ) {
    9.     Print(L"Hello, World!\n");
    10.     return EFI_SUCCESS;
    11. }
    复制代码
  • 创建文件 HelloWorld.inf
    GUID 可通过网站产生:https://guidgen.com/
    1. [Defines]
    2.     INF_VERSION = 0x00010006
    3.     BASE_NAME = HelloWorld
    4.     FILE_GUID = 69ea2943-dbdd-404c-a3bf-6ef3fdfdf0a1
    5.     MODULE_TYPE = UEFI_APPLICATION
    6.     VERSION_STRING = 1.0
    7.     ENTRY_POINT = UefiMain
    8. [Sources]
    9.     HelloWorld.c
    10. [Packages]
    11.     MdePkg/MdePkg.dec
    12. [LibraryClasses]
    13.     UefiApplicationEntryPoint
    14.     UefiLib
    复制代码
  • 创建文件 HelloWorldPkg.dsc
    1. [Defines]
    2.     PLATFORM_NAME = HelloWorldPkg
    3.     PLATFORM_GUID = 0adf0da5-100e-49a9-9f87-76215486216d
    4.     PLATFORM_VERSION = 0.1
    5.     DSC_SPECIFICATION = 0x00010005
    6.     SUPPORTED_ARCHITECTURES = X64
    7.     BUILD_TARGETS = DEBUG|RELEASE
    8. [LibraryClasses]
    9.     UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
    10.     UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
    11.     PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
    12.     PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
    13.     MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
    14.     DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf
    15.     BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
    16.     BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
    17.     UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
    18.     DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
    19.     UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
    20.     RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
    21.     DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
    22. [Components]
    23.     HelloWorldPkg/HelloWorld.inf
    复制代码
  • 编译为 .efi 文件
    打开终端
    1. cd /home/ayuan/src/edk2
    2. source edksetup.sh
    复制代码
    编译 HelloWorldPkg
    打开文件 ./Conf/target.txt,修改如下项
    1. ACTIVE_PLATFORM       = HelloWorldPkg/HelloWorldPkg.dsc
    2. TARGET                = DEBUG
    3. TARGET_ARCH           = X64
    4. TOOL_CHAIN_TAG        = GCC5
    复制代码
    回到终端执行命令 build,生成的 efi 文件的路径如下:
    1. /home/ayuan/src/edk2/Build/HelloWorldPkg/DEBUG_GCC5/X64/HelloWorld.efi
    复制代码
  • 在 QEMU 中打开 OVMF 固件,然后在 UEFI Shell 中执行刚才编译的 HelloWorld.efi 文件
    1. qemu-system-x86_64 -bios /home/ayuan/run-ovmf/OVMF.fd -drive format=raw,file=fat:rw:/home/ayuan/run-ovmf/hda-contents -m 512M
    2. # 或者
    3. qemu-system-x86_64 -m 512M -drive if=pflash,format=raw,readonly=on,file=/home/ayuan/run-ovmf/OVMF.fd -drive if=pflash,format=raw,file=fat:rw:/home/ayuan/run-ovmf/hda-contents
    复制代码
    3.png

除了 .c 源文件之外,我们还涉及到 INF, DSC, DEC 三个重要的文件。三个文件分别用于描述“模块”,“平台”,和“包”。他们的关系如下所示:
  1. 平台 (Platform)
  2.    └── 由多个 模块 (Module) 组成
  3.           ├── 来自 包A (Package A)
  4.           ├── 来自 包B (Package B)
  5.           └── 来自 包C (Package C)
复制代码
1. 包(Package)是“资源提供者” 一个包就是一个功能或主题相关的“大仓库”。 例如:

  • MdePkg:最基础的库、头文件、通用协议
  • MdeModulePkg:通用驱动(如控制台、文件系统、USB 等)
  • OvmfPkg:专用于 QEMU/Ovmf 虚拟机的平台包
  • ShellPkg:UEFI Shell 相关模块
包通过 .DEC 文件对外声明:我提供了哪些头文件、哪些库、哪些 GUID、哪些 PCD。
2. 模块(Module)是“可构建单元” 模块是真正会被编译的东西(.efi、.lib)。 每个模块 必须属于某个包,它的 .INF 文件第一件事就是通过 [Packages] 节声明自己属于哪些包,从而获得头文件和定义。 一个 ConOutDxe.inf(控制台输出驱动)属于 MdeModulePkg 这个包。
3. 平台(Platform)是“最终产品组装者” 平台负责决定:“我这个主板/产品要用哪些模块?” 它通过 .DSC 文件的 [Components] 节,把来自不同包的各种模块“挑选”进来,并配置 PCD 值、库映射关系等。 最终通过构建命令生成完整的固件映像。
三个文件的相互引用关系如下:
在 .INF 文件中必须说明引用包,就是当前模块的源码使用了哪些包定义的函数或者接口:
  1. [Packages]
  2.   MdePkg/MdePkg.dec
  3.   MdeModulePkg/MdeModulePkg.dec
复制代码
在 .DSC 文件中需要引用模块,就是需要将那些模块编译进该平台,如:
  1. [Components]
  2.   MdeModulePkg/Universal/Console/ConOutDxe/ConOutDxe.inf
  3.   OvmfPkg/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 依赖表达式。
例如:
  1. [Defines]  INF_VERSION                    = 1.27  BASE_NAME                      = HelloWorld  FILE_GUID                      = 12345678-ABCD-1234-ABCD-123456789ABC  MODULE_TYPE                    = UEFI_APPLICATION   # 或 DXE_DRIVER、BASE 等  VERSION_STRING                 = 1.0  ENTRY_POINT                    = UefiMain          # 入口函数名[Packages]
  2.   MdePkg/MdePkg.dec
  3.   MdeModulePkg/MdeModulePkg.dec[Sources]  HelloWorld.c  HelloWorld.h[LibraryClasses]  UefiLib  UefiApplicationEntryPoint  DebugLib[Protocols]  gEfiShellProtocolGuid           ## CONSUMES[Guids]  gEfiMdeModulePkgTokenSpaceGuid  ## SOMETIMES_PRODUCES
复制代码
3. DSC 文件说明

DSC 为平台描述文件,是整个平台(Platform)的“构建蓝图”。它定义了这个平台要包含哪些模块(INF 文件),库类(LibraryClass)如何映射到具体实现,PCD(Platform 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] 等架构特定节。
例如:
  1. [Defines]
  2.   PLATFORM_NAME                  = MyPlatform
  3.   PLATFORM_GUID                  = 87654321-ABCD-1234-ABCD-123456789ABC
  4.   PLATFORM_VERSION               = 1.0
  5.   DSC_SPECIFICATION              = 1.28
  6.   OUTPUT_DIRECTORY               = Build/MyPlatform
  7.   SUPPORTED_ARCHITECTURES        = IA32|X64
  8.   BUILD_TARGETS                  = DEBUG|RELEASE
  9.   SKUID_IDENTIFIER               = DEFAULT
  10. [LibraryClasses]
  11.   DebugLib| MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
  12.   UefiLib| MdePkg/Library/UefiLib/UefiLib.inf
  13.   # ... 其他库映射
  14. [PcdsFixedAtBuild]
  15.   gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes| 5 | UINT32 | 0x40000005
  16. [Components]
  17.   # 核心模块
  18.   MdeModulePkg/Universal/Console/ConOutDxe/ConOutDxe.inf
  19.   MyPkg/HelloWorld/HelloWorld.inf   # 引用上面的 INF
  20. [Components.X64]
  21.   # 只在 X64 下构建的模块
  22.   OvmfPkg/QemuVideoDxe/QemuVideoDxe.inf
复制代码
4. 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]:声明所有 PCD(FeatureFlag、FixedAtBuild、Dynamic 等)及其默认值、类型、Token。
例如:
  1. [Defines]
  2.   DEC_SPECIFICATION              = 1.27
  3.   PACKAGE_NAME                   = MdePkg
  4.   PACKAGE_GUID                   = 1E0A9C1A-5A9C-4C9A-9B7A-5A9C1E0A9C1A
  5.   PACKAGE_VERSION                = 1.05
  6. [Includes]
  7.   Include
  8.   Include/Ia32                   # 架构特定
  9. [LibraryClasses]
  10.   ## @libraryclass 基础内存操作库
  11.   BaseMemoryLib| Include/Library/BaseMemoryLib.h
  12. [Guids]
  13.   ## Include/Guid/MdePkgTokenSpace.h
  14.   gEfiMdePkgTokenSpaceGuid = { 0x1E0A9C1A, 0x5A9C, 0x4C9A, {0x9B, 0x7A, 0x5A, 0x9C, 0x1E, 0x0A, 0x9C, 0x1A} }
  15. [PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]
  16.   ## 此 PCD 定义 HelloWorld 打印次数
  17.   # @Prompt HelloWorld print times.
  18.   gEfiMdePkgTokenSpaceGuid.PcdHelloWorldPrintTimes|1|UINT32|0x40000005
复制代码
*部分示例代码由 grok 生成
本文参考:https://www.bilibili.com/video/BV17h411x7Wr?spm_id_from=333.788.videopod.sections&vd_source=2ee7caa81fced5c94d0d863e82c6acae
Steady Progress!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册