UEFI——hello world
包(以pkg结尾的文件夹)即package为包。是由一组模块(.工程文件.inf和源文件.c)和一个平台描述文件(.dsc)和包声明文件(.dec)组成的集合。inf文件用于自动编译源代码。
模块的可执行文件(.efi)可以像插件一样动态加载到UEFI内核中,本文主要讲述uefi编程常用的几种模块。
标准应用程序模块(DXE阶段或者shell阶段运行的应用程程序)
#include <Uefi.h> //头文件。Uefi定义了UEFI基本数据类型和核心数据结构。
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;
}
1.入口函数UefiMain,作为入口函数名,可以在改变它的函数名,但是相应的也要在inf工程文件中改变入口函数的名字,而且他的函数签名(参数类型和返回值类型)不能改变。
-返回值类型 EFI_STATUS,本质为无符号长整数。
-最高位为1时其值为错误代码,为0时表示非错误值。通过宏EFI_ERROR(Status)可以判断返回值Status时候为错误代码。若Status为错误代码EFI_ERROR(Status)返回值为真,否则为假。
-EFI_SUCCESS为预定义常量,其值为0,表示没有错误的状态值和返回值。
2.入口函数参数 ImageHandle和SystenTable
efi文件(UEFI应用程序或UEFI驱动程序)加载到内存后生成的对象成为Image(映像)。
ImageHandle是 Image的句柄,作为模块入口函数的参数,它表示模块自身加载到内存后生成的Image对象 。
句柄(handle)作为对象的唯一标识,和对象唯一对应的,以此来寻找和操作对象。handle实现id的一些功能,但是权限比id少,代码暴露较少。
SystemTable是程序同UEFI内核交互的桥梁,通过它可以获得UEFI提供的各种服务(BT/RT),SystemTable是UEFI内核的一个全局结构体。
工程文件(.inf)
[Defines] //定义本模块的属性变量及其他变量,这些变量可以在工程文件其他块中引用
INF_VERSION = 0x00010005 //INF版本号
BASE_NAME = HelloWorld //模块名字
FILE_GUID = 6987936E-ED34-ffdb-AE97-1FA5E4ED2117 //GUID生产固件库
MODULE_TYPE = UEFI_APPLICATION //模块类型 此为标准应用程序模块
VERSION_STRING = 1.01 //模块版本号
ENTRY_POINT = UefiMain //入口函数 UfeiMain
[Sources] //所有源文件和资源文件
HelloWorld.c
// a.c | MSFT //VS编译器有效
// b.c | GCC //gcc编译器有效
// INTEL IC编译器有效
// RVCT arm编译器
//[Sources.IA32] //32位模块
// cpu32.c
//[Sources.X64] //64位模块
// cpu64.c
[Packages] //本模块所有引用到的包的包声明模块
MdePkg/MdePkg.dec //modelpackage.dec对应Uefi.h
MyPkg/MyPkg.dec
[LibraryClasses] //本模块要链接的库模板
UefiApplicationEntryPoint //应用工程模块
UefiLib
//UefiDriverEntryPoint //驱动工程模块
//[Protrcols] //列出使用的prorocol的GUID
//[BuildOptions] //本模块的编译和链接选项
程序流程:StartImage - ModuleEntryPoint - ProcessModuleEntryPointList-Main
ShellAppMain应用程序模块(shell环境运行的应用程序)
-
c文件
方便shell环境执行,以ShellAppMain作为函数入口,可以传参数,以全局变量gST操纵系统表。第一个参数是命令行个数,第二个是命令行列表。
ENTRY_POINT必须为ShellCEntryLib,入口函数不支持自定义,标准应用程序UefiMain支持自定义修改可以换成别的名字,源程序中要提供ShellAppMain。 -
inf文件
- [Defines]块
基本与标准应用程序工程模块相同,除了EntryPoint。在标准应用程序工程模块中,入口函数名,需要开发者自己定义,需要保持工程文件与源文件一致,一般设置为UefiMain。但是在Shell应用程序工程模块中,它必须设置为ShellAppMain。 - [Package]块
MdePkg/MdePkg.dec和ShellPkg/ShellPkg.dec。 - [LibraryClasses]块
必须列出ShellCEntryLib,通常还要列出 UefiBootServicesTableLib和UefiLib。
- [Defines]块
Shell应用程序工程模块是在标准应用程序模块的基础之上利用Protocol做了封装,使我们可以很好地编写带命令行参数的UEFI程序。
Shell应用程序的入口函数是ShellCEntryLib,它的入参和标准应用程序的入参是一样的,它会去打开EFI_SHELL_PAEAMETERS_PROTOCOL,通过这个protocol获得ShellAppMain的入参,就是首先获取命令行参数,然后再调用用户的入口函数ShellAppMain去打开Shell。
main应用程序工程模块(shell环境,且程序连接了StdLib库)
使用main函数的应用程序工程模块会使用StdLib,在StdLib中提供了ShellAppMain函数,我们自己实现main函数作为程序的入口函数供ShellAppMain去调用,真正的模块入口函数是ShellCEntryLib,调用过程是:ShellCEntryLib———>ShellAppMain——>main
使用main函数的应用程序工程模块要在AppPkg环境下才能编译成功,所以需要将main.inf添加到AppPkg.dsc文件的[Components]块下。
库模块(作为静态库被其他模块调用)
在开发uefi工程过程中,会使用到大量的库。库也有源文件、工程文件,其他模块要使用库,要将这个库添加到包的dsc声明文件中。
可以看到库的工程文件和其他应用程序有很大的区别。没有ENTRY_POINT;它会指定 MODULE_TYPE是BASE函数DRIVER等类型;通过LIBRARY_CLASS设定库名,指定库的适用范围,让它仅能被特定的模块调用
其他模块要使用这个库,就将库添加到自己的dsc文件中:
[LibraryClasses]
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
然后在自己的inf中添加这个库:
[LibraryClasses]
UefiLib
然后在源文件中就可以使用这个库了
库如果要在使用之前需要初始化,那要在库的工程文件中指定CONSTRUCTOR和DESTRUCTOR
驱动模块
驱动和应用程序的最大区别就是驱动会常驻内存,而应用程序在执行完毕之后就会从内存中清除。