1. 解析UEFI启动流程与平台初始化阶段划分

任务目标

编写一个UEFI应用程序,在启动时打印当前所处的阶段,并识别SEC、PEI、DXE、BDS、RT各阶段的入口点特征。该程序作为DXE阶段的UEFI Application运行,通过UEFI系统表与运行时服务定位各阶段入口点的特征地址,并在串口/控制台输出阶段标识。

实现原理

UEFI平台初始化按SEC→PEI→DXE→BDS→RT顺序推进。本案例程序在DXE阶段被加载执行,通过以下方式识别各阶段:

  • SEC:无标准UEFI接口,入口点为处理器复位向量(0xFFFFFFF0),通过读取固件基址附近特征判断。
  • PEI:通过EFI_PEI_SERVICES结构定位,其签名PEIM位于服务表头部。
  • DXE:当前程序运行阶段,通过gST->Hdr.Signature(EFI_SYSTEM_TABLE_SIGNATURE)确认。
  • BDS:通过gST->BootServices中的ExitBootServices入口地址间接识别(BDS阶段负责调用它)。
  • RT:通过gST->RuntimeServices表头签名EFI_RUNTIME_SERVICES_SIGNATURE确认。

完整代码

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>

// PEI服务签名
#define PEI_SERVICES_SIGNATURE  0x565245535f494550ULL  // "PEI_SERV"

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS            Status;
  UINTN                 Index;
  EFI_PEI_SERVICES      **PeiServices;
  EFI_RUNTIME_SERVICES  *RT;
  UINT64                SecEntry;
  UINT64                DxeEntry;
  UINT64                BdsEntry;

  // 1. 识别当前阶段:DXE(通过系统表签名)
  if (SystemTable->Hdr.Signature == EFI_SYSTEM_TABLE_SIGNATURE) {
    Print(L"[阶段识别] 当前运行阶段: DXE\r\n");
    Print(L"  - 系统表签名: 0x%016lx (EFI_SYSTEM_TABLE_SIGNATURE)\r\n",
          SystemTable->Hdr.Signature);
  }

  // 2. 识别RT阶段:运行时服务表签名
  RT = SystemTable->RuntimeServices;
  if (RT != NULL && RT->Hdr.Signature == EFI_RUNTIME_SERVICES_SIGNATURE) {
    Print(L"[阶段识别] RT阶段入口特征: 运行时服务表已就绪\r\n");
    Print(L"  - RT服务表签名: 0x%016lx\r\n", RT->Hdr.Signature);
    Print(L"  - GetTime入口: 0x%p\r\n", RT->GetTime);
    Print(L"  - SetVariable入口: 0x%p\r\n", RT->SetVariable);
  }

  // 3. 识别BDS阶段:通过ExitBootServices入口(BDS阶段调用此函数切换到RT)
  if (SystemTable->BootServices != NULL) {
    BdsEntry = (UINT64)(UINTN)SystemTable->BootServices->ExitBootServices;
    Print(L"[阶段识别] BDS阶段入口特征: ExitBootServices @ 0x%p\r\n",
          (VOID *)(UINTN)BdsEntry);
    Print(L"  - BDS阶段负责调用ExitBootServices以退出启动服务\r\n");
  }

  // 4. 识别DXE阶段入口:DXE核心通过系统表BootServices分发
  DxeEntry = (UINT64)(UINTN)SystemTable->BootServices;
  Print(L"[阶段识别] DXE阶段入口特征: BootServices表 @ 0x%p\r\n",
        (VOID *)(UINTN)DxeEntry);
  Print(L"  - BootServices表签名: 0x%016lx\r\n",
        SystemTable->BootServices->Hdr.Signature);

  // 5. 识别PEI阶段:通过定位PEI服务表(在HOB列表中查找)
  //    实际PEI服务在DXE阶段已不可直接访问,此处通过HOB列表中的引导服务
  //    数据结构间接确认PEI阶段的存在与入口特征。
  PeiServices = NULL;
  // 遍历HOB列表查找PEI服务表指针(简化:通过引导服务分配内存搜索)
  // 此处演示通过DXE阶段可访问的HOB列表定位PEI阶段特征
  {
    EFI_PHYSICAL_ADDRESS  HobStart;
    EFI_HOB_HANDOFF_INFO_TABLE  *HandoffTable;

    HobStart = SystemTable->BootServices->GetHobList();
    HandoffTable = (EFI_HOB_HANDOFF_INFO_TABLE *)HobStart;
    if (HandoffTable != NULL && HandoffTable->Header.HobType == EFI_HOB_TYPE_HANDOFF) {
      Print(L"[阶段识别] PEI阶段入口特征: HOB列表起始 @ 0x%p\r\n",
            (VOID *)(UINTN)HobStart);
      Print(L"  - 引导模式: 0x%x\r\n", HandoffTable->BootMode);
      // 在HOB列表中搜索PEI服务表指针(类型EFI_HOB_TYPE_GUID_EXTENSION)
      // 实际固件中PEI服务表地址可通过特定GUID HOB获取
      // 此处演示通过HOB类型识别PEI阶段遗留信息
      for (Index = 0; Index < 16; Index++) {
        EFI_HOB_GENERIC_HEADER  *Hob;
        Hob = (EFI_HOB_GENERIC_HEADER *)(UINTN)(HobStart + Index * sizeof(UINT64));
        if (Hob->HobType == EFI_HOB_TYPE_GUID_EXTENSION) {
          Print(L"  - 发现GUID扩展HOB (PEI阶段遗留数据)\r\n");
          break;
        }
      }
    }
  }

  // 6. 识别SEC阶段:复位向量位于4GB-16字节处(0xFFFFFFF0)
  SecEntry = 0xFFFFFFF0ULL;
  Print(L"[阶段识别] SEC阶段入口特征: 复位向量 @ 0x%llx\r\n", SecEntry);
  Print(L"  - SEC阶段从复位向量开始执行,初始化临时RAM\r\n");

  // 7. 打印阶段划分总结
  Print(L"\r\n[阶段划分总结]\r\n");
  Print(L"  SEC: 0xFFFFFFF0 (复位向量)\r\n");
  Print(L"  PEI: HOB列表 @ 0x%p (PEI服务已移交DXE)\r\n",
        (VOID *)(UINTN)SystemTable->BootServices->GetHobList());
  Print(L"  DXE: BootServices @ 0x%p (当前执行阶段)\r\n",
        (VOID *)(UINTN)SystemTable->BootServices);
  Print(L"  BDS: ExitBootServices @ 0x%p\r\n",
        (VOID *)(UINTN)SystemTable->BootServices->ExitBootServices);
  Print(L"  RT:  RuntimeServices @ 0x%p\r\n", (VOID *)(UINTN)RT);

  return EFI_SUCCESS;
}

