2. UEFI应用退出与返回固件

任务目标

编写一个UEFI应用,演示两种从应用返回固件的方式:正常返回(return EFI_SUCCESS)与调用ExitBootServices退出启动服务。通过观察应用退出后的行为差异,理解UEFI应用生命周期与启动服务的边界。

环境与依赖

  • EDK2开发环境(OvmfPkg/QEMU或真实硬件)
  • 使用MdeModulePkg/MdeModulePkg.dsc作为平台构建描述
  • 应用入口函数原型:EFI_APPLICATION_ENTRY_POINT

核心实现

以下代码实现一个UEFI Shell下可运行的应用。它首先打印当前内存映射项数,然后尝试调用ExitBootServices(传入无效的MemoryMapKey以观察失败路径),最后通过正常返回退出。代码中同时包含一个条件编译开关,用于切换“仅正常返回”与“尝试ExitBootServices”两种模式。

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

#define TEST_EXIT_BOOT_SERVICES  1   // 1=尝试调用ExitBootServices, 0=仅正常返回

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  UINTN       MemoryMapSize = 0;
  EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL;
  UINTN       MapKey = 0;
  UINTN       DescriptorSize = 0;
  UINT32      DescriptorVersion = 0;

  Print(L"[UEFI-App] Entered UefiMain.\n");

  // 1. 获取当前内存映射信息(仅用于演示,不实际使用)
  Status = gBS->GetMemoryMap (
                  &MemoryMapSize,
                  MemoryMap,
                  &MapKey,
                  &DescriptorSize,
                  &DescriptorVersion
                  );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    // 预期行为:第一次调用返回缓冲区过小
    Print(L"[UEFI-App] GetMemoryMap returned BUFFER_TOO_SMALL (expected).\n");
  } else if (EFI_ERROR(Status)) {
    Print(L"[UEFI-App] GetMemoryMap error: %r\n", Status);
    return Status;
  }

#if TEST_EXIT_BOOT_SERVICES
  // 2. 尝试调用ExitBootServices(使用无效的MapKey,预期返回EFI_INVALID_PARAMETER)
  Print(L"[UEFI-App] Calling ExitBootServices with invalid MapKey...\n");
  Status = gBS->ExitBootServices (ImageHandle, MapKey);
  if (EFI_ERROR(Status)) {
    Print(L"[UEFI-App] ExitBootServices returned error: %r (expected)\n", Status);
    Print(L"[UEFI-App] Boot Services still active. Continuing as normal UEFI app.\n");
  } else {
    // 若意外成功,则后续不能再调用Boot Services
    Print(L"[UEFI-App] ExitBootServices succeeded! This should not happen with invalid key.\n");
    // 此时再调用Print会崩溃,因此直接返回
    return EFI_SUCCESS;
  }
#endif

  // 3. 正常返回固件(Shell)
  Print(L"[UEFI-App] Exiting and returning to firmware (Shell).\n");
  return EFI_SUCCESS;
}

构建与运行

  1. 将上述代码保存为ExitDemo.c,放入OvmfPkg/ExitDemo/目录。
  2. 创建ExitDemo.inf文件:
[Defines]
  INF_VERSION = 0x00010005
  BASE_NAME = ExitDemo
  FILE_GUID = 8D0E8C7A-2F5A-4B6E-9C3D-1A2B3C4D5E6F
  MODULE_TYPE = UEFI_APPLICATION
  VERSION_STRING = 1.0
  ENTRY_POINT = UefiMain

[Sources]
  ExitDemo.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiBootServicesTableLib
  UefiLib
  1. 在OvmfPkg/OvmfPkgX64.dsc中添加:
OvmfPkg/ExitDemo/ExitDemo.inf
  1. 构建并运行:
build -a X64 -p OvmfPkg/OvmfPkgX64.dsc -t GCC5
qemu-system-x86_64 -bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd -net none -nographic
  1. 在UEFI Shell中运行:
fs0:\ExitDemo.efi

验证方法与判定标准

  • 正常返回路径:当TEST_EXIT_BOOT_SERVICES设为0时,应用打印退出信息后返回Shell提示符(Shell>),说明固件成功回收控制权。
  • ExitBootServices失败路径:当宏设为1时,应用应打印ExitBootServices returned error: Invalid Parameter,随后继续执行并正常返回Shell。这证明无效的MemoryMapKey不会终止应用,且Boot Services在退出前仍可用。
  • 判定通过条件:应用能编译链接生成.efi文件;在Shell中运行后,观察到的输出与上述预期一致;应用退出后Shell提示符恢复,无系统挂起或重启。

关键说明

  • 本案例刻意使用无效的MapKey调用ExitBootServices,以演示失败路径——真实场景中,调用前必须用GetMemoryMap获取有效Key,且调用后不能再使用任何Boot Services(包括Print)。
  • 若需演示成功退出启动服务,需在获取有效MapKey后立即调用,且调用后只能使用Runtime Services或直接返回固件——但UEFI应用通常不这样做,因为退出启动服务后固件将不再管理设备,系统进入OS加载阶段。