【UEFI基础】EDK编译生成的二进制的结构

综述

EDK代码经过编译后,会得到如下的二进制(当然还有很多中间文件,这里并不关注):

  • xxx.Fd
  • xxx.Fv
  • xxx.efi

比如在编译OVMF的时候,会生成如下的文件(具体的编译方法可以参考【UEFI实战】OVMF基础_jiangwei0512的博客-CSDN博客):

它们的组成关系如下:

  • FD包含FV
  • FV包含EFI

首先是FD对FV的包含关系,该关系比较简单,就是按照fdf文件中的定义将FV堆叠起来构成一个FD文件,下面是OVMF中的fdf文件对FD的描述:

[FD.OVMF]
BaseAddress   = $(FW_BASE_ADDRESS)
Size          = $(FW_SIZE)
ErasePolarity = 1
BlockSize     = $(BLOCK_SIZE)
NumBlocks     = $(FW_BLOCKS)

!include VarStore.fdf.inc

$(VARS_SIZE)|$(FVMAIN_SIZE)
FV = FVMAIN_COMPACT

$(SECFV_OFFSET)|$(SECFV_SIZE)
FV = SECFV

这里包含两个FV以及头部的变量区(再VarStore.fdf.inc中定义),$(VARS_SIZE)指定了FV_MAIN_COMPACT整个FV的起始地址,$(FVMAIN_SIZE)表示的是它的大小,SECFV也是类似。最终得到的FD的大致形式如下图所示:

而FV对EFI的包含要稍微复杂一些,大致的形式如下图所示:

这个图来自:http://101.96.10.65/www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/fsp-architecture-spec-v2.pdf。FSP INFO HEADER可以不关注。这里需要关注的是几个Header:

  1. Firmware Volume Header
  2. Firmware Volume Extended Header
  3. Firmware File Header
  4. Raw Section Header

它们分别在以下的两个文件中定义:

  • MdePkg\Include\Pi\PiFirmwareVolume.h
  • MdePkg\Include\Pi\PiFirmwareFile.h

具体的结构体将在下文介绍。

Firmware Volume Header

首先介绍Firmware Volume Header这个结构体:

///
/// Describes the features and layout of the firmware volume.
///
typedef struct {
  ///
  /// The first 16 bytes are reserved to allow for the reset vector of
  /// processors whose reset vector is at address 0.
  ///
  UINT8                     ZeroVector[16];
  ///
  /// Declares the file system with which the firmware volume is formatted.
  ///
  EFI_GUID                  FileSystemGuid;
  ///
  /// Length in bytes of the complete firmware volume, including the header.
  ///
  UINT64                    FvLength;
  ///
  /// Set to EFI_FVH_SIGNATURE
  ///
  UINT32                    Signature;
  ///
  /// Declares capabilities and power-on defaults for the firmware volume.
  ///
  EFI_FVB_ATTRIBUTES_2      Attributes;
  ///
  /// Length in bytes of the complete firmware volume header.
  ///
  UINT16                    HeaderLength;
  ///
  /// A 16-bit checksum of the firmware volume header. A valid header sums to zero.
  ///
  UINT16                    Checksum;
  ///
  /// Offset, relative to the start of the header, of the extended header
  /// (EFI_FIRMWARE_VOLUME_EXT_HEADER) or zero if there is no extended header.
  ///
  UINT16                    ExtHeaderOffset;
  ///
  /// This field must always be set to zero.
  ///
  UINT8                     Reserved[1];
  ///
  /// Set to 2. Future versions of this specification may define new header fields and will
  /// increment the Revision field accordingly.
  ///
  UINT8                     Revision;
  ///
  /// An array of run-length encoded FvBlockMapEntry structures. The array is
  /// terminated with an entry of {0,0}.
  ///
  EFI_FV_BLOCK_MAP_ENTRY    BlockMap[1];
} EFI_FIRMWARE_VOLUME_HEADER;

以前面图中的DXEFV.Fv为例:

1. 文件的最开始是ZeroVector,它就是16个0x00,跟上图符合。

2. FileSystemGuid也是16个字节,在fdf文件中,每个FV都有一个FvNameGuid:

[FV.DXEFV]
FvNameGuid         = 7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1
BlockSize          = 0x10000

不过这里并不是使用这个,而是使用了如下的GUID:

  ## Include/Guid/FirmwareFileSystem2.h
  gEfiFirmwareFileSystem2Guid     = { 0x8c8ce578, 0x8a3d, 0x4f1c, { 0x99, 0x35, 0x89, 0x61, 0x85, 0xc3, 0x2d, 0xd3 }}

这说明了它是Firmware File System 2类型的,这也是当前的最新版本(EFI_FVH_REVISION的值是0x02)。

3. FvLength表示FV的大小,这里是A00000h,就是10M,符合DXEFV.Fv的大小,不过在并入最后的FD的时候它是会被压缩的。注意它的长度是64位的。

4. Signature是一个标识符,对于Firmware Volume Header来说,使用的标识是“_FVH”。

5. Attributes有如下的取值:

