UEFI原理与编程第⼆章学习-UEFI标准应⽤⼯程模块⽂件介绍及编译流程
标准应⽤程序⼯程模块
  标准应⽤程序⼯程模块是其他应⽤程序⼯程模块的基础,也是UEFI中常见的⼀种应⽤程序⼯程模块。每个⼯程模块分为两部分:⼯程⽂件和源⽂件,标准应⽤程序⼯程模块也不例外。其中,源⽂件包括:C/C++⽂件、.asm汇编⽂件,也可以包括.uni(字符串资源⽂件)和 .vfr(窗⼝资源⽂件)等资源⽂件。
  ⼀个简单的标准应⽤程序⼯程模块应该包含⼀个C程序模块(本例中为Main.c)以及⼀个⼯程⽂件(Main.inf),接下来逐部分编写⼀个简单的标准应⽤程序的各个模块。
1、⼊⼝函数
//简单的标准应⽤程序
#include<Uefi,h>
EFI_STATUS UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"HelloWorld\n");
return EFI_SUCCESS;
}
⼀般来说标准应⽤程序⾄少要包含以下两个部分:
1. 头⽂件 :所有的UEFI程序都要包含头⽂件Uefi.h。Uefi.h定义了UEFI基本数据类型以及核⼼数据结构。
2. ⼊⼝⽂件 :UEFI标准应⽤程序⼊⼝函数可以由开发者⾃⾏指定,但通常命名为UefiMain。⼊⼝函数由⼯程⽂件
UefiMain.inf指定。虽然⼊⼝函数的函数名可以变化,但其函数签名(即返回值类型和参数列表类型)不能变化。
⼊⼝函数返回值与参数讲解:
1. ⼊⼝函数的返回值类型是EFI_STATUS。
1. 在UEFI程序中基本所有返回值类型都是EFI_STATUS。EFI_STATUS本质上是⽆符号程序长整数型变量。
2. 在最⾼位为1时值为错误代码,为0时表⽰没有错误的状态值或返回值。通过宏EFI_ERROR(Status)可以判
断返回值Status是否为错误码。若Status是错误吗,则EFI_ERROR的返回值为TRUE,否则为False。
3. EFI_SUCCESS为预定义常量,其值为0,表⽰没有错误的状态或是返回值。
2. ⼊⼝函数的参数是ImageHandle和SystemTable。
1. .efi⽂件(UEFI应⽤程序或UEFI驱动程序)加载到内存后⽣成的对象称为Image映像。ImageHandle是
Image对象的句柄,作为模块⼊⼝函数,它表⽰模块⾃⾝加载到内存后⽣产的Image对象。
2. SystemTable是程序同UEFI内核交互的桥梁,程序通过SystemTable可以获得UEFI提供的各种服务,如启
动(BS)服务和运⾏时(RT)服务。SystemTable时UEFI内核中的⼀个全局结构体。
  向标准输出设备打印字符串是通过SystemTable的ConOut提供的服务完成的。ConOut是
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的⼀个实例。⽽EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的主要功能是控
制字符输出设备。向输出设备打印字符串是通过ConOut提供的OutputString服务完成的。该服务(函数)第⼀个参数是
This指针,指向EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL实列(此处为ConOut)本⾝;第⼆个参数是Unicode字符
串。这条打印语句的流程是通过SystemTable->ConOut->OutputString五福将字符串L"HelloWorld"打印到
SystemTable->ConOut所控制的字符输出设备。
2、⼯程⽂件
  在编写Main.c⽂件的同时,需要编写.inf(Module Information File)⽂件。.inf⽂件时模块的⼯程⽂件,其作⽤类似于Visual Studio的.proj⽂件,⽤于指导EDK2编译⼯具⾃动编译模块。
  ⼯程⽂件分为很多个块,每个块以“[块名]”开头,“[块名]”必须单独占⼀⾏。有的块是所有⼯程⽂件都必需的块,还有另⼀部分的块仅在⽤的时候需要编写。
