UEFI开发学习26 – 获取固件中的资源

前言

在UEFI的项目代码中,除了代码,还可能会包含一些二进制文件,如Intel VBT,DXE Driver, UEFI APP等,代码在执行期间,会将其读取出来做相应的处理。由于文件位于固件内,像EFI_FILE_PROTOCOL、EFI_SIMPLE_FILE_SYSTEM_PROTOCOL这些处理磁盘文件的Protocol是无法使用的。相应的,PI手册中定义了EFI_FIRMWARE_VOLUME2_PROTOCOL来管理固件中的文件。

定义

在PI手册中,EFI_FIRMWARE_VOLUME2_PROTOCOL的定义如下:

typedef struct_EFI_FIRMWARE_VOLUME_PROTOCOL {
EFI_FV_GET_ATTRIBUTES GetVolumeAttributes;
EFI_FV_SET_ATTRIBUTES SetVolumeAttributes;
EFI_FV_READ_FILE ReadFile;
EFI_FV_READ_SECTION ReadSection;
EFI_FV_WRITE_FILE WriteFile;
EFI_FV_GET_NEXT_FILE GetNextFile;
UINT32 KeySize;
EFI_HANDLE ParentHandle;
EFI_FV_GET_INFO GetInfo;
EFI_FV_SET_INFO SetInfo;
} EFI_FIRMWARE_VOLUME2_PROTOCOL;

由于篇幅有限,这一章主要写写EFI_FIRMWARE_VOLUME2_PROTOCOL.ReadFile()和EFI_FIRMWARE_VOLUME2_PROTOCOL.ReadSection()两个函数。

EFI_FIRMWARE_VOLUME2_PROTOCOL.ReadFile()

此函数用于读取指定的文件,并将取得的数据放在Buffer中。其定义如下:

typedef
EFI_STATUS
(EFIAPI * EFI_FV_READ_FILE) (
IN CONST EFI_FIRMWARE_VOLUME2_PROTOCOL *This,
IN CONST EFI_GUID *NameGuid,
IN OUT VOID **Buffer,
IN OUT UINTN *BufferSize,
OUT EFI_FV_FILETYPE *FoundType,
OUT EFI_FV_FILE_ATTRIBUTES *FileAttributes,
OUT UINT32 *AuthenticationStatus
);

函数的几个输入参数中:

This - 表示此Protocol的实例

NameGuid - 需要读取的文件Guid值

Buffer - 存放返回的数据,且不包含文件头部信息;当作为输入时,给它一个指向NULL的指针或者分配一个内存地址均可

BufferSize - 即Buffer的大小

FoundType - 返回数据的文件类型,如RAW,FREEFORM,PEIM,DRIVER等

FileAttributes - 文件属性,主要有对齐信息,是否为Memory Map,地址是否可变等

AuthenticationStatus - 返回有关读取文件的身份验证状态的信息

EFI_FIRMWARE_VOLUME2_PROTOCOL.ReadSection()

此函数用于获取指定文件中特定的Section数据。其定义如下:

typedef
EFI_STATUS
(EFIAPI * EFI_FV_READ_SECTION) (
IN CONST EFI_FIRMWARE_VOLUME2_PROTOCOL *This,
IN CONST EFI_GUID *NameGuid,
IN EFI_SECTION_TYPE SectionType,
IN UINTN SectionInstance,
IN OUT VOID **Buffer,
IN OUT UINTN *BufferSize,
OUT UINT32 *AuthenticationStatus
);

This - 表示此Protocol的实例

NameGuid - 需要读取的文件Guid值

SectionType - 指定获取的Section类型

SectionInstance - Section实例的索引值,一个文件中可能有多个同类型的section

Buffer - 存放返回的数据,且不包含文件头部信息;当作为输入时,给它一个指向NULL的指针或者分配一个内存地址均可

BufferSize - 即Buffer的大小

FileAttributes - 文件属性,主要有对齐信息,是否为Memory Map,地址是否可变等