//
// Attributes bit definitions
//
#define EFI_FVB2_READ_DISABLED_CAP  0x00000001
#define EFI_FVB2_READ_ENABLED_CAP   0x00000002
#define EFI_FVB2_READ_STATUS        0x00000004
#define EFI_FVB2_WRITE_DISABLED_CAP 0x00000008
#define EFI_FVB2_WRITE_ENABLED_CAP  0x00000010
#define EFI_FVB2_WRITE_STATUS       0x00000020
#define EFI_FVB2_LOCK_CAP           0x00000040
#define EFI_FVB2_LOCK_STATUS        0x00000080
#define EFI_FVB2_STICKY_WRITE       0x00000200
#define EFI_FVB2_MEMORY_MAPPED      0x00000400
#define EFI_FVB2_ERASE_POLARITY     0x00000800
#define EFI_FVB2_READ_LOCK_CAP      0x00001000
#define EFI_FVB2_READ_LOCK_STATUS   0x00002000
#define EFI_FVB2_WRITE_LOCK_CAP     0x00004000
#define EFI_FVB2_WRITE_LOCK_STATUS  0x00008000
#define EFI_FVB2_ALIGNMENT          0x001F0000
#define EFI_FVB2_ALIGNMENT_1        0x00000000
#define EFI_FVB2_ALIGNMENT_2        0x00010000
#define EFI_FVB2_ALIGNMENT_4        0x00020000
#define EFI_FVB2_ALIGNMENT_8        0x00030000
#define EFI_FVB2_ALIGNMENT_16       0x00040000
#define EFI_FVB2_ALIGNMENT_32       0x00050000
#define EFI_FVB2_ALIGNMENT_64       0x00060000
#define EFI_FVB2_ALIGNMENT_128      0x00070000
#define EFI_FVB2_ALIGNMENT_256      0x00080000
#define EFI_FVB2_ALIGNMENT_512      0x00090000
#define EFI_FVB2_ALIGNMENT_1K       0x000A0000
#define EFI_FVB2_ALIGNMENT_2K       0x000B0000
#define EFI_FVB2_ALIGNMENT_4K       0x000C0000
#define EFI_FVB2_ALIGNMENT_8K       0x000D0000
#define EFI_FVB2_ALIGNMENT_16K      0x000E0000
#define EFI_FVB2_ALIGNMENT_32K      0x000F0000
#define EFI_FVB2_ALIGNMENT_64K      0x00100000
#define EFI_FVB2_ALIGNMENT_128K     0x00110000
#define EFI_FVB2_ALIGNMENT_256K     0x00120000
#define EFI_FVB2_ALIGNMENT_512K     0x00130000
#define EFI_FVB2_ALIGNMENT_1M       0x00140000
#define EFI_FVB2_ALIGNMENT_2M       0x00150000
#define EFI_FVB2_ALIGNMENT_4M       0x00160000
#define EFI_FVB2_ALIGNMENT_8M       0x00170000
#define EFI_FVB2_ALIGNMENT_16M      0x00180000
#define EFI_FVB2_ALIGNMENT_32M      0x00190000
#define EFI_FVB2_ALIGNMENT_64M      0x001A0000
#define EFI_FVB2_ALIGNMENT_128M     0x001B0000
#define EFI_FVB2_ALIGNMENT_256M     0x001C0000
#define EFI_FVB2_ALIGNMENT_512M     0x001D0000
#define EFI_FVB2_ALIGNMENT_1G       0x001E0000
#define EFI_FVB2_ALIGNMENT_2G       0x001F0000
#define EFI_FVB2_WEAK_ALIGNMENT     0x80000000

这里的值是0x4FEFF,它是一个32位的值,最终就是如下的值:

#define EFI_FVB2_READ_DISABLED_CAP  0x00000001
#define EFI_FVB2_READ_ENABLED_CAP   0x00000002
#define EFI_FVB2_READ_STATUS        0x00000004
#define EFI_FVB2_WRITE_DISABLED_CAP 0x00000008
#define EFI_FVB2_WRITE_ENABLED_CAP  0x00000010
#define EFI_FVB2_WRITE_STATUS       0x00000020
#define EFI_FVB2_LOCK_CAP           0x00000040
#define EFI_FVB2_LOCK_STATUS        0x00000080
#define EFI_FVB2_STICKY_WRITE       0x00000200
#define EFI_FVB2_MEMORY_MAPPED      0x00000400
#define EFI_FVB2_ERASE_POLARITY     0x00000800
#define EFI_FVB2_READ_LOCK_CAP      0x00001000
#define EFI_FVB2_READ_LOCK_STATUS   0x00002000
#define EFI_FVB2_WRITE_LOCK_CAP     0x00004000
#define EFI_FVB2_WRITE_LOCK_STATUS  0x00008000
#define EFI_FVB2_ALIGNMENT_16       0x00040000

这对应到fdf文件中FV这个Section的属性:

[FV.DXEFV]
FvNameGuid         = 7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1
BlockSize          = 0x10000
FvAlignment        = 16
ERASE_POLARITY     = 1
MEMORY_MAPPED      = TRUE
STICKY_WRITE       = TRUE
LOCK_CAP           = TRUE
LOCK_STATUS        = TRUE
WRITE_DISABLED_CAP = TRUE
WRITE_ENABLED_CAP  = TRUE
WRITE_STATUS       = TRUE
WRITE_LOCK_CAP     = TRUE
WRITE_LOCK_STATUS  = TRUE
READ_DISABLED_CAP  = TRUE
READ_ENABLED_CAP   = TRUE
READ_STATUS        = TRUE
READ_LOCK_CAP      = TRUE
READ_LOCK_STATUS   = TRUE

