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/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。




共有 0 条评论