PCD的全称是Platform Configuration Database,它是一个存放了UEFI下可访问数据的数据库。
它的特点是可以在UEFI存在的大部分时间内访问,在【UEFI实战】在库中使用全局变量中就曾经介绍过通过PCD来传递全局变量。这里说是大部分时间内,那是因为在诸如SEC阶段,以及PEI、DXE阶段的早期,某些PCD相关的模块还没有加载起来之前,这些类型的PCD还是不能访问的。
TokenSpaceGuidCName.PcdCName
TokenSpaceGuidCName是一个GUID,PcdCName是一个变量名,两者合起来构成了唯一的PCD变量。
PCD的类型
FeatureFlag PCD:它最终返回的是一个TRUE或者FALSE,用于判断条件中;FeatureFlag PCD跟FixedAtBuild是一样的,它相当于类型是BOOELAN的FixedAtBuild PC
PatchableInModule PCD:这种变量的值可以在编译的时候确定,这个不算特别,特别的是它可以在编译完成的二进制文件上通过工具来修改值;
FixedAtBuild PCD:静态值,在编译的时候确定,整个UEFI阶段不可变;
Dynamic PCD:前面的三种类型可以认为是静态的PCD,而这里以及之后的是动态的PCD;它的特点是可以在UEFI运行的过程中通过Set宏来修改值;在《edk-ii-build-specification.pdf》中有说明该种类型的PCD必须在DSC中在列一遍,但是实际使用似乎并不是必须的;
DynamicEx PCD:跟Dynamic PCD类似,算是加强版,使用宏PcdGetEx/PcdSetEx来访问变量;
需要注意的是上面的类型并不是在一个SPEC中定义的,前面的4中是满足EDKII规范,而最后一个满足的是PI规范,这个对使用的影响还不是很确定。
实现说明
下面说明各种PCD的实现。
FixedAtBuild PCD
不同的PCD类型实现方式有差异,这里首先介绍最简单的FixedAtBuild PCD。.然后在一个模块中使用
3. 编译后查看该模块编译目录中的AutoGen.c文件,可以看到如下的内容
这里的_PCD_VALUE_PcdTestVarx的值在AutoGen.h中声明:
也就是说,对应FixedAtBuild PCD来说,它就是在编译的时候通过宏的方式生成的。
也因此它是固定不变的一个值。
Dynamic PCD
下面介绍Dynamic PCD,首先看一个例子:
dec定义,
inf添加
C函数调用
Log打印:
1)AutoGen.c中没有了PCD变量的定义;
2)PCD访问方式变了,Dynamic对应的是函数;
3)Set函数出现了;
AutoGen.c
AutoGen.h
也就是说这里要依赖于Protocol(DXE阶段是Protocol,而PEI阶段是PPI)来完成操作。
这Protocol在Pcd.info模块中安装。
Pcd.inf模块分为PEI和DXE两个版本,分别放在PEI阶段和DXE阶段的最前面,只有整个模块初始化完成之后,才能够开始正常使用PCD宏来访问Dynamic PCD变量。
以DXE阶段的Pcd.inf模块为例,它主要做了两件事情:
1. 初始化该阶段使用的PCD数据库;
2. 安装各种处理PCD需要的Protocol;
/ PCD protocol need to be installed before the module access Dynamic type PCD.
// But dynamic type PCD is not required in PI 1.2 specification.
v
///
/// This service abstracts the ability to set/get Platform Configuration Database (PCD).
///
typedef struct {
PCD_PROTOCOL_SET_SKU SetSku;
PCD_PROTOCOL_GET8 Get8;
PCD_PROTOCOL_GET16 Get16;
PCD_PROTOCOL_GET32 Get32;
PCD_PROTOCOL_GET64 Get64;
PCD_PROTOCOL_GET_POINTER GetPtr;
PCD_PROTOCOL_GET_BOOLEAN GetBool;
PCD_PROTOCOL_GET_SIZE GetSize;
PCD_PROTOCOL_GET_EX_8 Get8Ex;
PCD_PROTOCOL_GET_EX_16 Get16Ex;
PCD_PROTOCOL_GET_EX_32 Get32Ex;
PCD_PROTOCOL_GET_EX_64 Get64Ex;
PCD_PROTOCOL_GET_EX_POINTER GetPtrEx;
PCD_PROTOCOL_GET_EX_BOOLEAN GetBoolEx;
PCD_PROTOCOL_GET_EX_SIZE GetSizeEx;
PCD_PROTOCOL_SET8 Set8;
PCD_PROTOCOL_SET16 Set16;
PCD_PROTOCOL_SET32 Set32;
PCD_PROTOCOL_SET64 Set64;
PCD_PROTOCOL_SET_POINTER SetPtr;
PCD_PROTOCOL_SET_BOOLEAN SetBool;
PCD_PROTOCOL_SET_EX_8 Set8Ex;
PCD_PROTOCOL_SET_EX_16 Set16Ex;
PCD_PROTOCOL_SET_EX_32 Set32Ex;
PCD_PROTOCOL_SET_EX_64 Set64Ex;
PCD_PROTOCOL_SET_EX_POINTER SetPtrEx;
PCD_PROTOCOL_SET_EX_BOOLEAN SetBoolEx;
PCD_PROTOCOL_CALLBACK_ONSET CallbackOnSet;
PCD_PROTOCOL_CANCEL_CALLBACK CancelCallback;
PCD_PROTOCOL_GET_NEXT_TOKEN GetNextToken;
PCD_PROTOCOL_GET_NEXT_TOKENSPACE GetNextTokenSpace;
} PCD_PROTOCOL;
在上述GetX和SetX的函数实现中,最重要的是如下的函数:
GetWorker():
SetValueWorker
GetWorker()的作用就是在PCD数据库里面找到对应的PCD的指针,而SetWork()的作用就是找到指针然后赋值。
到这里为止,最需要了解的就是PCD数据库是如何建立的?
从BuildPcdDxeDataBase()这个函数中可以找到答案。
这里主要进行了PCD数据库的创建,它分为两个步骤:
1. 通过LocateExPcdBinary()从FFS中获取PCD已初始化变量;
2. 为未初始化的变量(默认全0)分配空间(这样做的目的应该是为了减少生成二进制的大小);
接下来需要关注的就是这个放在FFS中的PCD数据是从何而来的?
查看LocateExPcdBinary()函数里面的代码:
gPcdDataBaseSignatureGuid
在rom中查找Guid
3C7D193C-682C-4C14-A68F-552DEA4F437E
因此Dynamic PCD的数据是在编译的时候初始化并存放在BIOS二进制中的,然后在BIOS运行过程中会获取这些数据,并存放到内存中,后续就可以修改了。
DynamicEx类型的基本上一致,这里不再说明。
PatchableInModule PCD
最后剩下的是PatchableInModule PCD,下面是一个例子:
这里定义了PCD相关的两个变量,一个是值,注意它的类型修饰符是volatile,另一个是PCD的大小。
它的访问方式与FixedAtBuild PCD没有什么两样。
对于PatchableInModule PCD来说,唯一的差别就在于,变量是volatile的,但是这个volatile具体体现在哪里呢?我们如何对它进行Patch?
因为该PCD是可以通过外部工具来修改的,那么有一点是可以肯定的,即它是可见的,通过查看BIOS二进制就可以找到。
首先找FV_RECOVERY.fd(这个是最终生成的BIOS二进制)中寻找,但是没有找到,再在FAT.efi中寻找,发现了它的踪迹: 在Fat.map中
因此理论上可以通过工具来修改这个值(在生成最终的RECOVERY.fd之前)。
而实际上,在EDK的源代码中,提供了操作PatchableInModule PCD的工具
在Fat.inf模块中加入Pcd 并且Pcd会在C文件中使用
將Efi和map文件放到工具路径
因此可以利用这两个工具来修改PCD。
这里将这两个工具(以及依赖文件)放到下述的编译中间目录:
打开Fat.efi后0x13090, 0x13110找到对应PCD的值,和工具解析对应
然后通过PatchPcdValue.exe来修改PCD:
这里将PcdPatchableVar的值修改成了0x88888888,比较修改前后的值:
但是如果在编译的时候自动完成这个动作,或者在整个二进制生成之后修改PatchableInModule PCD呢?
目前这还是个疑问。
- }
- returnGetPcdProtocol()->Get32(TokenNumber);
- LibPcdGet32(
- IN UINTN TokenNumber
- )
- {