AuthenticationStatus - 返回有关读取文件的身份验证状态的信息

实例1

使用ReadFile读取固件中的shell.efi的信息。

/**
 * @file FirmwareVolume.c
 * 
 * @author https://ay123.net 
 * @version 0.1
 * @date 2023-10-29
 * 
 * @copyright Copyright (c) 2015 - 2023
 * 
 */

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Pi/PiFirmwareVolume.h>
#include <Pi/PiFirmwareFile.h>
#include <Protocol/FirmwareVolume2.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_FIRMWARE_VOLUME2_PROTOCOL         *FwVol;
  EFI_STATUS                            Status;
  EFI_HANDLE                            *HandleBuffer;
  UINTN                                 NumberOfHandles;
  UINTN                                 Index;
  EFI_FV_FILETYPE                       FileType;
  UINT32                                FvStatus;
  EFI_FV_FILE_ATTRIBUTES                Attributes;
  UINTN                                 Size;
  UINT8                                 *Buffer = NULL;

  FwVol = NULL;

  ///
  /// Locate FV protocol.
  ///
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiFirmwareVolume2ProtocolGuid,
                  NULL,
                  &NumberOfHandles,
                  &HandleBuffer
                  );
  if (EFI_ERROR(Status)) {
    Print (L"No Efi Firmware Volume Protocol available.\n");
    return EFI_UNSUPPORTED;
  } else {
    Print (L"Efi Firmware Volume Protocol is loaded, num:%d.\n", NumberOfHandles);
  }

  for (Index = 0; Index < NumberOfHandles; Index ++) {
    Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&FwVol);
    if (!EFI_ERROR(Status)) {
      Status = FwVol->ReadFile (
                            FwVol,
                            &gUefiShellFileGuid,
                            (VOID**)&Buffer,
                            &Size,
                            &FileType,
                            &Attributes,
                            &FvStatus
                            );
      if (!EFI_ERROR(Status)) {
        
        SectionBuf = (EFI_USER_INTERFACE_SECTION *)Buffer;

        Print (L"File Size = %ld\n\r", Size);
        Print (L"File Type = %d\n\r", FileType);
        Print (L"File Attributes = %8X\n\r", Attributes);
        Print (L"File Buffer:\n\r");

        for (Index = 0; Index < 64; Index ++) {
          if ((Index % 16) == 0 && Index > 0) {
            Print (L"\n\r");
          }
          Print (L"%2X ", Buffer[Index]);
        }
        Print (L"\n\r");
      }
      return EFI_SUCCESS;
    }
  }

  return EFI_SUCCESS;
}

运行结果:


运行结果中,头部数据是一致的,可以大小却有差异,难道读错了?实际上,此处获取到的buffer数据不单单是shell.efi,还包含了raw, ui, version等section信息,这些信息组成了一个ffs文件,可在编译生成目录RELEASE_VS2015x86\FV\Ffs\7C04A583-9E3E-4f1c-AD65-E05268D0B4D1Shell 中查看。当把这几个文件的大小加在一起,结果便相同了。

为什么不直接对比ffs的文件大小呢?因为ReadFile读的便是ffs文件的数据,返回时去掉了头部部分(前文关于Buffer的定义有描述),即这4个文件大小的总和。

实例2

使用ReadSection()获取指定Section类型的数据,并尽可能输出多个SectionInstance的信息。

由于EDK2代码中找不到具有相同section类型的多个Instance,所以只能自己在固件中加入,这不得不用到QEMU了。

首先来构造这样一个文件,用HxD制作2个二进制文件,然后添加到inf文件中,并加入到OvmfPkg\OvmfPkgX64.dsc和OvmfPkg\OvmfPkgX64.fdf文件。

[Defines]
  INF_VERSION                = 0x00010017
  BASE_NAME                  = TestBin
  FILE_GUID                  = 944cadd5-7c14-462f-aa96-298f2004e0b0
  MODULE_TYPE                = USER_DEFINED
  VERSION_STRING             = 1.0