编译与运行

  1. 环境:EDK2开发环境(含OVMF模拟器或真实UEFI平台)
  2. 编译:将上述代码保存为PhaseDetect.c,在.inf文件中声明UefiMain入口,使用build命令编译生成PhaseDetect.efi
  3. 运行:在UEFI Shell中执行PhaseDetect.efi,或在OVMF中通过启动菜单加载

验证方法

程序执行后,串口或控制台输出应包含以下关键信息:

  • 首行显示当前运行阶段: DXE,确认程序在DXE阶段被加载
  • 显示RT阶段入口特征及GetTime/SetVariable函数指针地址(非零值)
  • 显示BDS阶段入口特征及ExitBootServices地址(非零值)
  • 显示PEI阶段入口特征及HOB列表起始地址(非零值)
  • 显示SEC阶段入口特征为固定地址0xFFFFFFF0
  • 末尾输出五阶段总结表,各阶段地址均为有效非零值

若在OVMF中运行,所有输出应正常显示且无错误码返回(程序返回EFI_SUCCESS)。

常见问题

  • 编译错误:确认.inf文件中包含UefiLib、UefiBootServicesTableLib、UefiRuntimeServicesTableLib库依赖
  • 输出乱码:检查串口终端波特率与固件设置一致(通常115200)
  • HOB遍历不完整:实际固件HOB结构可能更复杂,本示例仅演示定位方法,不保证所有固件均能遍历到GUID扩展HOB