6. HeaderLength大小为16位,这里的值是0x48,即72个字节:

7. Checksum就不多说了,也是16位大小。

8. ExtHeaderOffset表示的是Firmware Volume Extended Header的偏移。这里的值是0x60。

9. Reserved占一个字节。

10. Revision占一个字节,这里的值是0x02。

11. BlockMap是一个数组,不过只有一个成员,类型是EFI_FV_BLOCK_MAP_ENTRY:

typedef struct {
  ///
  /// The number of sequential blocks which are of the same size.
  ///
  UINT32 NumBlocks;
  ///
  /// The size of the blocks.
  ///
  UINT32 Length;
} EFI_FV_BLOCK_MAP_ENTRY;

这里的值分别是0xA0和0x10000,它们的乘积就是整个FV的大小了。不过因为HeaderLength的值是0x48,所以后面还有8个0x00,也应该是这里的一部分,这从BlockMap的注释中也可以看出来。

Firmware Volume Extended Header

下面介绍Firmware Volume Extended Header:

///
/// Extension header pointed by ExtHeaderOffset of volume header.
///
typedef struct {
  ///
  /// Firmware volume name.
  ///
  EFI_GUID  FvName;
  ///
  /// Size of the rest of the extension header, including this structure.
  ///
  UINT32    ExtHeaderSize;
} EFI_FIRMWARE_VOLUME_EXT_HEADER;

当然这还不是全部,后面还可以跟着一个个的ExtEntry,其结构如下:

///
/// Entry struture for describing FV extension header
///
typedef struct {
  ///
  /// Size of this header extension.
  ///
  UINT16    ExtEntrySize;
  ///
  /// Type of the header.
  ///
  UINT16    ExtEntryType;
} EFI_FIRMWARE_VOLUME_EXT_ENTRY;

到这里也还不是完全体,根据不同的ExtEntryType可以有不同的值,主要有以下两种类型,分别是

#define EFI_FV_EXT_TYPE_OEM_TYPE  0x01
///
/// This extension header provides a mapping between a GUID and an OEM file type.
///
typedef struct {
  ///
  /// Standard extension entry, with the type EFI_FV_EXT_TYPE_OEM_TYPE.
  ///
  EFI_FIRMWARE_VOLUME_EXT_ENTRY Hdr;
  ///
  /// A bit mask, one bit for each file type between 0xC0 (bit 0) and 0xDF (bit 31). If a bit
  /// is '1', then the GUID entry exists in Types. If a bit is '0' then no GUID entry exists in Types.
  ///
  UINT32    TypeMask;
  ///
  /// An array of GUIDs, each GUID representing an OEM file type.
  ///
  /// EFI_GUID  Types[1];
  ///
} EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE;

#define EFI_FV_EXT_TYPE_GUID_TYPE 0x0002

///
/// This extension header EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE provides a vendor specific
/// GUID FormatType type which includes a length and a successive series of data bytes.
///
typedef struct {
  ///
  /// Standard extension entry, with the type EFI_FV_EXT_TYPE_OEM_TYPE.
  ///
  EFI_FIRMWARE_VOLUME_EXT_ENTRY     Hdr;
  ///
  /// Vendor-specific GUID.
  ///
  EFI_GUID                          FormatType;
  ///
  /// An arry of bytes of length Length.
  ///
  /// UINT8                             Data[1];
  ///
} EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE;

这两个也是不定结构体,就是说下面还可以接东西。还是以DXEFV.Fv为例,前面已经看到Firmware Volume Extended Header的起始位置是0x60:

1. 起始的FvName也是一个GUID,这个才是写在fdf文件各个FV Section中的GUID:

[FV.DXEFV]
FvNameGuid         = 7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1
BlockSize          = 0x10000

2. ExtHeaderSize的值是0x14,即20个字节,也就是说,包含一个GUID加这个参数刚好20个字节,后面没有跟ExtEntry。

之后的4个字节的FF是为了8字节对齐,再后面的部分应该是Firmware File的Header了。不过再继续往后介绍之前,还需要解决一个问题,即在Firmware Volume Header和Firmware Volume Extended Header之间还有一部分内容没有说明,它是从0x48到0x60之间的数据:

这部分内容没有在前面的FV示意图中出现,所以为了进一步了解它,就需要查看生成FV的源代码,可以在BaseTools\Source\C\GenFv\GenFv.c中找到:

    //
    // Call the GenerateFvImage to generate Fv Image
    //
    Status = GenerateFvImage (
              InfFileImage,
              InfFileSize,
              OutFileName,
              MapFileName
              );

进一步追踪代码,最后找到的是AddPadFile这个函数(BaseTools\Source\C\GenFv\GenFvInternalLib.c):

EFI_STATUS
AddPadFile (
  IN OUT MEMORY_FILE  *FvImage,
  IN UINT32           DataAlignment,
  IN VOID             *FvEnd,
  IN EFI_FIRMWARE_VOLUME_EXT_HEADER *ExtHeader,
  IN UINT32           NextFfsSize
  )
