UEFI原理与编程(二):UEFI工程模块文件-标准应用程序工程模块

UEFI 工程模块文件-标准应用程序工程模块

前言

  在EDK2环境下编程之前,先介绍EDK2的两个概念模块(Module)和包(Package).
  “包”是一组模块及平台描述文件(.dsc文件)、包声明文件(.dec文件)则、组成的集合,多在以*pkg命名的文件夹中,一般也称这样的文件夹为一个包。
  模块是UEFI系统的一个特色。模块(可执行文件,即.efi文件)像插件一样可以动态地加载到UEFI内核中。对应到源文件,EDK2中的每个工程模块由元数据文件(.inf)和源文件(有些情况也可以包含.efi文件)组成。
主要介绍3种应用程序模块、UEFI驱动模块和库模块。

一、标准应用程序工程模块

  标准引用程序工程模块是其它应用程序模块的基础,也是UEFI中常见的一种应用程序模块。每个工程模块由两部分组成:工程文件和源文件。源文件包括C/C++文件、.asm汇编文件,也可以包括.uni(字符串资源文件)和.vrf(窗体资源文件)等资源文件。

1.源文件

示例程序:

//hello.c
#include<Uefi.h>
EFI_STATUS UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
    SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Hello man,\n welcome to UEFI world\n");
    return EFI_SUCCESS;
}
  • 头文件:所有的标准应用程序工程模块的源文件的头文件都要包含Uefi.h。Uefi 定义了UEFI基本数据类型和核心数据结构。
  • 入口函数:UEFI标准应用程序的入口函数通常是UefiMain,它是约定成俗的函数,它是可以在.inf文件中指定。它的函数签名(返回值类型和参数列表类型)是不能变化的。
    • 入口函数的返回值类型是EFI_STATUS
      • 在UEFI程序中基本所有的返回值类型都是EFI_STATUS。它的本质是无符号长整数
      • 最高位为1时其值为错误代码,为0时表示非错误值。通过宏EFI_ERROR(Status)可以判断返回值Status时候为错误代码。若Status为错误代码EFI_ERROR(Status)返回值为真,否则为假。
      • EFI_SUCCESS为预定义常量,其值为0,表示没有错误的状态值和返回值。
  • 入口函数参数 ImageHandleSystenTable

    • .efi文件(UEFI应用程序或UEFI驱动程序)加载到内存后生成的对象成为Image(映像)。ImageHandleImage的句柄,作为模块入口函数的参数,它表示模块自身加载到内存后生成的Image对象 。
    • SystemTable是程序同UEFI内核交互的桥梁,通过它可以获得UEFI提供的各种服务(BT/RT),SystemTable是UEFI内核的一个全局结构体。

    向标准输出设备打印字符串是通过SystemTableConOut提供的OutputString服务完成的。ConOutEFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的一个实例,而EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL主要功能是控制字符输出设备。OutputString服务的第一个参数是指向EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的一个实例即:ConOut。第二个参数是Unicode字符串。简单讲就是SystemTable->ConOut->OutputString服务将字符串L”Hello World”打印到SystemTable->ConOut所控制的字符串输出设备。

2.工程文件

  工程模块分为很多块,每个块以“[快名]”开头,它必须单独占一行。有些块是所有工程文件都必需的块,这些块包括[Defines]、[Sources]、[Packages]和[LibraryClass]。
详细的工程模块如下表:
这里写图片描述

  • [Defines]块
    [Defines]块用于定义模块的属性和其它变量,块内定义的变量可以被其它块引用。

    • 属性定义的语法
      属性名 = 属性值
    • 块内必须属性
      • INF_VERSION:INF标准版本号。EDK2的build会检查INF_VERSION 的值并根据这个值解释.inf文件。设置为0x00010006或0x00010005。
      • BASE_NAME:模块名字符串,不能包含空格。它通常也是输出文件的名字。
      • FILE_GUID:每个工程文件必须有一个 8-4-4-4-12格式的GUID用于生成固件。(每一位数十六进制 0-F)
      • VERSION_STRING:模块的版本号,一般设置为1.0,根据自己写的模块版本设定即可。
      • MODULE_TYPE:定义模块的模块类型,对于标准应用模块,设为UEFI_APPLICATION.
      • ENTRY_POINT:定义模块的入口函数,根据在源文件中的入口函数填写。一般是UefiMain。
[Defines]
INF_VERSION     = 0x00010006
BASE_NAME       = HelloWorld
FILE_GUID       = 4ea97c46-7491-4dfd-b442-747010f3ce5f
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 1.0
ENTRY_POINT     = UefiMain
  • [Sources]块
    用于列出模块的所有源文件和资源文件。
    • 语法
      块内每一行表示一个文件,文件使用相对工程文件的路径。
    • 体系结构相关块
      可以在使用Sources.$(Arch),其中$(Arch)是表示本块的体系结构,可以是IA32, X64, IPF, EBC, ARM中一个。这个的作用是不同的体系结构可能包含的源文件或资源文件不同,如果都写进[Sources]可能有问题,但是可以列出对应的[Sources.$(Arch)],然后根据编译时标识设置,[Sources]都会被编译,[Sources.$(Arch)]中和标识相符的才会被编译。
    • 编译工具链相关的源文件
      有是文件后跟工具链的符号表示只有在该工具链编译器编译时有效。
      • MSFT : Visual Stdio
      • INTEL : ICC编译器
      • RVCT : ARM编译器
