UEFI开发探索40 – 构建自己的Package

前段时间在Linux下开发UEFI程序,发现以前写的AppPkg的32位程序没法编译,无法在模拟环境下测试执行程序。

我当时就想脱离AppPkg,自己构建Package。当然,StdLib的库不能使用了,也不能以main()函数为入口。我觉得这都不是什么大事,毕竟平常构建的Option ROM代码也不能使用这些。

说干就干,顺便把各种类型文件的知识点过一遍。

1 编译框架

EDKII的编译系统是基于Python和C的代码构建的,可以跨平台编译。也可运行在不同的CPU架构上,比如X86和ARM,最近我们在做针对龙芯MIPS的软件,乐见UEFI发展版图的扩大。

图1 EDKII 工作流程

图1中给出了EDKII的工作流程。总结来说,编译工具将分析元数据文件(DSC, DEC和INF等),生成一个顶层的Makefile,以及针对各个Module的Makefile和AutoGen.c/Autogen.h。

在Autogen文件中,EDKII的编译工具将生成Moudule所需要的guids、protocols、ppis、PCDs等。最后将编译成所指定的二进制文件,包括efi、acpi、aml等。

仔细观察平常编译的过程,不同的平台使用了不同的编译器(比如Windows下编译是使用VS Stduio,Linux使用GCC),而最终生成UEFI环境下可运行的执行文件,是使用了GenFw工具(生成镜像文件使用GenFV)。

大部分工具都位于/BaseTools目录下,它是我们在搭建环境时生成的。当然,也可以直接使用编译好了的工具,比如目前在龙芯下的开发,厂商不提供源码,只能使用编译好的工具使用。

在平常的开发中,最常生成的是UEFI Driver和UEFI Application。Option ROM本质上还是一个UEFI Driver,编译的时候做一些调整就可以了,具体的过程在之前的博客中已经描述过。

以上就是基本的编译框架,让我们大致知道,文件是怎么编译来的。下面将对各个元数据文件的细节进行了解。

2 .dsc文件

.dsc文件用来编译一个Package,包含了[Defines]、[LibraryClasses]、[Components]几个必要部分,以及[PCD]、[BuildOptions]、[SkuIds]等可选部分。

补充一下,EDKII的元数据文件基本都是采用EBNF的格式(扩展巴科斯范式),具体的描述可以参考dsc规格文件《edk-ii-dsc-specification.pdf》。

对我的用处是,在使用Vs code时可指定dsc和dec文件为BNF规范的,高亮显示的代码比较好读:

图2 高亮显示dsc文件

[Defines]

用于定义变量,在编译的步骤中可以使用,Package中其他模块(Modules)可以引用。它必须是.dsc文件的第一部分,每节的格式为 Name = Value。

另外,还有两种格式:DEFINE MACRO = Value和EDK_GLOBAL MACRO = Value。这两种方式定义的宏,可通过$(MACRO)的引用方式在.dsc和.fdf文件中使用。

此块中必须定义的宏变量包括:DSC_SPECIFICATION、PLATFORM_GUID、PLATFORM_VERSION、PLATFORM_NAME、SKUID_IDENTIFIER、SUPPORTED_ARCHITECTURES和BUILD_TARGETS。其他为可选宏变量,具体的可以参考.dsc的规格文件。

[LibraryClasses]

定义了库的名字以及库.inf文件的路径,这些库可以被[Components]块内的模块引用(如果所写的模块需要新的库,就在此处定义)。

它主要用来给Modules提供库的接口,如果dsc文件没有EDKII Modules,则是一个可选的块。当然,我们的目标就是编译Modules,所以还是认为是必选项。

常用的格式有:[LibraryClasses]、[LibraryClasses.IA32]、[LibraryClasses.X64]、[LibraryClasses.IPF]、[LibraryClasses.EBC]、[LibraryClasses.common]。跟在其后的是库的入口:

LibraryClassName|Path/To/LibInstanceName.inf
LibraryClassName1|Path/To/LibInstanceName1.inf

LibraryClassName不能为NULL,它是库名的关键字,必须是唯一的。所有INF文件在链接其他库和Moudles时,会使用此库的入口。

通用的语法如下:

[LibararyClasses.$(Arch).$(MODULE_TYPE)]
LibraryName | path/LibraryName.inf

另外还可以使用结构

[LibararyClasses.$(Arch1).$(MODULE_TYPE1),LibararyClasses.$(Arch1).$(MODULE_TYPE1)]
LibraryName | path/LibraryName.inf

$Arch和$MODULE_TYPE是可选项。不使用表示通用。

$Arch表示体系结构,可以是下列值之一:IA32,X64,IPF,EBC,ARM,common。common表示对所有体系结构有效。

$MODULE_TYPE表示模块的类别,块内列出的库只能提供$(MODULE_TYPE)类别的模块连接。它可以是下列值:SEC、PEI_CORE、PEIM、DEX_CORE、DEX_SAL_DRIVER、BASE、DXE_SMM_DRIVER、DXE_DRIVER、\ DXE_RUNTIME_DRIVER、UEFI_DRIVER、UEFI_APPLICATION、USER_DEFINED。

[Components]

一个或者多个[Componets]段包含了EDKII Moudles的列表,一般常用的格式包括:[Components]、[Components.IA32]、[Components.X64]、[Components.IPF]、[Components.EBC]、[Components.common]。