/*++

Routine Description:

  This function adds a pad file to the FV image if it required to align the
  data of the next file.

从注释说明来看,它是Firmware Volume Header和Firmware Volume Extended Header之间的填充,但是实际上这里是做了一层兼容性操作,它把Firmware Volume Extended Header包装成了一个Firmware File,所以Firmware Volume Extended Header就成了一个包含在Firmware File中的内容,所以在它就有了一个Firmware File的头部,如下所示:

//
// Each file begins with the header that describe the 
// contents and state of the files.
// 
typedef struct {
  EFI_GUID                Name;
  EFI_FFS_INTEGRITY_CHECK IntegrityCheck;
  EFI_FV_FILETYPE         Type;
  EFI_FFS_FILE_ATTRIBUTES Attributes;
  UINT8                   Size[3];
  EFI_FFS_FILE_STATE      State;
} EFI_FFS_FILE_HEADER;

它正好是0x18个字节,其中的Name是全FF,因为它本身不存在实际意义;之后是一个Checksum占两个字节;再之后是类型为0xF0的FFS文件(后面会看到0xF0表示的是用于填充的类型,EFI_FV_FILETYPE_FFS_PAD);然后是属性值0x00,它表示的不是真实的FFS文件属性;其后是FFS文件的大小,它的值是0x2C,即44个字节,它包含了后续Firmware Volume Extended Header(0x14),这样总计就是0x2C(0x18+0x14);最后是状态。关于FFS文件的类型、属性和状态在后面介绍Firmware File的时候还会继续介绍。

可以说前面的FV示意图还是少了一部分,即最后介绍的EFI_FFS_FILE_HEADER,这里做补充。之后就要开始介绍真正的文件,而真正的文件开始也是一个EFI_FFS_FILE_HEADER(或者EFI_FFS_FILE_HEADER2)。

Firmware File Header

首先还是要说明Firmware File的头部,它有两种类型。下面是一代Firmware File Header(其实在前面已经介绍过了):

///
/// Each file begins with the header that describe the
/// contents and state of the files.
///
typedef struct {
  ///
  /// This GUID is the file name. It is used to uniquely identify the file.
  ///
  EFI_GUID                Name;
  ///
  /// Used to verify the integrity of the file.
  ///
  EFI_FFS_INTEGRITY_CHECK IntegrityCheck;
  ///
  /// Identifies the type of file.
  ///
  EFI_FV_FILETYPE         Type;
  ///
  /// Declares various file attribute bits.
  ///
  EFI_FFS_FILE_ATTRIBUTES Attributes;
  ///
  /// The length of the file in bytes, including the FFS header.
  ///
  UINT8                   Size[3];
  ///
  /// Used to track the state of the file throughout the life of the file from creation to deletion.
  ///
  EFI_FFS_FILE_STATE      State;
} EFI_FFS_FILE_HEADER;

下面是二代Firmware File Header:

typedef struct {
  ///
  /// This GUID is the file name. It is used to uniquely identify the file. There may be only
  /// one instance of a file with the file name GUID of Name in any given firmware
  /// volume, except if the file type is EFI_FV_FILETYPE_FFS_PAD.
  ///
  EFI_GUID                  Name;

  ///
  /// Used to verify the integrity of the file.
  ///
  EFI_FFS_INTEGRITY_CHECK   IntegrityCheck;

  ///
  /// Identifies the type of file.
  ///
  EFI_FV_FILETYPE           Type;

  ///
  /// Declares various file attribute bits.
  ///
  EFI_FFS_FILE_ATTRIBUTES   Attributes;

  ///
  /// The length of the file in bytes, including the FFS header.
  /// The length of the file data is either (Size - sizeof(EFI_FFS_FILE_HEADER)). This calculation means a
  /// zero-length file has a Size of 24 bytes, which is sizeof(EFI_FFS_FILE_HEADER).
  /// Size is not required to be a multiple of 8 bytes. Given a file F, the next file header is
  /// located at the next 8-byte aligned firmware volume offset following the last byte of the file F.
  ///
  UINT8                     Size[3];

  ///
  /// Used to track the state of the file throughout the life of the file from creation to deletion.
  ///
  EFI_FFS_FILE_STATE        State;

  ///
  /// If FFS_ATTRIB_LARGE_FILE is set in Attributes, then ExtendedSize exists and Size must be set to zero.
  /// If FFS_ATTRIB_LARGE_FILE is not set then EFI_FFS_FILE_HEADER is used.
  ///
  UINT64                    ExtendedSize;
} EFI_FFS_FILE_HEADER2;

二代其实就是在一代的基础上做了扩充,所以二代是一个不定结构体,之后还有扩展内容。为了判断是哪种类型,提供了如下的宏来判断:

#define IS_FFS_FILE2(FfsFileHeaderPtr) \
    (((((EFI_FFS_FILE_HEADER *) (UINTN) FfsFileHeaderPtr)->Attributes) & FFS_ATTRIB_LARGE_FILE) == FFS_ATTRIB_LARGE_FILE)

还是从DXEFV.Fv开始看,前面已经说到从0x78开始就是第一个Firmware File了(正式的第一个,但实际上已经是第二个了):

1. 最开始Name也是一个GUID,第一个Firmware File对应的是Apriori文件,它的GUID如下:

  ## Include/Guid/Apriori.h
  gAprioriGuid                   = { 0xFC510EE7, 0xFFDC, 0x11D4, { 0xBD, 0x41, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }}

2. IntegrityCheck可以看成是一个Checksum,具体类型如下,它占据2个字节:

///
/// Used to verify the integrity of the file.
///
typedef union {
  struct {
    ///
    /// The IntegrityCheck.Checksum.Header field is an 8-bit checksum of the file
    /// header. The State and IntegrityCheck.Checksum.File fields are assumed
    /// to be zero and the checksum is calculated such that the entire header sums to zero.
    ///
    UINT8   Header;
    ///
    /// If the FFS_ATTRIB_CHECKSUM (see definition below) bit of the Attributes
    /// field is set to one, the IntegrityCheck.Checksum.File field is an 8-bit
    /// checksum of the file data.
    /// If the FFS_ATTRIB_CHECKSUM bit of the Attributes field is cleared to zero,
    /// the IntegrityCheck.Checksum.File field must be initialized with a value of
    /// 0xAA. The IntegrityCheck.Checksum.File field is valid any time the
    /// EFI_FILE_DATA_VALID bit is set in the State field.
    ///
    UINT8   File;
  } Checksum;
  ///
  /// This is the full 16 bits of the IntegrityCheck field.
  ///
  UINT16    Checksum16;
} EFI_FFS_INTEGRITY_CHECK;

3. Type占据一个字节,有以下的取值:

///
/// File Types Definitions
///
#define EFI_FV_FILETYPE_ALL                   0x00
#define EFI_FV_FILETYPE_RAW                   0x01
#define EFI_FV_FILETYPE_FREEFORM              0x02
#define EFI_FV_FILETYPE_SECURITY_CORE         0x03
#define EFI_FV_FILETYPE_PEI_CORE              0x04
#define EFI_FV_FILETYPE_DXE_CORE              0x05
#define EFI_FV_FILETYPE_PEIM                  0x06
#define EFI_FV_FILETYPE_DRIVER                0x07
#define EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER  0x08
#define EFI_FV_FILETYPE_APPLICATION           0x09
#define EFI_FV_FILETYPE_SMM                   0x0A
#define EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE 0x0B
#define EFI_FV_FILETYPE_COMBINED_SMM_DXE      0x0C
#define EFI_FV_FILETYPE_SMM_CORE              0x0D
#define EFI_FV_FILETYPE_OEM_MIN               0xc0
#define EFI_FV_FILETYPE_OEM_MAX               0xdf
#define EFI_FV_FILETYPE_DEBUG_MIN             0xe0
#define EFI_FV_FILETYPE_DEBUG_MAX             0xef
#define EFI_FV_FILETYPE_FFS_MIN               0xf0
#define EFI_FV_FILETYPE_FFS_MAX               0xff
#define EFI_FV_FILETYPE_FFS_PAD               0xf0

这里的取值是0x02,即FreeForm。

4. Attributes也占据一个字节,取值如下:

///
/// FFS File Attributes.
///
#define FFS_ATTRIB_LARGE_FILE         0x01
#define FFS_ATTRIB_FIXED              0x04
#define FFS_ATTRIB_DATA_ALIGNMENT     0x38
#define FFS_ATTRIB_CHECKSUM           0x40

这里的值是0x00,跟上面的都不一样,表示不带属性?因为FFS_ATTRIB_LARGE_FILE是来表示这个Header是否是2代的(参考2代结构体的注释说明),目前来看这个Header不是2代。

5. Size占3个字节,这里的取值是0x4C,即76个字节。这就是整个Firmware File的大小,包括头部。

6. State占据1个字节,它的取值如下:

///
/// FFS File State Bits.
///
#define EFI_FILE_HEADER_CONSTRUCTION  0x01
#define EFI_FILE_HEADER_VALID         0x02
#define EFI_FILE_DATA_VALID           0x04
#define EFI_FILE_MARKED_FOR_UPDATE    0x08
#define EFI_FILE_DELETED              0x10
#define EFI_FILE_HEADER_INVALID       0x20

这里的值是0xF8,看上去不像是一个正常的Header。

查看它的数据,从0x78开始,总共有0x4C个数据,前面的是Header,所以这里从0x90开始到0xC4的数据是该Firmware File的数据:

那么这些数据是什么呢?将在"Raw Section Header"中介绍。

Raw Section Header

Raw Section Header也分为两代,一个是普通的,一个是扩展后的。下面是普通的Raw Section Header:

///
/// Common section header.
///
typedef struct {
  ///
  /// A 24-bit unsigned integer that contains the total size of the section in bytes,
  /// including the EFI_COMMON_SECTION_HEADER.
  ///
  UINT8             Size[3];
  EFI_SECTION_TYPE  Type;
  ///
  /// Declares the section type.
  ///
} EFI_COMMON_SECTION_HEADER;

下面是扩展的Raw Section Header:

typedef struct {
  ///
  /// A 24-bit unsigned integer that contains the total size of the section in bytes,
  /// including the EFI_COMMON_SECTION_HEADER.
  ///
  UINT8             Size[3];


  EFI_SECTION_TYPE  Type;


  ///
  /// If Size is 0xFFFFFF, then ExtendedSize contains the size of the section. If
  /// Size is not equal to 0xFFFFFF, then this field does not exist.
  ///
  UINT32            ExtendedSize;
} EFI_COMMON_SECTION_HEADER2;

无论是普通还是扩展,都也可能不是全部,可能只是一个更大的Raw Section的一部分。下面是几类Raw Section:

Compression Section:

///
/// An encapsulation section type in which the
/// section data is compressed.
///
typedef struct {
  ///
  /// Usual common section header. CommonHeader.Type = EFI_SECTION_COMPRESSION.
  ///
  EFI_COMMON_SECTION_HEADER   CommonHeader;
  ///
  /// The UINT32 that indicates the size of the section data after decompression.
  ///
  UINT32                      UncompressedLength;
  ///
  /// Indicates which compression algorithm is used.
  ///
  UINT8                       CompressionType;
} EFI_COMPRESSION_SECTION;

typedef struct {
  ///
  /// Usual common section header. CommonHeader.Type = EFI_SECTION_COMPRESSION.
  ///
  EFI_COMMON_SECTION_HEADER2    CommonHeader;
  ///
  /// UINT32 that indicates the size of the section data after decompression.
  ///
  UINT32                        UncompressedLength;
  ///
  /// Indicates which compression algorithm is used.
  ///
  UINT8                         CompressionType;
} EFI_COMPRESSION_SECTION2;

Freeform Subtype GUID Section:

///
/// The leaf section which contains a single GUID.
///
typedef struct {
  ///
  /// Common section header. CommonHeader.Type = EFI_SECTION_FREEFORM_SUBTYPE_GUID.
  ///
  EFI_COMMON_SECTION_HEADER   CommonHeader;
  ///
  /// This GUID is defined by the creator of the file. It is a vendor-defined file type.
  ///
  EFI_GUID                    SubTypeGuid;
} EFI_FREEFORM_SUBTYPE_GUID_SECTION;

typedef struct {
  ///
  /// The common section header. CommonHeader.Type = EFI_SECTION_FREEFORM_SUBTYPE_GUID.
  ///
  EFI_COMMON_SECTION_HEADER2    CommonHeader;
  ///
  /// This GUID is defined by the creator of the file. It is a vendor-defined file type.
  ///
  EFI_GUID                      SubTypeGuid;
} EFI_FREEFORM_SUBTYPE_GUID_SECTION2;

GUID Defined Section:

///
/// The leaf section which is encapsulation defined by specific GUID.
///
typedef struct {
  ///
  /// The common section header. CommonHeader.Type = EFI_SECTION_GUID_DEFINED.
  ///
  EFI_COMMON_SECTION_HEADER   CommonHeader;
  ///
  /// The GUID that defines the format of the data that follows. It is a vendor-defined section type.
  ///
  EFI_GUID                    SectionDefinitionGuid;
  ///
  /// Contains the offset in bytes from the beginning of the common header to the first byte of the data.
  ///
  UINT16                      DataOffset;
  ///
  /// The bit field that declares some specific characteristics of the section contents.
  ///
  UINT16                      Attributes;
} EFI_GUID_DEFINED_SECTION;

typedef struct {
  ///
  /// The common section header. CommonHeader.Type = EFI_SECTION_GUID_DEFINED.
  ///
  EFI_COMMON_SECTION_HEADER2    CommonHeader;
  ///
  /// The GUID that defines the format of the data that follows. It is a vendor-defined section type.
  ///
  EFI_GUID                      SectionDefinitionGuid;
  ///
  /// Contains the offset in bytes from the beginning of the common header to the first byte of the data.
  ///
  UINT16                        DataOffset;
  ///
  /// The bit field that declares some specific characteristics of the section contents.
  ///
  UINT16                        Attributes;
} EFI_GUID_DEFINED_SECTION2;

User Interface Section:

///
/// The leaf section which contains a unicode string that
/// is human readable file name.
///
typedef struct {
  EFI_COMMON_SECTION_HEADER   CommonHeader;

  ///
  /// Array of unicode string.
  ///
  CHAR16                      FileNameString[1];
} EFI_USER_INTERFACE_SECTION;

typedef struct {
  EFI_COMMON_SECTION_HEADER2    CommonHeader;
  CHAR16                        FileNameString[1];
} EFI_USER_INTERFACE_SECTION2;

Version Section:

///
/// The leaf section which contains a numeric build number and
/// an optional unicode string that represents the file revision.
///
typedef struct {
  EFI_COMMON_SECTION_HEADER   CommonHeader;
  UINT16                      BuildNumber;

  ///
  /// Array of unicode string.
  ///
  CHAR16                      VersionString[1];
} EFI_VERSION_SECTION;

typedef struct {
  EFI_COMMON_SECTION_HEADER2    CommonHeader;
  ///
  /// A UINT16 that represents a particular build. Subsequent builds have monotonically
  /// increasing build numbers relative to earlier builds.
  ///
  UINT16                        BuildNumber;
  CHAR16                        VersionString[1];
} EFI_VERSION_SECTION2;

另外还有很多种的Raw Section,不过多是EFI_COMMON_SECTION_HEADER或EFI_COMMON_SECTION_HEADER2的别名,具体有哪些可以看下面:

typedef union {
  EFI_COMMON_SECTION_HEADER         *CommonHeader;
  EFI_COMPRESSION_SECTION           *CompressionSection;
  EFI_GUID_DEFINED_SECTION          *GuidDefinedSection;
  EFI_PE32_SECTION                  *Pe32Section;
  EFI_PIC_SECTION                   *PicSection;
  EFI_TE_SECTION                    *TeSection;
  EFI_PEI_DEPEX_SECTION             *PeimHeaderSection;
  EFI_DXE_DEPEX_SECTION             *DependencySection;
  EFI_VERSION_SECTION               *VersionSection;
  EFI_USER_INTERFACE_SECTION        *UISection;
  EFI_COMPATIBILITY16_SECTION       *Code16Section;
  EFI_FIRMWARE_VOLUME_IMAGE_SECTION *FVImageSection;
  EFI_FREEFORM_SUBTYPE_GUID_SECTION *FreeformSubtypeSection;
  EFI_RAW_SECTION                   *RawSection;
  //
  // For section whose size is equal or greater than 0x1000000
  //
  EFI_COMMON_SECTION_HEADER2         *CommonHeader2;
  EFI_COMPRESSION_SECTION2           *CompressionSection2;
  EFI_GUID_DEFINED_SECTION2          *GuidDefinedSection2;
  EFI_PE32_SECTION2                  *Pe32Section2;
  EFI_PIC_SECTION2                   *PicSection2;
  EFI_TE_SECTION2                    *TeSection2;
  EFI_PEI_DEPEX_SECTION2             *PeimHeaderSection2;
  EFI_DXE_DEPEX_SECTION2             *DependencySection2;
  EFI_VERSION_SECTION2               *VersionSection2;
  EFI_USER_INTERFACE_SECTION2        *UISection2;
  EFI_COMPATIBILITY16_SECTION2       *Code16Section2;
  EFI_FIRMWARE_VOLUME_IMAGE_SECTION2 *FVImageSection2;
  EFI_FREEFORM_SUBTYPE_GUID_SECTION2 *FreeformSubtypeSection2;
  EFI_RAW_SECTION2                   *RawSection2;
} EFI_FILE_SECTION_POINTER;

接着上面提到的二进制:

1. Size占据3个字节,这里就是0x34,刚好到0xC4的位置,后面跟的4个FF是用来8字节对齐的。

2. Type占据1个字节,有以下的取值:

///
/// Leaf section Type values.
///
#define EFI_SECTION_PE32                  0x10
#define EFI_SECTION_PIC                   0x11
#define EFI_SECTION_TE                    0x12
#define EFI_SECTION_DXE_DEPEX             0x13
#define EFI_SECTION_VERSION               0x14
#define EFI_SECTION_USER_INTERFACE        0x15
#define EFI_SECTION_COMPATIBILITY16       0x16
#define EFI_SECTION_FIRMWARE_VOLUME_IMAGE 0x17
#define EFI_SECTION_FREEFORM_SUBTYPE_GUID 0x18
#define EFI_SECTION_RAW                   0x19
#define EFI_SECTION_PEI_DEPEX             0x1B
#define EFI_SECTION_SMM_DEPEX             0x1C

这里的值是0x19,表示的是RAW数据。如下所示:

///
/// The leaf section which contains an array of zero or more bytes.
///
typedef EFI_COMMON_SECTION_HEADER   EFI_RAW_SECTION;
typedef EFI_COMMON_SECTION_HEADER2  EFI_RAW_SECTION2;

RAW数据的Raw Seciton大小就是一个EFI_COMMON_SECTION_HEADER,而后者的大小就是4个字节,所以从0x94的就是真正的数据了。但是这些数据是什么呢?从前面的Firmware File Header结构体成员FvName中我们可以知道它是一个Apriori,对应到fdf文件中就是如下的内容:

APRIORI DXE {
  INF  MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
  INF  MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
!if $(SMM_REQUIRE) == FALSE
  INF  OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
!endif
}

由于SMM_REQUIRE的值是FALSE,所以这里包含所有3个模块。这3个模块的GUID分别是:

9B680FCE-AD6B-4F3A-B60B-F59899003443
80CF7257-87AB-47f9-A3FE-D50B76D89541
733cbac2-b23f-4b92-bc8e-fb01ce5907b7

正好和前面的内容一一对应,所以DXEFV.Fv中包含的第一个Firmware File就是Apriori,里面包含若干了GUID。

efi格式

前面介绍的Firmware File只是一个Apriori,还没有涉及到efi模块,我们继续从FV中找efi。

FV中的efi模块

对于DXEFV.Fv,我们继续往下找,理论上能够找到下面的efi模块:

INF  MdeModulePkg/Core/Dxe/DxeMain.inf

这是定义在fdf文件中Apriori后续的第一个模块。查看DXEFV.Fv的后续内容:

从0xC4到0xC8之间是填充0xFF来做8字节对齐,后面就是Firmware File Header。它的第一个元素是Name,它是一个GUID,对应的值是:

D6A2CB7F-6A18-4e2f-B43B-9920A733700A

刚好和DXEFV.Fv的后续内容一致。接着是IntegrityCheck,是一个16位的值,这里的值是0xAAB1,属于Checksum,不用特别关注。接着是Type,它的值是0x05,表示的是EFI_FV_FILETYPE_DXE_CORE,也能够对应起来。接着是Attributes,它的值是0x00,这个也没有特别的说明,不过至少说明该Firmware File Header不是扩展型的。接着是Size,它的值是0x22D5E,包括头部。我们查看DxeCore.efi的实际大小:

转化到十六进制是0x22D20,加上Firmware File Header(大小是0x18)和Raw Section Header(大小是0x4,后面会说明),这跟计算的结果不匹配,这是为什么?后续再说明。

之后是Raw Section Header:

它的大小只有4个字节,表示的分别是大小(0x22D24,包含头部)和类型(EFI_SECTION_PE32)。Raw Section Header中的大小加上Header本身的大小刚好0x22D24,所以真正的DxeCore.efi在FV中的位置是0xE4-0x22E03。查看FV中的该位置:

前面也已经讲到,Firmware File Header里面的大小是0x22D5E,似乎还多了一些字节。而从上图,我们看到了DxeCore等的字样,它应该也是Firmware File的一部分。不过它们是在哪里定义的呢?具体是什么东西呢?可以认为Firmware File最后还有一个结束结构体,但是这个结构体是什么内容,目前没有找到具体的说明

efi格式具体说明

下面开始说明efi文件。以下是一个efi文件二进制的头部:

efi文件符合的是《Microsoft Portable Executable and Common Object File Format Specification》(后称Spec),文档可以在https://msdn.microsoft.com/zh-cn/windows/hardware/gg463080.aspx下载到。在这个文档中描述了二进制头部的格式如下:

它的这种格式被称为Microsoft PE format

MS-DOS 2.0 Section

前面一大部分,即MS-DOS 2.0 Section并没有什么用,但是指明了两点:

1. Signature是“MZ”。

2. 0x3c位置是PE头的偏移。这里就是0x80:

COFF File Header(PE Header)

从0x80开始首先是Signature “PE”,它其实是占了4个字节,所以应该是“PE\0\0”。再之后是称为COFF File Header的部分。这个头部的介绍如下(以0x80为基址计算):

0x04. Machine:这个程序对应的目标机器类型,占两个字节,比如这里的值是0x8664,表示的就是Intel X64的机器。具体的值可以参考Spec;

0x06. NumberOfSections:段的数量,占两个字节,这里的值是3,表示有三个段,分别是text、data和reloc,后面可以看到;

0x08. TimeDateStamp:占4个字节,表示的是从1970年1月1日0时开始的秒数,然而这里并没有使用;

0x0C. PointerToSymbolTable:占4个字节,指向System Table,这里的值是0,一般都不会用这个,因为已经被弃用。

0x10. NumberOfSysmbols:占4个字节,也不用了,所以值是0;

0x14. SizeOfOptionalHeader:占2个字节,Optional Header的大小,这里的值是0xF0;

0x16. Characteristics:占2个字节,属性值,具体可以参考Spec;

Optional Header

前面已经提到了,Optional Header的大小是0xF0,虽然没有某个成员特别说明存在Optional Header,但是既然大小都有了,存在也是肯定的了。关于Optional Header,Spec中有如下的说明:

Every image file has an optional headerthat provides information to the loader. This header is optional in the sensethat some files (specifically, object files) do not have it.For image files,this header is required. An object file can have an optional header, butgenerally this header has no function in an object file except to increase its size. 

也就是说是,对于我们的efi文件,肯定是有的。下面是具体的Header:

位置是从0x98开始到0x187为止。关于Optional Header,涉及到的成员太多了,就不一一介绍了,还是参考Spec。

Section Header

再往后就是Section Header了。在PE Header中已经提到过,这个efi文件有三个Section,所以也就有三个Section Header:

三个Section Header的名称分别是.text,.data和.reloc。下面说明Section Header的成员:

从0x240开始就是真正的代码了,这个值也在.text这个Section Header中指定了。

以上就是efi文件格式的介绍。

efi文件可以直接通过工具查看。因为这里使用了VS2015编译的UEFI代码,因此可以通过VS2015自带的工具dumpbin来查看efi文件。首先使用下面目录里的一个命令提示符(可以随意选):

然后就进入了如下的界面:

在这里可以查看dumpbin工具:

然后进入到EDK编译目录下,就可以查看efi文件的信息了,比如:

需要注意:

1. 不能使用普通的CMD,不然没有dumpbin命令;

2. 这里的信息跟前面的截图不能完成匹配,因为不是一个时间段写的,内容可能不一样了。

  • 15
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
编译生成 OVMF 是指通过 ed2k 来获得 OVMF 源代码,并将其编译成可执行文件的过程。 首先,我们需要通过 ed2k 下载 OVMF 的源代码。ed2k 是一种文件传输协议,可以在网络上找到对应的资源。我们可以使用 ed2k 客户端软件来下载 OVMF。打开 ed2k 客户端,粘贴 OVMF 的 ed2k 链接,然后开始下载。 下载完成后,我们需要解压缩 OVMF 的源代码。可以使用压缩软件(如 WinRAR)将下载的文件解压缩到一个目录中。 接下来,我们需要打开命令行终端,并进入到解压缩后的 OVMF 源代码目录。 在命令行中输入编译命令来生成 OVMF。具体的编译命令可能因操作系统和编译器而不同。常见的编译命令包括 make、gcc、cmake 等。我们可以查阅 OVMF 的文档或官方网站来获取准确的编译命令。 根据编译命令的要求,执行相应的编译命令,等待编译过程完成。编译过程可能需要一定的时间,取决于计算机性能和源代码的复杂度。 一旦编译完成,我们可以在指定的目录中找到生成的 OVMF 可执行文件。该文件就是编译生成的 OVMF。 最后,我们可以将生成的 OVMF 可执行文件用于相应的应用,如虚拟机管理器(例如 QEMU、VirtualBox)中来运行 UEFI 镜像。 总结来说,编译生成 OVMF 需要下载 OVMF 源代码,解压缩,进入命令行终端,执行编译命令,等待编译完成,并获取生成的 OVMF 可执行文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值