# 模块
- 包:是一组
模块
及平台描述文件
(.dsc
文件),包声明文件 (.dec
文件) 组成的集合。 模块
(可执行
文件,即.efi
文件) 像插件一样
可以动态地
加载到UEFI内核
中。- 每个成功模块由
元数据文件
(.inf
) 和源文件
(有些情况包含.efi
文件) 组成。.inf
类似与Linux
下得Makefile
,.dsc
文件则相当于 VS 项目中的.sln文件
;模块相当于 VS 项目中的工程,.inf
文件则相当于 VS 工程中的.proj文件
。
# UEFI
主要模块
# DSC
文件
DSC
文件描述了模块
,库和组件如何编译,其中还包含很多的节
(section
)标志,包含必要的[Defines]
,[Components]
, 和可选的[LibraryClasses]
,[Libraries]
,[SkuIds]
,[BuildOptions]
,[PCD]
,[UserExtensions]
,[DefaultStores]
。所有节标志,都内置与中括号中,且大小写敏感
。在同一个括号中,可以包含复数的节标志字符串,他们之间使用逗号
隔开,注释使用#
# 使用逗号隔开 | |
[Library.X64,LibraryClasses.IPF] | |
# 其制定CPU架构的节,通常的节(如[LibraryClasses]有更高的优先级优先使用高优先级编辑) | |
# 在DSC文件中,使用!include来包含其他的文件,!include可以在任何节中出现 | |
[components] | |
...... | |
!include StdLib/StdLib.inc #包含StdLib库 |
# [Defines]
此节定义
各种变量
,以供后续编译
使用,必须在DEC中第一个
定义,通过
DEFINE
定义的宏
都是全局的
,都可以使用$(MACRO)
来访问语法如下
[Defines]
中可配置PCD
信息,PCD
:Platform Configuration Database
, 数据库,类似window的注册表
。PCD 除了SEC早期
,PEI阶段
,DXE早期阶段外
,都可以访问。
[Defines] | |
Name = Value | |
DEFINE MACRO = Value |
# 示例
# 定义的全局宏, | |
[Defines] | |
PLATFORM_NAME = AppPkg | |
PLATFORM_GUID = 0458dade-8b6e-4e45-b773-1b27cbda3e06 | |
PLATFORM_VERSION = 0.01 | |
DSC_SPECIFICATION = 0x00010006 | |
OUTPUT_DIRECTORY = Build/AppPkg | |
SUPPORTED_ARCHITECTURES = IA32|IPF|X64 | |
BUILD_TARGETS = DEBUG|RELEASE | |
SKUID_IDENTIFIER = DEFAULT | |
# | |
# Debug output control | |
# | |
DEFINE DEBUG_ENABLE_OUTPUT = FALSE # Set to TRUE to enable debug output | |
DEFINE DEBUG_PRINT_ERROR_LEVEL = 0x80000040 # Flags to control amount of debug output | |
DEFINE DEBUG_PROPERTY_MASK = 0 | |
DEFINE UEFI_BOOK_DIR = uefi\book |
# [ LibraryClasses
]
用来
提供模块
所使用的库入口
,而且它允许将模块编译成库
,这些库可以被 [Components
] 中的模块使用
,当DSC
文件中的模块不需要使用库
时,这个节也可以不设置,可选的。
# 语法格式
$(Arch)
和$(MODULE_TYPE)
是可选的,节内的库对指定的结构
和模块
都有效。
[LibraryClasses.$(Arch).$(MODULE_TYPE),LiBraryClasses.$(Arch).$(MODULE_TYPE)] | |
LibraryName | Path/LibraryName.inf |
LibraryClasses
有六种
表示方法,按照模块优先搜
索的顺序,从高到低
<LibraryClasses> | |
[LibraryClasses.$(Arch).$(MODULE_TYPE),LibraryClasses.$(Arch).$(MoDULE_TYPE)] | |
[LibraryClasses.$(Arch).$(MODULE_TYPE)] | |
[LibraryClasses.common.$(MODULE_TYPE)] | |
[LibraryClasses.$(Arch)] | |
[LibraryClasses.common] |
MdeModulePkg/Universal/PCD/Pei/Pcd.inf { | |
<LibraryClasses> | |
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf | |
} | |
# 直接制定目前编译模块所需要的库,此方法优先级最高 |
[
Components
] 中的模块在寻找所需要的库时,将按照上面优先级依次
寻找
# [ Components
]
用来定义模块编译的
节
,通过制定模块的INF文件
所在的位置,Build
工具可以编译生成.efi
文件。
[Components.$(Arch)] | |
Path/and/Filename.inf | |
# 语法格式 | |
[Components.$(Arch)]{ | |
Path/and/Filename.inf{ | |
<LibraryClasses> # 嵌套节 | |
LibraryName | Path/LibraryName.inf | |
# 还可以嵌套<Defines>,<PCD*>和<BuildOptions> | |
} | |
} |
# [ BuildOptions
]
给出编译器和相关的
编译参数
,它会覆盖为编译模块准备的默认参数。如果是为了替换编译参数,则可以使用==
。如果是为了添加编译参数,则可以使用=
# 语法格式
[BuildOptions] | |
${FAMILY}:${TARGET}_${TAGNAME}_{ARCH}_${TOOLCODE}——FLAGS[= | == ] 编译参数 |
# 示例
编译参数定义宏
DISABLE_NEW_DEPRECATED_INTERFACES
, 作为源代码
的编译开关
。两个参数分别禁用优化
和启用框架指针
省略功能。
[BuildOptions] | |
*_*_*_CC_FLAGS = -D DISABLE_NEW_DEPRECATED_INTEREACES | |
MSFT:DEBUG_*_*_CC_FLAGS = /Od / Oy- |
# 语法格式
FAMILY
是指编译
时使用的编译器
。
# 标准引用程序工程模块
工程模块的基础,
每个工程模块至少需要分为
工程
文件 :.inf文件
源文件
:C/C++
文件,.asm
汇编文件,也包含.uni
(字符串资
源文件) 和.vfr
(窗口资源
文件)
# INF 文件
INF
是模块的工程文件
,描述了模块的属性,包含模块由那些代码组成
,提供
了什么
,依赖什么库
,支持什么CPU架构
等信息。对 ODM 厂商 (第三方开发者而言),可以针对自家设备发布二进制形式的模块
,不必提供源代码。一般来说,如果是提供
库的模块
,则其位于包的Library
子目录下,并且会针对不同的架构再创建子目录划分;如果是UEFI_APPLICATION
一般位于子目录Applications
下。
# [Defines]
如果编译的模块为
库模块
,则LIBRARY_CLASS
变量必须制定
,生成的库模块在指定运行哪些类型
的模块时使用
LIBRARY_CLASS = FOO | PEI_CORE PEIM | |
LIBRARY_CLASS = BAR | DXE_CORE DXE_DRIVER DXE_SMM_DRIVER | |
# 如果只想在UEFI应用中使用,则可以如下设置 | |
LIBRARY_CLASS = FOO | UEFI_APPLICATION |
# [Sources]
列出模块中所有的
源文件
和资源文件
,这些文件位于INF文件
所在的目录或者子目录。这个节可以针对不同的架构指定文件.
$(Arch)
可以是COMMON
,IA32
,X64
,IPF
,EBC
,ARM
或AARCH64
中任何一个
。如果需要对所有架构
适用,可以使用COMMON
或者不指定任何架构
。
[Sources.$(Arch)] | |
SourceCode.c |
同时可以
对源文件
制定编译的工具链
,即只有在使用指定的工具链时,此源文件才会被编译,目前常用的4种工具
分别是MSFT
(微软的Visual Studio编
译器),GCC
(GNU GCC编译
器),INTEL
(Intel C 编译器和ntel EFI
字节码编译器) 和RVCT
(ARM RealVIew
工具链)
[Sources.ARM] | |
GicV3/Arm/ArmGicV3.S | GCC | |
GicV3/Arm/ArmGicV3.asm | RVCT |
# [ BuildOptions
]
INF
中的与DSC文件
中的语法格式基本相同
,区别在与INF文件
只对本模块有效
。而DSC
对所有模块
都有效。日常开发中,通过 INF 文件修改编译选项以解决一些
特别问题
。比如在解决汉字字符串
显示问题时,强制要求编译器把源文件按UTF-8编码
进行识别
[BuildOptions] | |
MSFT:*_*_*_CC_FLAGS = /utf-8 |
# [Protocols]
列出
模块
使用的协议
,在INF文件
中列出的是协议的GUID
,通过EDK2
的分析工具,GUID
被输出
到模块的AUtoGen.c
,如果模块没有使用任何协议
,则这个节为空
[Protocols.$(Arch)]#COMMON,IA32,X64,IPF,EBC或者不指定 | |
gEfiProtocolGuid [ | FeatureFlagExpression ] | |
# 当FeatureFlagExpression为true时所添加的Protocol Guid是有效的,当为FALSE是,会忽略Protocol Guid |
# [ LibraryClasses
]
列出
本模块
需要链接的库
[LibraryClasses.$(Arch)] #COMMON,IA32,X64,IPF,EBC或者不指定
LibraryClassNamel [ | FeatureFlagExpression ]
在日常开发中,模块如果要
添加
库,一般需要进行两个步骤,
- 在
INF
文件下的[LibraryClasses]
中添加库名
- 在
DSC
文件的[LibraryClasses]
中寻找词库
,如果有,则需要添加编译此库
的INF文件
[LibraryClasses] | |
UefiApplicationEntryPoint | |
UefiLib |
# [Packages]
列出
本模块
引用的所有包DEC文件
[Packages.$(Arch)] # COMMON,IA32,X64,IPF,EBC或者不指定,针对不同的平台架构 | |
MdePkg/MdePkg.dec |
EDC文件
的目录使用的是相对路径
,其根位
置($(WORKSPACE
)(通过edksetup.bat/edksetup.sh
指定的工作目录
))。DEC文件
的指定是有顺序的
,比如MdePkg/MdePkg.dec
必须在MdeModulePkg/MdeModulePkg.dec
之前
.
[Packages] | |
MdePkg/MdePkg.dec | |
MdeModulePkg/MdeModulePkg.dec | |
[Package.IA32] | |
DEFINE CPUS = IA32FamilyCpuPkg | |
$(CPUS)/DualCore/DualCore.dec |
# DEC 文件
每个
包
只有一个DEC
文件,DEC文件
用来配合DSC文
件,描述了包
的公开数据
和接口
# [Defines]
必须的节
,用来提供包的GUID
,版本
和名称等信息
与DSC相同
# [Includes]
列出本包提供的
头文件
所在目录
,此节DSC文件
中没有对应语法
结构
[Includes.$(Arch)] # COMMON,IA32,X64,IPF,EBC或者不指定,可添加Private | |
Path |
头文件的路径是
相对路径
,其根目录为DEC所在的目录
指定框架的时候,可以添加
Private
限定符,用来规定
所包含的头文件
只能在本包
中的模块中
使用
[Include.common] | |
[Include.common.Private] | |
[Include.IA32.Private] |
不带Private标志
的项,严禁
与带Private标志
的项结合同一文件目录
,不能
同时指定带Private标志
的项和不带Private标志
的项
# [ LibraryClasses
]
对外提供
的库
中都会提供头文件
,这些头文件位于包下的 ``Include\Library目
录下,用来明确库 和头文件
的对应关系
[LibraryClasses.$(Arch)] # COMMON,IA32,X64,IPF,EBC或者不指定 | |
LibraryClassesName | PAth/LibraryHeader.h |
# [ Guids
]
这个
节
用于定义Guid变量
,对于Private标志
的要求[Includes]
相同
[Guids.$(Arch)] # COMMON,IA32,X64,IPF,EBC或者不指定,可添加Private | |
GUIDName = GUID |
# 举例
## Include/Guid/GlobalVariable.h | |
gEfiGlobalVariableGuid = { 0x8BE4DF61, 0x93CA, 0x11D2, { 0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C }} | |
## Include/Guid/PcAnsi.h | |
gEfiVT100PlusGuid = { 0x7BAEC70B, 0x57E0, 0x4C76, { 0x8E, 0x87, 0x2F, 0x9E, 0x28, 0x08, 0x83, 0x43 }} |
# [Protocols]
定义 Protocol
的 GUID
,其规则与 [Guids]
是一样的。
[Protocols.(Arch)] # common,IA32,X64,IPF,EBC或者不指定,可添加Private | |
ProtocolName = GUID |
# 其余节
PPI
是PI
阶段PEIM和PEIM之间
的沟通桥梁
,类似Dxe阶段
的Protocol
。一个PEIM
中Instal
l 一个PPI
后,另一个PEIM
通过Locate
获取该PPI
。
[Ppis]
: 用于源文件
用到的PPI
, 语法与[Guids]
类似[PCD]
:是对DSC
文件[PCD]
的补充[UserExtensions]
:可以定制用户
的命令
# FDF 文件
Flash Description File
用于描述固件
在Flash
中的布局
和位置
,这些固件
是与UEFI/PI
兼容的二进制镜像
。一般生成固件
的源码只有一个FDF文件
,其作用是规定把那些包编入Flash
中,并确定编入的位置
FDF文件
用于生成Option ROM镜像
,固件镜像
和可启动镜像
,它与DSC文件
和二进制文件
配合,在GenFW工具
的协助下生成镜像
。
# [Defines]
可选的节,用来个
跟踪FDF
文件的版本,定义全局宏
以及设定PCD
的值
[Defines] | |
Name=Value | |
DEFINE MACRO = Value |
其中
set语句
专门用来对PCD变量
进行赋值的
# [FD]
FD
(Firmware Device) 即固件设备
,一个BIOS ROM
就是一个FD
, 这个节在开发平台 Flash 时是必须得,开发Option ROM
时不需要此项。FD 由各类声明和 FD 区域布局组成,它可以构成一个完整的Flash设备
镜像。Flash 设备可以是移动式可启动镜像
(比如可启动的 U 盘),系统Flash镜像
(如 BIOS ROM) 或更新镜像
(UEFI 中称为 Capsule 镜像),用来升级系统Flash
# 令牌声明 (TOKEN Statements) :
Token = Value [| PcdName] |
BaseAddress
: FD 的基址,设备开机后BIOS
被加载到系统中
的位置Size
:FD的大小
,单位为字节
BlockSize
:Flash
中一个Block
的大小ErasePolarity
: 表示用1或者0
擦除 Flash, 一般为1
NumBlocks
: Flash 中 Block 的个数
# 定义声明
DEFINE Statements
: 用来定义宏的声明
,所定义的宏
在整个FDF
文件中都有效,使用的时候,可以通过$(MACRO)
来引用
DEFINE MACRO = PATH |
# 设置声明
设置
声明
用来设置PCD变量
的值语法格式
SET PcdName = VALUE |
SET gUefiCpuPkgTokenSpaceGuid.PcdSevEsWorkAreaBase = $(MEMFD_BASE_ADDRESS) + gUefiOvmfPkgTokenSpaceGuid.PcdOvmfWorkAreaBase + gUefiOvmfPkgTokenSpaceGuid.PcdOvmfConfidentialComputingWorkAreaHeader | |
SET gUefiCpuPkgTokenSpaceGuid.PcdSevEsWorkAreaSize = gUefiOvmfPkgTokenSpaceGuid.PcdOvmfWorkAreaSize - gUefiOvmfPkgTokenSpaceGuid.PcdOvmfConfidentialComputingWorkAreaHeader |
除
类声明
外,[FD]
的另外一个组成部分是区域布局
(Region Layout
),它用来指明各种区域类型
的数据在FD中
的布局
offset|Size | |
[TokenSpaceGuidCName.PcdOffsetCname | TokenSpaceGuidCName.PcdSizeCName] ? | |
[RegopmType] ? |
上诉代码的含义为
FD开辟
一段空间,用来放置区域类型 (RegionType
) 所指明的内容,其中offset
和Size
表示其后的内容处于FD
中的偏移
和内容的大小
,RegionType
可以是FV
(Firmware Volume
,固件区块
),DATA
(数据),FILE
(文件),INF
(INF 文件) 和CAPSULE
(更新固件的镜像) 也可以不指定
0x000000|0x006000 | |
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfSecPageTablesBase|gUefiOvmfPkgTokenSpaceGuid.PcdOvmfSecPageTablesSize | |
0x006000|0x001000 | |
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfLockBoxStorageBase|gUefiOvmfPkgTokenSpaceGuid.PcdOvmfLockBoxStorageSize |
# [ FV
]
FV
(Firmware Volume
) 是固件
的逻辑区块
,相当于FD上的分区
,此节定义
了镜像包含的组件
和模块
,对于平台镜像是必要
的,但Option ROM镜像不需要
此节
[FV.UiFvName] |
# [ OptionRom
]
此节
用来编译
独立的Legacy PCI Option ROM
或者UEFI PCI Option ROM
.
# 资源文件
IDF
和UNI
文件VFR文件
可算做资源文件
,用于描述图像
,文字
和框架
等资源
,在Build工具
的协助
下,生成二进制
文件 (如.efi
文件,.lib
文件)
IDF
(Image Description File
,图像描述
文件),UNI
(Unicode String File
,宽字节字符串
文件) 和VFR
(Visual Forms Representation
,可视化窗体
描述) 都是资源文件
,同属于用户接口组件
。
IDF
文件用来描述图像资源
,UNI文件
用来描述字符串资源
,VFR
文件用来描述窗体资源
,类似与 Windows 操作系统下的窗口。
# IDF 文件
在
IDF文件
中,可通过#image标识符
指定图像文件
,所给出的资源文件
一般与IDF文
件在同一个目录下。使用
TRANSPARENT
用来指定是否使用透明
显示。
#image IMG_LOGO TRANSPARENT Logo.bmp | |
#image IMG_FULL_LOGO Logo.jpg | |
#image IMG_OEM_LOGO Logo.png |
# UNI 文件
同一个字符串
变量,可针对不同的语言
定义不同的内容
#langdef en-US "English" | |
#langdef zh-Hans "简体中文" | |
#string STR_LANGUAGE_SELECT #language en-US "Select Language" | |
#language zh-Hans "选择语言" |
在 UNI 文件中,可以使用的标识符有
#langdef
,#string
,#language
。
#langdef
用于声明本字符串
资源文件所支持的语言#string
用于定义字符串#language
用于标注所用的语言
#langdef
和#language
标识符在使用时,需要指定语言代码 (Language Code
),en-US
代表的是英文
# VFR 文件
VFR文件
用来描述窗体
的框架文件
,它可以使用#define
和#include
来定义变量
和包含的头文件
formset
(窗体集合):用来标志
整个窗体的结构
form
:窗体,标志整个窗体的结构checkbox
:复选框
,可通过空格键
或者回车键
进行选择
- 关键字
formset
和endformset
成对出现,他们之间所包含的内容定义
了整个formset
。guid
: 标志本formset
的GUID值
title
: 在界面中标志本formset
的字符串标题help
: 在界面上显示本formset
的帮助信息classguid
: 本formset
所挂载界面
的GUID值
varstore
: 变量所用数据结构
类型form
:窗体
关键字,与endform成对
出现,定义窗体的结构
checkbox
:复选框关键字
,与endcheckbox
成对出现,定义复选框
供用户使用
。
///** @file | |
// | |
// File Explorer Formset | |
// | |
// Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR> | |
// SPDX-License-Identifier: BSD-2-Clause-Patent | |
// | |
//**/ | |
#include "FormGuid.h" | |
formset | |
guid = EFI_FILE_EXPLORE_FORMSET_GUID, | |
title = STRING_TOKEN(STR_FILE_EXPLORER_TITLE), | |
help = STRING_TOKEN(STR_NULL_STRING), | |
classguid = EFI_FILE_EXPLORE_FORMSET_GUID, | |
form formid = FORM_FILE_EXPLORER_ID, | |
title = STRING_TOKEN(STR_FILE_EXPLORER_TITLE); | |
label FORM_FILE_EXPLORER_ID; | |
label LABEL_END; | |
endform; | |
form formid = FORM_ADD_NEW_FILE_ID, | |
title = STRING_TOKEN(STR_ADD_NEW_FILE_TITLE); | |
string | |
prompt = STRING_TOKEN(STR_NEW_FILE_NAME_PROMPT), | |
help = STRING_TOKEN(STR_NEW_FILE_NAME_HELP), | |
flags = INTERACTIVE, | |
key = NEW_FILE_NAME_ID, | |
minsize = 2, | |
maxsize = 20, | |
endstring; | |
subtitle text = STRING_TOKEN(STR_NULL_STRING); | |
text | |
help = STRING_TOKEN(STR_CREATE_FILE_AND_EXIT), | |
text = STRING_TOKEN(STR_CREATE_FILE_AND_EXIT), | |
flags = INTERACTIVE, | |
key = KEY_VALUE_CREATE_FILE_AND_EXIT; | |
text | |
help = STRING_TOKEN(STR_NO_CREATE_FILE_AND_EXIT), | |
text = STRING_TOKEN(STR_NO_CREATE_FILE_AND_EXIT), | |
flags = INTERACTIVE, | |
key = KEY_VALUE_NO_CREATE_FILE_AND_EXIT; | |
endform; | |
form formid = FORM_ADD_NEW_FOLDER_ID, | |
title = STRING_TOKEN(STR_ADD_NEW_FOLDER_TITLE); | |
string | |
prompt = STRING_TOKEN(STR_NEW_FOLDER_NAME_PROMPT), | |
help = STRING_TOKEN(STR_NEW_FOLDER_NAME_HELP), | |
flags = INTERACTIVE, | |
key = NEW_FOLDER_NAME_ID, | |
minsize = 2, | |
maxsize = 20, | |
endstring; | |
subtitle text = STRING_TOKEN(STR_NULL_STRING); | |
text | |
help = STRING_TOKEN(STR_CREATE_FOLDER_AND_EXIT), | |
text = STRING_TOKEN(STR_CREATE_FOLDER_AND_EXIT), | |
flags = INTERACTIVE, | |
key = KEY_VALUE_CREATE_FOLDER_AND_EXIT; | |
text | |
help = STRING_TOKEN(STR_NO_CREATE_FOLDER_AND_EXIT), | |
text = STRING_TOKEN(STR_NO_CREATE_FOLDER_AND_EXIT), | |
flags = INTERACTIVE, | |
key = KEY_VALUE_NO_CREATE_FOLDER_AND_EXIT; | |
endform; | |
endformset; |
# EDK2 目录介绍
AppPkg
:UEFI Application Development Kit
是一系列用来开发UEFI APP
开发的套件,标准依赖库
,工具以及demo
,目标是降低UEFI app
的开发框架ArmPkg
: 提供ARM框架
相关的 Protocols, 属于ARM平台上
的通用代码ArmPlatformPkg
:ARM开发板
相关的UEFI代码
,包含ARM平台上通用
的一些组件
,重复利用
这些组件会令 ARM 平台不用的版型之间的移植变得更加容易BaseTools
: 提供了编译EDK2的相关工具
:AutoGen,Build,GenSec,GenFV,GenFW,GenRds 工具。Conf
:CryptoPkg
:UEFI
定义了HLOS(high level OS)
和平台固件之间
的接口,多个安全特性也再去其中,用来提供加密支持EmbeddedPkg
: 为memory mapped controllers
提供protocol实现
,是一个简单的 EFI shell(EBL)EmulatorPkg
:Emulator虚拟环境
,用来替代 Nt32Pkg 和 UnixPkg,k 可以跨平台编译FatPkg
:FAT支持包
MdeModulePkg
: 此包提供符合UEEI/PI
工业标准的模版,也提供标准相关的开发环境,PPIs/PROTOCOLs/GUIDS
和依赖库
MdePkg
: 全程Module Development Environment Package
, 此为特殊的Package
,包含了用于开发module
所需要的最小环境。一个module可
能也会依赖于其他的Package
,但是所有 modules 必须依赖于MdePkg
.NetworkPkg
: 提供网络支持的包
,比如:IPV6 网络协议栈 / IPsec 驱动 / ISCSI 驱动 / 网络配置先关的shell app
OvmfPkg
:OVM
F 是用来给虚拟机提供UEFI支持的包
,可以使用QEMU
和KVM
来引导OVMF固件
,并进一步引导HLOS
PcAtChipsetPkg
: 此包提供了符合PcAt标准器件
的接口和实现StdLib
: 提供了标准库UDK
实现,StdLibPrivateInternalFiles
包时用来给StdLib
使用的,不能用作其他引用UefiCpuPkg
: 提供兼容UEFI
的CPU模版
和库
# 参考资料
- UEFI 原理与编程 - 第三章节内容整理
- 《UEFI 编程实战》