指定inf文件的路径一般采用这种格式: /Path/and/Filename.inf。或者可以采用嵌套的方式,

Path\Exectuables.inf{
   < LibraryClasses> # 做嵌套
     LibraryName | Path/LibraryName.inf
   < BuildOptions> # 嵌套块
     #字块中还可以包含< Pcds*>
}

注意,Path使用相对于EDK2根目录的相对路径。

[BuildOption]

对Modules来说,如果不想使用工具链中指定的编译参数,则可以在此指定自己独有的编译参数。

其格式为:

[BuildOptions.$(Arch).$(CodeBase)]
            [编译器]:[$(Target)]_[Tool]_[$(Arch)]_[CC|DLINK]_FLAGS=

编译器宏名为FAMILY,定义于Conf/tools_def.txt中,可以是MSFT, INTEL或GCC等。其他的参数可以参考dsc规格文件,这里不详细讨论了。

之前AppPkg在Linux下无法进行IA32的编译,就是因为在此块中定义了只能在微软编译器下编译的文件。

3 .dec文件

.dec文件支持EDKII Module的编译,它用来定义各Modules间共享的信息。有八种类型的块可以定义在.dec文件中,分别是Defines, Includes, LibraryClasses, Guids, Protocols, Ppis, PCD 和UserExtensions。

也就是说,.dec文件是各种模块(Modules)共用数据和接口的集合地。

[Defines]

这是必须要存在的块。典型的例子如下:

[Defines]
DEC_SPECIFICATION = 0x0001001B
PACKAGE_NAME = MdePkg
PACKAGE_GUID = 1E73767F-8F52-4603-AEB4-F29B510B6766
PACKAGE_VERSION = 1.02
PACKAGE_UNI_FILE = MdePkg.uni

PACKAGE_UNI_FILE用来指定存储Unicode字符串的文件名,个人觉得主要是方便用来支持多语言的。

[Includes]

这是一个可选块。列出本Package提供的头文件所在的目录,可针对不同的架构来指定。

[LibraryClasses]

这是一个可选块。Package可以通过.dec文件对外提供库,每个库都必须有一个头文件,放在Include\Library目录下。本区块用于明确库和头文件的对应关系。格式:

[LibraryClasses.$(Arch)]
LibraryName | Path/LibraryHeader.h

[Guids]

可选块,用来定义Guid C名的Guid值。此块中的标识名Private,用来防止本Package外的模块访问。

常用的定义名:[Guids]、[Guids.common]、[Guids.common.Private]、[Guids.IA32]、[Guids.IA32.Private]、[Guids.X64]、[Guids.X64.Private]、[Guids.IPF]、[Guids.IPF.Private]、[Guids.EBC]、[Guids.EBC.Private]。这些定义名也可以组合,比如:[Guids.IA32, Guids.X64]。

[Protocols]

可选块。它类似于Guids块,常用的定义名如下:[Protocols]、[Protocols.common]、[Protocols.common.Private]、[Protocols.IA32]、[Protocols.IA32.Private]、[Protocols.X64]、[Protocols.X64.Private]、[Protocols.IPF]、[Protocols.IPF.Private]、[Protocols.EBC]、[Protocols.EBC.Private]。

这些定义名可以组合,比如[Protocols.IA32, Protocols.X64]。

一般的格式如下:

[Protocol.$(Arch)]
ProtocolCName = {C Format Guid Value} # Comment

其他的块就不具体列出了,可以参考dec文件的规格书。

4 构建Package

其他文件的内容,与构建Package关系不大,比如.fdf文件、.uni文件等,可以去寻找相应的规格文件。基本上都能在此网页下找到:

https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Specifications

构建此Package的目的,是希望代码能在Linux下和Windows都能编译,不挂在其他Package下(有时间把ftol2.obj改为Linux下也能编译的汇编版本,那么main()函数也能使用了),方便调试。

我是以AppPkg的.dsc和.dec文件为蓝本,修改为我需要的文件。

要修改的地方不多,主要是:

1) .dsc文件中的PLATFORM_NAME,其值改为RobinPkg;
2) .dsc文件中的PLATFORM_GUID,其值随便修改几位,保证唯一即可;(当然,也可以用微软的工具,生成一个)
3) .dsc文件的OUTPUT_DIRECTORY改为Build/RobinPkg;
4) .dsc文件注掉最后StdLib和Sockets;
5) .dec文件改掉Name和GUID;
6) .dec文件中除去[Defines]之外的块全部注掉;

具体修改好的Package见百度云,修改好之后进行编译即可。构建好的Package是这样的:

图3 构建好的RobinPkg

我是直接把上一篇博客中,在Ubuntu16.04下编译通过的两个框架例子拿来做实验的。发现一个小问题,在Linux下以UTF-8存储的文件,在Windows下编译不通过。

怀疑是中文字符引起的,我的代码中有不少的中文注释。将提示错误的文件改为以GB 2312存储,编译通过了。在几个平台和编辑工具下换来换去,不知道哪个字符引起了编译器的不满,之后还是尽量用英文注释吧。

(第一次发表地址: http://yiiyee.cn/blog/author/luobing/ )

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 25 RobinPkg 下

104 total views, 2 views today

发表评论

电子邮件地址不会被公开。 必填项已用*标注