inf是属于UEFI的标准应用程序工程模块的工程文件,每个程序工程模块,由工程文件和源文件组成。源文件就是c/asm这些程序文件,工程文件就是inf文件。inf文件相当于Makefile文件一样,是对源文件去做整合编译成模块文件的工程文件,用于让edk2编译工具去自动编译模块。
inf工程文件分为很多个块,每个块以 [块名] 开头,这个 [块名] 必须独立一行。其中一些块是inf必须要有的,还有一些不是必须的。
1、[Defines]块:
属性定义语法:
属性名 = 属性值
[Defines]
INF_VERSION = 0x00010005 //必须 ,INF标准的版本号。edsk2的build会检查这个值而去解释.inf文件,最新的版本号是0x00010016,通常设置成0x00010015就行
BASE_NAME = DxeCore //必须,模块名字字符串,不能包含空格。它通常也是输出文件的名字,生成文件DxeMain.efi
MODULE_UNI_FILE = DxeCore.uni //非必须 ,字符串资源文件
FILE_GUID = D6A2CB7F-6A18-4e2f-B43B-9920A733700A //必须, 8-4-4-4-4-12格式的唯一GUID,用于生成固件,
MODULE_TYPE = DXE_CORE //必须 ,定义模块的模块类型。SEC、PEIM、DXE_DEIVER等
VERSION_STRING = 1.0 //必须, 模块的版本号字符串
ENTRY_POINT = DxeMain //必须,模块的入口函数
2、[Sources]块:
此块下的每一行表示一个文件,文件使用相对路径
[Sources] //一般情况下不指定特定编译项时,使用这个即可,适用于任何体系结构
Test.c
[Sources.IA32] //编译32位模块时(build命令选项中指定了-a IA32选项),包含这个和Sources中的Test.c
Cpu32.c
[Sources.X64] //编译64位模块时,包含这个和Sources中的Test.c
Cpu64.c
[Sources]
TimerWin.c | MSFT //指定编译器有效
TimerLinux.c | GCC //指定编译器有效
3、[Packages]块:
此块下面列出了模块所引用到的所有包的声明文件dec文件。如果在Sources块内列出了源文件,在此块下必须列出MdePkg/MePkg.dec,并放在此块的首行
4、[LibraryClasses]块:
此块内列出了模块所要链接的所有库模块(使用了库的库函数就要列出来)。库定义在包的dsc文件中
5、[BuildOptions]块:
1、语法:
[BuildOptions]
[编译器家族] : [$ (Target)][Tool_CHAIN_TAG][$ (Arch)]_[CC | DLINK]_FLAGS [= |==]选项
2、编译器家族可以是MSFT(Visual Studio编译器家族)、INTEL(Intel编译器家族)、GCC(Gcc编译器家族)和 RVCT(ARM编译器家族)中的一个
Target是DEBUG、RELEASE和*中的一个,*是通配符
TOOL_CHAIN_TAG是编译器的名字,编译器定义在Conf/tools_def.txt文件中,*表示对指定编译器家族内的所有编译器都有效
Arch是体系结构,可以是IA32、X64、IPF、EBC或ARM,*是通配符
CC表示编译选项,DLINK表示链接选项
=表示选项附加到默认选项后面,==表示仅使用所定义的选项,不用默认选项。它们后面是编译选项或连接选项
[BuildOptions]
# Enable STDARG for variable arguments
*_*_*_CC_FLAGS = -DHAVE_STDARG_H
# Override MSFT build option to remove /Oi and /GL
MSFT:*_*_*_CC_FLAGS = /GL-
INTEL:*_*_*_CC_FLAGS = /Oi-
# Oniguruma: potentially uninitialized local variable used
MSFT:*_*_*_CC_FLAGS = /wd4701 /wd4703
# Oniguruma: intrinsic function not declared
MSFT:*_*_*_CC_FLAGS = /wd4164
# Oniguruma: old style declaration in st.c
MSFT:*_*_*_CC_FLAGS = /wd4131
# Oniguruma: 'type cast' : truncation from 'OnigUChar *' to 'unsigned int'
MSFT:*_*_*_CC_FLAGS = /wd4305 /wd4306
# Oniguruma: nameless union declared in regparse.h
MSFT:*_*_*_CC_FLAGS = /wd4201
# Oniguruma: 'type cast' : "int" to "OnigUChar", function pointer to "void *"
MSFT:*_*_*_CC_FLAGS = /wd4244 /wd4054
# Oniguruma: previous local declaration
MSFT:*_*_*_CC_FLAGS = /wd4456
# Oniguruma: signed and unsigned mismatch/cast
MSFT:*_*_*_CC_FLAGS = /wd4018 /wd4245 /wd4389
# Oniguruma: tag_end in parse_callout_of_name
GCC:*_*_*_CC_FLAGS = -Wno-error=maybe-uninitialized
# Oniguruma: implicit conversion from 'UINTN' (aka 'unsigned long long') to 'long'
GCC:*_CLANG9_*_CC_FLAGS = -Wno-error=constant-conversion
# Not add -Wno-error=maybe-uninitialized option for XCODE
# XCODE doesn't know this option
XCODE:*_*_*_CC_FLAGS =
源文件,inf工程文件完成,想要编译运行这个模块,需要将inf工程文件添加到dsc的[Components]部分,然后就可以使用build工具编译
模块应用程序是如何被编译成.efi文件的:
1、.c源码文件先被编译成目标文件.obj
2、连接器将目标文件*.obj和其他的库连接成*.dll
3、GenFw工具将*.dll转化成*.efi
这些过程都由build命令自动完成
1、将efi文件加载到内存
efi模块文件————gBS ->LoadImage()将efi文件加载到内存中生成image对象————gBS->StartImage(Image)启动Image对象
gBS 指向启动服务表 global Boot Servers
gST 指向系统表 global System Table
gRT 指向运行时服务表 global Running Time
gImageHandle 指向正在执行的驱动或应用程序
EFI_STATUS
LoadDriver(
IN CONST CHAR16 *FileName,
IN CONST BOOLEAN Connect
)
{
EFI_HANDLE LoadedDriverHandle;
EFI_STATUS Status;
EFI_DEVICE_PATH_PROTOCOL *FilePath;
EFI_LOADED_IMAGE_PROTOCOL *LoadedDriverImage;
LoadedDriverImage = NULL;
FilePath = NULL;
LoadedDriverHandle = NULL;
Status = EFI_SUCCESS;
*************************************
//
// Use LoadImage to get it into memory
//
Status = gBS->LoadImage(
FALSE,
gImageHandle,
FilePath,
NULL,
0,
&LoadedDriverHandle);
**********************************************
//
// Make sure it is a driver image
//
Status = gBS->HandleProtocol (LoadedDriverHandle, &gEfiLoadedImageProtocolGuid, (VOID *) &LoadedDriverImage);
*************************************************
/*
* 找出可执行的程序镜像Image的入口函数并执行找到的入口函数, gBS->StartImage是个函数指针,实际指向CoreStartImage函数
*/
Status = gBS->StartImage(LoadedDriverHandle, NULL, NULL);
if (EFI_ERROR(Status)) {
ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_LOAD_ERROR), gShellLevel2HiiHandle, FileName, Status);
} else {
ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_LOAD_LOADED), gShellLevel2HiiHandle, FileName, LoadedDriverImage->ImageBase, Status);
}
}
**********************************************************
return (Status);
}
2、进入镜像image的入口函数
CoreStartImage的主要作用是调用镜像的入口函数,在这里面SetIump/LongJumps是为应用程序提供了一种错误处理机制。gBS->StartImage的核心是EntryPoint函数,它就是这个应用程序的入口函数,就是_ModuleEntryPoint函数,进入到这里,代码执行的控制权才转交给我们的应用程序efi
EFI_STATUS
EFIAPI
CoreStartImage (
IN EFI_HANDLE ImageHandle,
OUT UINTN *ExitDataSize,
OUT CHAR16 **ExitData OPTIONAL
)
{
EFI_STATUS Status;
LOADED_IMAGE_PRIVATE_DATA *Image;
LOADED_IMAGE_PRIVATE_DATA *LastImage;
UINT64 HandleDatabaseKey;
UINTN SetJumpFlag;
EFI_HANDLE Handle;
**************************************************************
//
// Set long jump for Exit() support
// JumpContext must be aligned on a CPU specific boundary.
// Overallocate the buffer and force the required alignment
//
Image->JumpBuffer = AllocatePool (sizeof (BASE_LIBRARY_JUMP_BUFFER) + BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);
if (Image->JumpBuffer == NULL) {
//
// Image may be unloaded after return with failure,
// then ImageHandle may be invalid, so use NULL handle to record perf log.
//
PERF_START_IMAGE_END (NULL);
//
// Pop the current start image context
//
mCurrentImage = LastImage;
return EFI_OUT_OF_RESOURCES;
}
Image->JumpContext = ALIGN_POINTER (Image->JumpBuffer, BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);
SetJumpFlag = SetJump (Image->JumpContext);
//
// The initial call to SetJump() must always return 0.
// Subsequent calls to LongJump() cause a non-zero value to be returned by SetJump().
//
if (SetJumpFlag == 0) {
RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));
//
// Call the image's entry point 核心
//
Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
******************************************************************
//
// UEFI Specification - StartImage() - EFI 1.10 Extension
// To maintain compatibility with UEFI drivers that are written to the EFI
// 1.02 Specification, StartImage() must monitor the handle database before
// and after each image is started. If any handles are created or modified
// when an image is started, then EFI_BOOT_SERVICES.ConnectController() must
// be called with the Recursive parameter set to TRUE for each of the newly
// created or modified handles before StartImage() returns.
//
**********************************************************************
//
// Save the Status because Image will get destroyed if it is unloaded.
//
Status = Image->Status;
**************************************************************************
//
// Done
//
PERF_START_IMAGE_END (Handle);
return Status;
}
_ModuleEntryPoint函数:
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
if (_gPeimRevision != 0) {
//
// Make sure that the PEI spec revision of the platform is >= PEI spec revision of the driver
//
ASSERT ((*PeiServices)->Hdr.Revision >= _gPeimRevision);
}
//
// Call constructor for all libraries
//
ProcessLibraryConstructorList (FileHandle, PeiServices);
//
// Call the driver entry point
//
return ProcessModuleEntryPointList (FileHandle, PeiServices);
}
3、进入模块的入口函数
在_ModuleEntryPoint中,调用了ProcessModuleEntryPointList ,然后在build过程中,会解析inf文件,生成AutoGen.c文件,里面查看ProcessModuleEntryPointList ,它调用了应用程序工程模块的真正入口函数,就是inf文件中指定的那个入口函数
EFI_STATUS
EFIAPI
ProcessModuleEntryPointList (
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
return PcdPeimInit (FileHandle, PeiServices);
}
标准应用程序工程模块入口函数调用过程:
efi——LoadImage——StartImage——_ModuleEntryPoint——ProcessModuleEntryPointList ——inf中指定的入口函数
参考:
戴正华 《UEFI原理与编程》
github:https://github.com/tianocore/