[Binaries.X64]
  BIN|Test1.bin
  BIN|Test2.bin

接着再在fdf文件定义包含此二进制文件的规则。

[Rule.Common.USER_DEFINED]
  FILE FREEFORM = $(NAMED_GUID) {
    RAW BIN                |.bin
  }

[Rule.Common.USER_DEFINED.BINARY]
  FILE FREEFORM = $(NAMED_GUID) {
    RAW BIN                |.bin
  }

编译后,Test1.bin和Test2.bin 将被包含到ovmf固件中。接着继续写测试代码:

/**
 * @file FirmwareVolume.c
 * 
 * @author https://ay123.net 
 * @version 0.1
 * @date 2023-10-29
 * 
 * @copyright Copyright (c) 2015 - 2023
 * 
 */

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Pi/PiFirmwareVolume.h>
#include <Pi/PiFirmwareFile.h>
#include <Protocol/FirmwareVolume2.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>

GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID gEfiTestBinGuid = {0x944cadd5, 0x7c14, 0x462f, {0xaa, 0x96,0x29, 0x8f, 0x20, 0x04, 0xe0, 0xb0}};

VOID Ay123HexDump (UINT8 *Buffer, UINT8 RowNum)
{
  UINT8   Cols;
  UINT8   Rows;

  for (Rows = 0; Rows < RowNum; Rows ++) {
    for (Cols = 0; Cols < 16; Cols ++) {
      Print (L"%2X ", Buffer[Cols+Rows*16]);
    }

    Print (L"  ");

    for (Cols = 0; Cols < 16; Cols ++) {
      Print (L"%c", Buffer[Cols+Rows*16]);
    }
    Print (L"\n\r");
  }
  Print (L"\n\r");
}


EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_FIRMWARE_VOLUME2_PROTOCOL         *FwVol;
  EFI_STATUS                            Status;
  EFI_HANDLE                            *HandleBuffer;
  UINTN                                 NumberOfHandles;
  UINTN                                 Index;
  UINT32                                FvStatus;
  UINTN                                 Size;
  UINT8                                 *Buffer = NULL;

  FwVol = NULL;

  ///
  /// Locate FV protocol.
  ///
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiFirmwareVolume2ProtocolGuid,
                  NULL,
                  &NumberOfHandles,
                  &HandleBuffer
                  );
  if (EFI_ERROR(Status)) {
    Print (L"No Efi Firmware Volume Protocol available.\n");
    return EFI_UNSUPPORTED;
  } else {
    Print (L"Efi Firmware Volume Protocol is loaded, num:%d.\n", NumberOfHandles);
  }

  for (Index = 0; Index < NumberOfHandles; Index ++) {
    Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&FwVol);
    if (!EFI_ERROR(Status)) {
      UINTN SectionInstance = 0;
      while (Status == EFI_SUCCESS)
      {
        Status = FwVol->ReadSection (
                              FwVol,
                              &gEfiTestBinGuid,
                              EFI_SECTION_RAW,
                              SectionInstance,
                              (VOID**)&Buffer,
                              &Size,
                              &FvStatus
                              );
        if (!EFI_ERROR(Status)) {
          Print (L"File Size = %ld\n\r", Size);
          Print (L"File Buffer:\n\r");
          Ay123HexDump(Buffer, 3);
        }
        SectionInstance ++;
      }
      return EFI_SUCCESS;
    }
  }
  return EFI_SUCCESS;
}

运行结果

ReadSection()函数成功读取了包含的两个文件,且不包含任何头部信息,更加精准的得到了所需的数据。

附件

https://gitee.com/ay123net/uefistudy

版权声明:
作者:bin
链接:https://ay123.net/mystudy/uefi/1671/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
海报
UEFI开发学习26 – 获取固件中的资源
前言 在UEFI的项目代码中,除了代码,还可能会包含一些二进制文件,如Intel VBT,DXE Driver, UEFI APP等,代码在执行期间,会将其读取出来做相应的处理。由于……
<<上一篇
下一篇>>
文章目录
关闭
目 录