2. 遍历固件卷中的文件

任务目标

在UEFI环境中,固件卷(Firmware Volume)是存放PEIM、DXE驱动、协议等固件文件的容器。本案例实现一个DXE驱动,在入口点遍历指定固件卷(FV)中所有文件,识别每个文件的类型(EFI_FV_FILETYPE),并输出文件GUID及其在FV中的偏移量。

核心实现

#include <Uefi.h>
#include <Pi/PiFirmwareVolume.h>
#include <Pi/PiFirmwareFile.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>

EFI_STATUS
EFIAPI
FvFileTraversalEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS                    Status;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *FvProtocol;
  EFI_HANDLE                    *HandleBuffer;
  UINTN                         HandleCount;
  UINTN                         Index;
  EFI_GUID                      FileGuid;
  EFI_FV_FILETYPE               FileType;
  EFI_FV_FILE_ATTRIBUTES        FileAttributes;
  UINTN                         FileSize;
  EFI_PHYSICAL_ADDRESS          FileOffset;
  UINT32                        AuthenticationStatus;

  // 1. 定位系统中所有固件卷协议实例
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiFirmwareVolume2ProtocolGuid,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "LocateHandleBuffer failed: %r\n", Status));
    return Status;
  }

  // 2. 遍历每个固件卷
  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    &gEfiFirmwareVolume2ProtocolGuid,
                    (VOID **)&FvProtocol
                    );
    if (EFI_ERROR (Status)) {
      continue;
    }

    DEBUG ((DEBUG_INFO, "=== FV[%d] ===\n", Index));

    // 3. 遍历当前FV中的所有文件
    FileOffset = 0;
    while (TRUE) {
      FileGuid = gZeroGuid;
      FileType = EFI_FV_FILETYPE_ALL;
      FileAttributes = 0;
      FileSize = 0;
      AuthenticationStatus = 0;

      Status = FvProtocol->ReadFile (
                              FvProtocol,
                              &FileGuid,
                              &FileType,
                              &FileAttributes,
                              &FileSize,
                              &FileOffset,       // 输入当前偏移,输出下一文件偏移
                              &AuthenticationStatus
                              );

      if (EFI_ERROR (Status)) {
        if (Status == EFI_NOT_FOUND) {
          // 已遍历完所有文件
          break;
        }
        DEBUG ((DEBUG_ERROR, "ReadFile error: %r\n", Status));
        break;
      }

      // 4. 输出文件信息:GUID、类型、偏移
      DEBUG ((
        DEBUG_INFO,
        "File GUID: %g, Type: 0x%02X, Offset: 0x%08X, Size: 0x%X\n",
        &FileGuid,
        FileType,
        (UINTN)FileOffset,
        FileSize
        ));
    }
  }

  if (HandleBuffer != NULL) {
    FreePool (HandleBuffer);
  }

  return EFI_SUCCESS;
}

代码说明

  • 协议获取:通过LocateHandleBuffer查找系统中所有实现EFI_FIRMWARE_VOLUME2_PROTOCOL的句柄,该协议是访问FV的标准接口。
  • 文件遍历机制:ReadFile函数具有迭代语义——首次调用时FileOffset传入0,函数返回第一个文件的信息并将FileOffset更新为下一个文件的偏移;后续调用沿用该偏移即可依次获取所有文件。当返回EFI_NOT_FOUND时表示遍历结束。
  • 文件类型识别:FileType参数返回文件类型枚举值(如EFI_FV_FILETYPE_RAW=0x01、EFI_FV_FILETYPE_FREEFORM=0x02、EFI_FV_FILETYPE_DRIVER=0x03等),可直接用于判断文件性质。

编译与运行

  1. 工程文件:在EDK2环境中创建驱动工程,将上述代码放入FvFileTraversal.c,并在.inf文件中声明依赖:
    [Sources]
      FvFileTraversal.c
    
    [LibraryClasses]
      UefiBootServicesTableLib
      DebugLib
      BaseMemoryLib
    
    [Protocols]
      gEfiFirmwareVolume2ProtocolGuid
    
  2. 编译:使用EDK2构建命令build -p YourPlatform.dsc -m FvFileTraversal.inf生成驱动。
  3. 加载:在UEFI Shell中执行load FvFileTraversal.efi,驱动入口点自动运行。

验证方法

加载驱动后,在串口调试终端或调试器中观察DEBUG_INFO级别日志。预期输出格式为:

=== FV[0] ===
File GUID: 8C8CE58C-8ADB-4B5E-8B7E-9E4D3A1B2C3D, Type: 0x03, Offset: 0x00000028, Size: 0x4A0
File GUID: 1A2B3C4D-5E6F-7890-ABCD-EF1234567890, Type: 0x01, Offset: 0x000004C8, Size: 0x200
...

通过条件:日志中能列出至少一个FV中的多个文件,且每个文件的GUID非零、Type值在合法枚举范围内(0x00~0xFF)、Offset随遍历递增且与文件实际布局一致。若系统中有多个FV,应能看到FV[0]、FV[1]等多个区块。

常见问题

  • 无输出:确认FV2协议已安装(部分平台需在DXE阶段后才可用),检查LocateHandleBuffer返回的句柄数是否为0。
  • 死循环:若ReadFile返回错误但非EFI_NOT_FOUND,代码已做break处理;若返回EFI_SUCCESS但偏移不前进,需检查FV实现是否符合PI规范。
  • 类型识别:EFI_FV_FILETYPE_ALL(0xFF)仅用于查询通配,实际返回的类型值不会是该值。