这一系列文章并不是按照“由浅入深”的原则排列的,更多的是按照“任务驱动”的方式编写的。就是按照:提出问题,解决问题,为什么能解决问题,是否还有其他解决问题的方法,原理探究的方式进行排列的。这种方式可以帮助你快速掌握编程技术,当然要想实现这个目标更重要的是要亲手操作研读代码。
前面的文章中介绍过 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 的内容了。