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 条评论