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;
}
构建与运行
- 将上述代码保存为
ExitDemo.c,放入OvmfPkg/ExitDemo/目录。 - 创建
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
- 在
OvmfPkg/OvmfPkgX64.dsc中添加:
OvmfPkg/ExitDemo/ExitDemo.inf
- 构建并运行:
build -a X64 -p OvmfPkg/OvmfPkgX64.dsc -t GCC5
qemu-system-x86_64 -bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd -net none -nographic
- 在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加载阶段。