2. 创建第一个UEFI应用程序

任务目标

编写一个最小的UEFI应用程序,在UEFI Shell或启动管理器加载该应用时,向控制台输出字符串 Hello, UEFI World!,并返回状态码 EFI_SUCCESS。本案例聚焦于理解UEFI应用程序的入口点签名、必要的头文件以及编译链接的基本流程。

环境与依赖

  • 操作系统:Linux(本文以Ubuntu 22.04为例)或Windows(需安装对应工具链)
  • EDK II 开发环境:已安装并配置好 edk2 源码树(推荐使用 edk2-stable202311 或更新版本)
  • 编译工具链:GCC(Linux)或 Visual Studio(Windows),EDK II 构建系统会自动检测
  • 模拟器(可选):QEMU + OVMF 固件,用于在无真实硬件时验证运行

实现步骤

在 EDK II 源码树中,应用程序通常放在 AppPkg/Applications/ 或自定义包目录下。本案例使用一个独立包 HelloWorldPkg 来保持最小化。

1. 创建包目录结构

edk2/
└── HelloWorldPkg/
    ├── HelloWorldPkg.dsc          # 包描述文件
    └── Applications/
        └── HelloWorld/
            ├── HelloWorld.c       # 主程序源码
            └── HelloWorld.inf     # 模块描述文件

2. 编写主程序源码(HelloWorld.c)

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>

/**
  应用程序入口点。

  @param[in] ImageHandle    加载的固件映像句柄。
  @param[in] SystemTable    指向系统服务表的指针。

  @return 返回 EFI_SUCCESS 表示成功。
**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print (L"Hello, UEFI World!\n");
  return EFI_SUCCESS;
}

3. 编写模块描述文件(HelloWorld.inf)

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = HelloWorld
  FILE_GUID                      = 8D0E8C3A-6F2A-4A6B-9B5F-3D7C1A2B4E5F
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

4. 编写包描述文件(HelloWorldPkg.dsc)

[Defines]
  PLATFORM_NAME                  = HelloWorldPkg
  PLATFORM_GUID                  = 9E4F2A1C-3B5D-4E6F-8A7B-2C3D4E5F6A7B
  PLATFORM_VERSION               = 1.0
  DSC_SPECIFICATION              = 0x00010005
  OUTPUT_DIRECTORY               = Build/HelloWorldPkg
  SUPPORTED_ARCHITECTURES        = IA32|X64
  BUILD_TARGETS                  = DEBUG|RELEASE
  SKUID_IDENTIFIER               = DEFAULT

[LibraryClasses.common]
  UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
  UefiLib|MdePkg/Library/UefiLib/UefiLib.inf

[Components]
  HelloWorldPkg/Applications/HelloWorld/HelloWorld.inf

编译与运行

编译

cd edk2
source edksetup.sh          # Linux 下初始化环境
build -p HelloWorldPkg/HelloWorldPkg.dsc -m HelloWorldPkg/Applications/HelloWorld/HelloWorld.inf -a X64 -t GCC5

编译成功后,生成的 HelloWorld.efi 文件位于 Build/HelloWorldPkg/DEBUG_GCC5/X64/ 目录下。

运行(QEMU + OVMF 模拟)

# 准备包含 HelloWorld.efi 的 FAT 镜像(假设已挂载到 /mnt)
cp Build/HelloWorldPkg/DEBUG_GCC5/X64/HelloWorld.efi /mnt/
qemu-system-x86_64 -bios /path/to/OVMF.fd -hda fat:rw:/mnt -nographic

在 QEMU 的 UEFI Shell 中,切换到 FS0: 并执行:

FS0:\> HelloWorld.efi

验证方法与判定标准

  • 编译验证:执行 build 命令后,终端输出 Build... done 且无错误信息,即编译通过。
  • 运行验证:在 UEFI Shell 中运行 HelloWorld.efi 后,屏幕显示 Hello, UEFI World!,随后 Shell 提示符重新出现,说明程序正常返回。
  • 状态码验证:在 Shell 中执行 echo %lasterror%,若输出 Success,则证明返回值为 EFI_SUCCESS。

常见问题

  • 编译报错找不到头文件:确认 MdePkg.dec 路径正确,且 edksetup.sh 已成功执行。
  • 运行后无输出:检查 HelloWorld.efi 是否被正确复制到 FAT 分区,且文件系统可读。
  • GUID 冲突:每个模块的 FILE_GUID 必须唯一,可使用 uuidgen 命令生成新 GUID。