必须块块描述
[Defines]定义本模块的属性变量及其他变量,这些变量可在其他块中引⽤
[Sources]列出本模块的所有源⽂件及资源⽂件
[Packages]列出本模块引⽤到的所有包的包声明⽂件
[LibraryClasses]列出本模块要链接的库模块
⾮必须块块描述
[Protocols]列出本模块⽤到的Protocol
[Guids]列出本模块⽤到的GUID
[Pcd]列出本模块⽤到的Pcd变量,这些Pcd变量可被整个UEFI系统访问
[PcdEx]列出本模块⽤到的Pcd变量,这些Pcd变量可被整个UEFI系统访问
[FixedPcd]列出本模块⽤到的Pcd编译期常量
system的头文件[FeaturePcd]⽤于列出本模块⽤到的Pcd常量
[PatchPcd]写出的Pcd变量进本模块可以使⽤
[BuildOptions]指定编译和链接选项
1. [Defines] 块:
[Defines]块⽤于定义模块的属性和其他变量,块内定义的变量可被其他块引⽤。
(1)属性定义的语法
属性名=属性值
(2)属性
块内必须定义的属性包括:
1. INF_VERSION: INF标准的版本号。EDK2的build会检查INF_VERSION的值,并根据这个值解释.inf⽂件。通常将
INF_VERSION的值设置为0x00010005,其中前半部分为主版本号,后半部分为次版本号。
2. BASE_NAME:模块名字符串,不能包含空格。它通常也是输出⽂件的名字。例如,BASE_NAME=UefiMain,则最
终⽣成的⽂件为UefiMain.efi。
3. FILE_GUID:每个⼯程⽂件必须有⼀个8-4-4-4-12格式的GUID,⽤于⽣产固件。例如,FILE_GUID=6987936E-
GUID-UEFI-AE86-012345678987。
4. VERSION_STRING:模块的版本号字符串。例如,可以设置为 VERSION_STRING=1.0。
5. MODULE_TYPE:定义模板的模板类型,可以是SEC、PEI_CORE、PEIM、DXE_CORE、DEX_SAL_DRIVER、
UEFI_APPLICATION、BASE中的⼀个。对标准应⽤程序⼯程模块来说,MODULE_TYPE的值为
UEFI_APPLICATION。
6. ENTRY_POINT:定义模块的⼊⼝函数。模块的⼊⼝函数就是通过设置ENTRY_POINT的值进⾏配置的。
[Defines]块⽰例代码:
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = UefiMain
FILE_GUID = 6987936E-GUID-UEFI-AE86-012345678987
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
2. [Sources] 块
2. [Sources] 块
(1)语法
块内每⼀⾏代表⼀个⽂件,⽂件使⽤相对路径,根路径是⼯程⽂件所在的⽬录。这次编写的简单标准应⽤程序⼯程模块仅含有⼀个源⽂件。 [Sources]块如下所⽰:
[Sources.$(Arch)]
UefiMain.c
(2)体系结构相关块
.$(Arch)是可选项,可以是IA32、X64、IPF、EBC、ARM中的⼀个,表⽰本模块适⽤的体系结构。[Sources]块适⽤于任何体系结构。例如,编译32位模块时(即,在build命令选项中选择使⽤ -a IA32选项),⼯程将包含[Sources]和[Sources.IA32]中的源⽂件,编译64位模块时,⼯程将包含[Sources]和[Sources.X64]中的源⽂件。
(3)⽰例
⽰例包含了三个块:[Sources]块、[Sources.IA32]块、[Sources.X64]块。在编译32位的模块时,模块包含
[Sources.IA32]中的Cpu32.c⽂件和[Sources]中的Common.c⽂件;在编译64位系统时,模块包含⽂件[Sources.X64]中的Cpu64.c和[Sources]中的Common.c。
[Sources]块、[Sources.IA32]块、[Sources.X64]块⽰例:
[Sources]
common.c
[Sources.IA32]
Cpu32.c
[Sources.X64]
Cpu64.c
(4)编译⼯具链相关的源⽂件
有时⽂件名后⾯会有⼀个“|”符号,该符号后⾯会跟⼯具链名字,如下例所⽰:
⼯具链相关的源⽂件:
[Sources]
TimerWin,c | MSFT
TimerLinux.c | GCC
这表⽰TimerWin.c仅在使⽤ Visual Studio 编译器时有效,TimerLinux.c仅在使⽤GCC编译器时有效。处理MSFT和GCC 外,EDK2还定义了INTEL和RVCT两种⼯具链。INTEL⼯具链是ICC编译器或Intel EFI 字节码编译器;RVCT是ARM编译器。
3. [Packages] 块
[Packages]块列出了本模块引⽤到的所有包的包声明⽂件(类似C++开头的import),即.dec⽂件。
(1)[Packages]语法
[packages]块内每⼀⾏列出⼀个⽂件,⽂件使⽤相对路径,相对路径的根路径位EDK2的根⽬录。如果[Sources]块内列出了源⽂件,则在[Packages]块内必须列出MdePkg/MdePkg。.dec,并将其放在本块的⾸⾏(详见⽰例)。
(2)⽰例
由于编写的简单标准应⽤⼯程模块⽰例( 块)中, UefiMain.c仅引⽤了MdePkg中的头⽂件Uefi.h,因此[Packages]仅列出MdePkg/MdePkg.dec即可,⽰例如下:
[Packages]
MdePkg/MdePkg.dec
4. [LibraryClasses] 块
[LibraryClasses]块的功能是列出本模块要链接的库模块。
(1)语法
块内每⼀⾏声明⼀个要链接的库(库定义在包的 .dsc⽂件中),语法如下:
[LibraryClasses]
库名称
(2)常⽤库
应⽤程序⼯程模块必须链接UefiApplicationEntryPoint库;驱动模块必须链接UefiDriverEntryPoint库。
(3)⽰例
这次尝试编写的简单应⽤程序⼯程模块中,UefiMain.c⽂件的UefiMain函数没有调⽤除UefiApplicationEntryPoint以外的其他库函数,因此只需要在[LibraryClasses]块中列出UefiApplicationEntryPoint即可,⽰例如下:
[LibraryClasses]
UefiApplicationEntryPoint
5. [Protocols]块
[Protocols]块中列出的是在模块中使⽤到的Protocol对应的GUID。如果模块中未使⽤任何Protocol,则[Protocols]块为空。
(1)语法
块内的每⼀⾏声明⼀个在本模块中引⽤的Protocol。语法格式如下:
[LibraryClasses]
Protocol 的 GUID
(2)⽰例
如果在程序中使⽤了某个Protocol的GUID。例如,源程序中使⽤了类似代码:
Status = gBS->LocateProtocol ( &gEfiHiiDatabaseProtocolGuid, NULL, (VOID**) &HiiDatabsa);
则在[Protocols]块中必须要列出gEfiHiiDatabaseProtocolGuid,⽰例如下:
[LibraryClasses]
gEfiHiiDatabaseProtocolGuid
6. [BuildOptions]块
[BuildOptions]块制定了本模块的编译和链接选项。
(1)语法
[BuildOptions]
[编译器家族]: [$(Target)]_[TOOL_CHAIN_TAG]_[$(Arch)]_[CC|DLINK]_FLAGS [=|==] [选项]
各选项说明如下:
1. [编译器家族]:可以是MSFT(Visual Studio编译器家族)、INTEL(Intel编译器家族)、GCC(GCC编译器家族)
和RVCT(ARM编译器家族)中的某⼀个。
2. [Target]:值可以是DEBUG、RELEASE和*中的任意⼀个,*为通配符,表⽰对DEBUG和RELEASE都有效。
3. [TOOL_CHAIN_TAG]:是编译器的名字。编译器的名字在⽂件中的第60⾏。预定的编译器名字可以
是:VS系列(VS2010、VS2015、VS2017等)、GCC系列(GCC45、GCC46等)、可以是CYGGCC或是ICC 等,还可以使⽤通配符*表⽰对所有编译家族内的所有编译器都适⽤。
4. [Arch]:是体系结构,可以IA32、X64、IPF、EBC、ARM或*,其中*表⽰对所有体系结构都有效。
5. [CC|DLINK]:CC表⽰编译选项。DLINK表⽰链接选项。
5. [CC|DLINK]:CC表⽰编译选项。DLINK表⽰链接选项。
6. [=|==]:=表⽰将选项附加到默认选项后;==表⽰仅使⽤所定义的选项,弃⽤默认选项。
7. [选项]:此项为编译选项或连接选项。
(2)⽰例
⽰例6.1 使⽤’='的[BuildOptions]块,编写⽤Visual Studio编译器进⾏编译,且在编译时添加/wd4804编译选项,连接时添加/BASE:0选项。
[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /wd4804
MSFT:*_*_*_DLINK_FLAGS = /BASE:0
⽰例6.2 使⽤"=="的[BuildOptions]块,编写是使⽤Visual Studio编译器进⾏编译32位的DEBUG版本仅使⽤指定的编译选项,并忽略所有默认的编译选项。
[BuildOptions]
MSTF:DEBUG_VS2010_IA32_CC_FLAGES == /nologo /c /WX /GS- /W4 /Gs32768 /DUNICODE /Olib2 /GL /EHs-c- /GR- /GY /Zi /Gm /D EF I_SPECIFCATION_VERSION = 0x0002000A /D TIANO_RELEASE_VERSION = 0x00080006 /FAs /Oi-
等上述代码块都编译好后,将其放在⼀起,组成完整的标准应⽤程序⼯程⽂件,⽰例如下:
⽰例,标准应⽤程序HelloWorld的完整⼯程⽂件
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = UefiMain
FILE_GUID = 6987936E-GUID-UEFI-AE86-012345678987
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UEFIMAIN
[Sources]
main.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
3、编译和运⾏
源⽂件和⼯程都编写完成后,将UefiMain.inf的相对(uefi)路径添加到Nt32Pkg.dsc或UnixPkg.dsc的[Components]部分,⽰例代码如下(uefi在EDK2⽂件夹中):
[Components]
uefi/book/infs/UefiMain.inf
添加完成后打开BaseTools下的build⼯具进⾏编译即可
Windows下执⾏下列命令进⾏编译:
C:\EDK2>edksetup.bat --nt32
C:\EDK2>build -p Nt32PkgNt32Pkg.dsc -a (IA32||X64)
Linux下执⾏下例命令进⾏编译:
$>source ./edksetup.sh BaseTools
$>source -p Unixpkg/UnixPkg.dsc -a IA32
※4、标准应⽤程序的加载过程

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。