这一系列文章并不是按照“由浅入深”的原则排列的,更多的是按照“任务驱动”的方式编写的。就是按照:提出问题,解决问题,为什么能解决问题,是否还有其他解决问题的方法,原理探究的方式进行排列的。这种方式可以帮助你快速掌握编程技术,当然要想实现这个目标更重要的是要亲手操作研读代码。
前面的文章中介绍过 EFI 文件格式的一些简单知识,这里会对一只EFI 做完整的分析,标明每一个字节的含义。
首先,我们选择实验的目标是 \AppPkg\Applications\Hello
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
/***
Print a welcoming message.
Establishes the main structure of the application.
@retval 0 The application exited normally.
@retval Other An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
Print(L"Hello there fellow Programmer.\n");
Print(L"Welcome to the world of EDK II.\n");
return(0);
}
这个代码功能很简单,就是显示两行字符串。生成的代码为 8,160 (0x1FE0)bytes大小。

特别的,要在对应的INF文件中加入下面的语句保证生成 COD文件。
[BuildOptions]
MSFT:*_*_X64_CC_FLAGS = /FAsc /Od
接下来使用 SFF 工具直接分析 EFI:
1.从 0 到 0x3C 是一个 Dos Header。这个只是作为兼容性的结构存在并没有任何功能。
这个结构可以在\MdePkg\Include\IndustryStandard\PeImage.h 看到定义
///
/// PE images can start with an optional DOS header, so if an image is run
/// under DOS it can print an error message.
///
typedef struct {
UINT16 e_magic; ///< Magic number.
UINT16 e_cblp; ///< Bytes on last page of file.
UINT16 e_cp; ///< Pages in file.
UINT16 e_crlc; ///< Relocations.
UINT16 e_cparhdr; ///< Size of header in paragraphs.
UINT16 e_minalloc; ///< Minimum extra paragraphs needed.
UINT16 e_maxalloc; ///< Maximum extra paragraphs needed.
UINT16 e_ss; ///< Initial (relative) SS value.
UINT16 e_sp; ///< Initial SP value.
UINT16 e_csum; ///< Checksum.
UINT16 e_ip; ///< Initial IP value.
UINT16 e_cs; ///< Initial (relative) CS value.
UINT16 e_lfarlc; ///< File address of relocation table.
UINT16 e_ovno; ///< Overlay number.
UINT16 e_res[4]; ///< Reserved words.
UINT16 e_oemid; ///< OEM identifier (for e_oeminfo).
UINT16 e_oeminfo; ///< OEM information; e_oemid specific.
UINT16 e_res2[10]; ///< Reserved words.
UINT32 e_lfanew; ///< File address of new exe header.
} EFI_IMAGE_DOS_HEADER;
使用 SFF 工具可以方便的看到每个项目释义:

可以看到大多数信息都是 00,从前面的实验也可以知道,这是编译过程中被我们的工具擦掉的。
2.接下来是 NT Header, 分为两个部分,一个是 File Header 另一个是 Optional Header.

这个结构同样在 \MdePkg\Include\IndustryStandard\PeImage.h 有定义,可以看到其中是有2部分的。
///
/// @attention
/// EFI_IMAGE_HEADERS64 is for use ONLY by tools.
///
typedef struct {
UINT32 Signature;
EFI_IMAGE_FILE_HEADER FileHeader;
EFI_IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} EFI_IMAGE_NT_HEADERS64;
2.1 先看一下 File Header,从 0xBC到 0xCE.

///
/// COFF File Header (Object and Image).
///
typedef struct {
UINT16 Machine;
UINT16 NumberOfSections;
UINT32 TimeDateStamp;
UINT32 PointerToSymbolTable;
UINT32 NumberOfSymbols;
UINT16 SizeOfOptionalHeader;
UINT16 Characteristics;
} EFI_IMAGE_FILE_HEADER;
其中的 Characteristics 提供了一些基本信息:

2.2 Optional Header 从 0xD0到0x1B4,其中有一些比较重要的信息

比如,这里给出了AddressOfEntryPoint 就是EFI文件的代码入口。
下面是其中的 DataDirectory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES] 定义:

///
/// Optional Header Standard Fields for PE32+.
///
typedef struct {
///
/// Standard fields.
///
UINT16 Magic;
UINT8 MajorLinkerVersion;
UINT8 MinorLinkerVersion;
UINT32 SizeOfCode;
UINT32 SizeOfInitializedData;
UINT32 SizeOfUninitializedData;
UINT32 AddressOfEntryPoint;
UINT32 BaseOfCode;
///
/// Optional Header Windows-Specific Fields.
///
UINT64 ImageBase;
UINT32 SectionAlignment;
UINT32 FileAlignment;
UINT16 MajorOperatingSystemVersion;
UINT16 MinorOperatingSystemVersion;
UINT16 MajorImageVersion;
UINT16 MinorImageVersion;
UINT16 MajorSubsystemVersion;
UINT16 MinorSubsystemVersion;
UINT32 Win32VersionValue;
UINT32 SizeOfImage;
UINT32 SizeOfHeaders;
UINT32 CheckSum;
UINT16 Subsystem;
UINT16 DllCharacteristics;
UINT64 SizeOfStackReserve;
UINT64 SizeOfStackCommit;
UINT64 SizeOfHeapReserve;
UINT64 SizeOfHeapCommit;
UINT32 LoaderFlags;
UINT32 NumberOfRvaAndSizes;
EFI_IMAGE_DATA_DIRECTORY DataDirectory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES];
} EFI_IMAGE_OPTIONAL_HEADER64;
3.紧接着是Section Headers[x]

定义在 \MdePkg\Include\IndustryStandard\PeImage.h
///
/// Section Table. This table immediately follows the optional header.
///
typedef struct {
UINT8 Name[EFI_IMAGE_SIZEOF_SHORT_NAME];
union {
UINT32 PhysicalAddress;
UINT32 VirtualSize;
} Misc;
UINT32 VirtualAddress;
UINT32 SizeOfRawData;
UINT32 PointerToRawData;
UINT32 PointerToRelocations;
UINT32 PointerToLinenumbers;
UINT16 NumberOfRelocations;
UINT16 NumberOfLinenumbers;
UINT32 Characteristics;
} EFI_IMAGE_SECTION_HEADER;
我们这次分析的EFI 文件中各个节如下:
.text 执行代码的节;
.rdata 保存常量数据的节;
.data 保存数据的节,这个对应C语言中以初始化的全局变量数据;
XXXX 一个名称全部为空的节;
.pdata和 .xdata都存放的是异常处理相关的内容。
4.随后是Relocation Directory

对应在 \MdePkg\Include\IndustryStandard\PeImage.h 有如下定义:
///
/// Relocation format.
///
typedef struct {
UINT32 VirtualAddress;
UINT32 SymbolTableIndex;
UINT16 Type;
} EFI_IMAGE_RELOCATION;
5.下面是 Debug Directory 在 \MdePkg\Include\IndustryStandard\PeImage.h 有如下定义
///
/// Debug Directory Format.
///
typedef struct {
UINT32 Characteristics;
UINT32 TimeDateStamp;
UINT16 MajorVersion;
UINT16 MinorVersion;
UINT32 Type;
UINT32 SizeOfData;
UINT32 RVA; ///< The address of the debug data when loaded, relative to the image base.
UINT32 FileOffset; ///< The file pointer to the debug data.
} EFI_IMAGE_DEBUG_DIRECTORY_ENTRY;
Section Headers 后面就是紧密排列着的每个 Section 的内容了。