//体系结构相关块示例
[Sources]
Common.c
[Sources.IA32]
Cpu32
[Sources.X64]
Cpu64
//编译工具链相关的源文件示例
[Sources]
TimerWin.c | MSFT
TimerLinux.c | GCC
  • [Packages]块
    [Packages]列出本模块引用到的所有包的声明(.dec)文件。
    • 语法
      [Packages]块内每一行列出一个文件,文件使用相对于EDK2根目录的路径。若[Sources]列出了源文件,则[Packages]块必须列出MdePkg/MdePkg.dec,并将其放在本块首行。
  • [LibraryClasses]
    [LibraryClasses]块列出本模块要连接的库模块。
    • 语法
      块内每一行声明一个要连接的库(库的定义在.dsc文件中)
    • 常用库
      应用程序工程模块必须连接UefiApplicationEntryPoint库,驱动模块必须连接UefiDriverEntryPoint库。

非必须块(如果有用到,则需要写出)

  • [Protocols]块
    [Protocol]列出的模块中使用的Protocol,实际上是Protocol对应的GUID,如果未使用则为空。
  • [BuildOptions]块
    • 语法
      [BuildOptions]
      [编译器家族]:[$(Target)][TOOL_CHAIN_TAG][$(Arch)]_[CC|DLINK]_FLAGS[=|==]选项
      • 编译器家族:MSFT、INTEL、GCC、RVCT。
      • Target:DEBUG、RELEASE、*(对前两个都有效)。
      • TOOL_CHAIN_TAG编译器名字,定义在Conf\tools_def.txt文件中,与定义编译器名字:VS2003,VS2005,VS2008,VS2010,GCC44,GCC45,GCC46,CYGGCC,ICC等 ,*表示对指定家族的编译器都有效。
      • Arch是体系结构,与前述相同,可以是IA32, X64, IPF, EBC, ARM中一个,* 对所有体系结构有效。
      • CC表示编译选项,DLINK表示连接选项
      • =表示选项附加到默认选项后面,==表示仅使用所定义的选项,弃用默认选项
      • =,==后面接选项

注:
这是个很有用的选项,我们写正常C程序时一些无关紧要的警告在EDK2编译模块文件时会将它是做错误。所以可以使用下面的[BuildOptions]可以避免将这些警告堪称错误。

[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /w

下面是HelloWorld的.inf文件

// HelloWorld.inf
[Defines]
INF_VERSION     = 0x00010006
BASE_NAME       = HelloWorld
FILE_GUID       = 4ea97c46-7491-4dfd-b442-747010f3ce5f
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 1.0
ENTRY_POINT     = UefiMain

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /w

三、编译运行

1. 添加工程文件

将工程文件即HelloWorld.inf 添加到NT32Pkg.dsc的[Components]部分。

2. 加载EDK2环境

打开MS-DOS(cmd),进入到EDK2的根目录,用edksetup.bat加载EDK2环境。

3. 输入

build -p Nt32Pkg\Nt32Pkg.dsc -m [helloworld.inf相对于EDK2根目录的相对路径名] -a IA32 (64位用X64)

4. build 信息

build 成功后画面如下:
这里写图片描述
输出内容里也会指明文件输出的路径。

5. 运行

在MS-DOS输入 build run 进入UEFI模拟环境,运行前面目录下的HelloWorld.efi文件。
注:可以输入fs0: 快速进入/EDK2/Build/NT32IA32/DEBUG_VS2008/IA32/

6. 运行结果

这里写图片描述

四、标准应用程序加载过程

编译过程:
  1. HelloWorld.c 首先被编译成目标文件 HelloWorld.obj
  2. 连接器将目标文件HelloWorld.c 和其它库连接成HelloWorld.dll。
  3. GenFw 工具将HelloWorld.dll 转化成 HelloWorld.efi。
上述过程由 build 命令自动完成,连接器在生成HelloWorld.dll时使用了/dll/entry:_ModuleEntryPoint。.efi是遵循了PE32格式的二进制文件,_ModuleEntryPoint便是这个二进制文件的入口函数。下面探讨应用程序加载过程,主要看_ModuleEntryPoint和源文件中入口函数UefiMain的关系。

1. 将HelloWorld.efi 文件加载到内存

  当shell中执行HelloWorld.efi时,shell首先用gBS->LoadImage()将HelloWorld.efi文件加载到内存生成Image对象,然后调用gBS->StartImag(Image)启动这个Image对象。gBS->StartImage()是一个函数指针,它实际指向的是CoreStartImage()

2. 进入映像入口函数

  CoreStartImage()的主要作用是调用映像入口函数,在gBS->StartImage 的核心是Image->EntryPoint(···),它就是程序映像的入口函数,对应程序来说就是_ModuleEntryPoint 函数。进入 _ModuleEntryPoint 后,控制权才转交给应用程序(HelloWorld.efi)。
  _ModuleEntryPoint主要处理三件事:
  1. 初始化:初始化函数ProcessLibraryConstructorList中调用一系列构造函数
  2. 调用本模块的入口函数 : ProcessModuleEntryPointList 中调用的是工程模块定义的入口函数
  3. 析构:ProcessLibraryDestructorList 中调用一系列析构函数。
这三个对应的函数AutoGen.h,AutoGen.c中。

3. 进入模块入口函数

  在ProcessModuleEntryPointList函数中调用了工程模块的真正入口函数UefiMain。
  

五、总结

  标准应用程序模块是其它应用程序模块的基础,需要对它熟悉使用掌握,后续会接着介绍其它类型的工程模块。
  另外,此篇文章后的demo是在已经安装了EDK2环境基础上编译运行的。如果还没有安装,可以参考:UEFI原理与编程(一):环境搭建

参考资料

<1>《UEFI原理与编程》戴正华 著
<2> UEFI Spec2_6
<3> 百度百科

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值