Variable的定义
变量代表内存中具有特定属性的一个存储单元,它用来存放数据,也就是变量的值,在程序运行期间,这些值是可以改变的,在UEFI架构下,Variable是一个由表示信息、属性、和值构成的组合体,类似全局变量。
"Variable" 通常指的是 EFI Variables,是一种用于存储持久性数据的机制。下面我将用生动的方式解释 EFI Variables:
想象一下你的计算机就像一个魔法书中的魔法屋,这个屋子里有很多魔法小盒子,每个小盒子都有一个名字。这些小盒子可以用来存储各种重要的信息,比如你的名字、出生日期、计算机的配置设置等等。
-
魔法小盒子:这些小盒子就是 EFI Variables,它们是一种用来存储数据的容器。每个变量都有一个唯一的名字,比如 "BootOrder" 或 "TimeAndDate"。
-
名字:每个小盒子都有一个名字,这个名字用来标识和查找特定的数据。就像你的名字可以用来找到你的个人信息一样。
-
内容:每个小盒子里可以存储一些数据,比如文本、数字、日期等等。这些数据可以是重要的系统配置信息,例如启动顺序或时间设置。
-
持久性:与普通的计算机内存不同,这些小盒子中的数据是持久性的,它们在计算机关闭后仍然保留。这意味着你可以在下次打开计算机时找到相同的数据。
-
魔法:在计算机开机时,它会读取这些小盒子中的数据,并根据它们的内容执行特定的操作,就像在魔法屋中使用魔法物品一样。
-
读写:你可以随时打开这些小盒子,读取里面的数据,也可以将新的数据放入其中。这允许你在计算机上存储和管理各种重要信息。
总之,EFI Variables 就像魔法小盒子,它们允许计算机存储和访问重要的持久性数据,这些数据可以影响计算机的行为和配置。它们对于UEFI固件和操作系统非常重要,用于存储系统配置、启动顺序、时间设置等等信息。
在uefi中所有的环境变量都用variable来表示,系统统一使用gRT->GetVariable和gRT->SetVariable来读和写一个Variable,注意这里gRT是uefi中的Runtime阶段的一个全局的表,表的关系为SystemTable->Runtime Services->EFI_SET_VARIABLE与EFI_GET_VARIABLE。
Variable相关函数
EFI_SET_VARIABLE
/**
Sets the value of a variable.
@param[in] VariableName A Null-terminated string that is the name of the vendor's variable.
Each VariableName is unique for each VendorGuid. VariableName must
contain 1 or more characters. If VariableName is an empty string,
then EFI_INVALID_PARAMETER is returned.
@param[in] VendorGuid A unique identifier for the vendor.
@param[in] Attributes Attributes bitmask to set for the variable.
@param[in] DataSize The size in bytes of the Data buffer. Unless the EFI_VARIABLE_APPEND_WRITE,
EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, or
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute is set, a size of zero
causes the variable to be deleted. When the EFI_VARIABLE_APPEND_WRITE attribute is
set, then a SetVariable() call with a DataSize of zero will not cause any change to
the variable value (the timestamp associated with the variable may be updated however
even if no new data value is provided,see the description of the
EFI_VARIABLE_AUTHENTICATION_2 descriptor below. In this case the DataSize will not
be zero since the EFI_VARIABLE_AUTHENTICATION_2 descriptor will be populated).
@param[in] Data The contents for the variable.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_SET_VARIABLE)(
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN UINT32 Attributes,
IN UINTN DataSize,
IN VOID *Data
);
EFI_GET_VARIABLE
/**
Returns the value of a variable.
@param[in] VariableName A Null-terminated string that is the name of the vendor's
variable.
@param[in] VendorGuid A unique identifier for the vendor.
@param[out] Attributes If not NULL, a pointer to the memory location to return the
attributes bitmask for the variable.
@param[in, out] DataSize On input, the size in bytes of the return Data buffer.
On output the size of data returned in Data.
@param[out] Data The buffer to return the contents of the variable.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_GET_VARIABLE)(
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
OUT UINT32 *Attributes, OPTIONAL
IN OUT UINTN *DataSize,
OUT VOID *Data
);
EFI_GET_NEXT_VARIABLE_NAME
/**
Enumerates the current variable names.
@param[in, out] VariableNameSize The size of the VariableName buffer.
@param[in, out] VariableName On input, supplies the last VariableName that was returned
by GetNextVariableName(). On output, returns the Nullterminated
string of the current variable.
@param[in, out] VendorGuid On input, supplies the last VendorGuid that was returned by
GetNextVariableName(). On output, returns the
VendorGuid of the current variable.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_GET_NEXT_VARIABLE_NAME)(
IN OUT UINTN *VariableNameSize,
IN OUT CHAR16 *VariableName,
IN OUT EFI_GUID *VendorGuid
);
EFI_QUERY_VARIABLE_INFO
/**
Returns information about the EFI variables.
@param[in] Attributes Attributes bitmask to specify the type of variables on
which to return information.
@param[out] MaximumVariableStorageSize On output the maximum size of the storage space
available for the EFI variables associated with the
attributes specified.
@param[out] RemainingVariableStorageSize Returns the remaining size of the storage space
available for the EFI variables associated with the
attributes specified.
@param[out] MaximumVariableSize Returns the maximum size of the individual EFI
variables associated with the attributes specified.
@retval EFI_SUCCESS Valid answer returned.
@retval EFI_INVALID_PARAMETER An invalid combination of attribute bits was supplied
@retval EFI_UNSUPPORTED The attribute is not supported on this platform, and the
MaximumVariableStorageSize,
RemainingVariableStorageSize, MaximumVariableSize
are undefined.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_QUERY_VARIABLE_INFO)(
IN UINT32 Attributes,
OUT UINT64 *MaximumVariableStorageSize,
OUT UINT64 *RemainingVariableStorageSize,
OUT UINT64 *MaximumVariableSize
);
Variable模块--VariableRuntimeDxe
根据UEFI提供的gRT->SetVariable与gRT->GetVariable服务,查找其在源码中是如何定义的,可以发现,最终定位MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf模块下,根据该模块的定义,可以找到模块的入口函数VariableServiceInitialize。
VariableServiceInitialize
根据VariableRuntimeDxe的inf文件找到其入口函数VariableServiceInitialize,根据函数注释可以看到,这个驱动入口函数主要做的就是将四个和Variable有关的服务放入系统表内,即Get、Set、GetNext、Query四个接口函数,并且为variable的读写服务安装了架构protocol,使得variable的读写服务可以被调用,最后注册了一个EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE事件。
根据函数内容可以看到,函数首先调用VariableCommonInitialize对存放non-volatile与volatile变量的区域进行初始化,随后便将VariableServiceGetVariable等接口函数放入SystemTable->RuntimeServices->GetVariable等系统表内,接着安装gEfiVariableArchProtocolGuid架构,以实现variable的读写服务,最后这个注册虚拟地址事件应该是为了供os使用。
/**
Variable Driver main entry point. The Variable driver places the 4 EFI
runtime services in the EFI System Table and installs arch protocols
for variable read and write services being available. It also registers
a notification function for an EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE event.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS Variable service successfully initialized.
**/
EFI_STATUS
EFIAPI
VariableServiceInitialize (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_EVENT ReadyToBootEvent;
EFI_EVENT EndOfDxeEvent;
Status = VariableCommonInitialize ();
ASSERT_EFI_ERROR (Status);
Status = gBS->InstallMultipleProtocolInterfaces (
&mHandle,
&gEdkiiVariableLockProtocolGuid,
&mVariableLock,
NULL
);
ASSERT_EFI_ERROR (Status);
Status = gBS->InstallMultipleProtocolInterfaces (
&mHandle,
&gEdkiiVarCheckProtocolGuid,
&mVarCheck,
NULL
);
ASSERT_EFI_ERROR (Status);
SystemTable->RuntimeServices->GetVariable = VariableServiceGetVariable;
SystemTable->RuntimeServices->GetNextVariableName = VariableServiceGetNextVariableName;
SystemTable->RuntimeServices->SetVariable = VariableServiceSetVariable;
SystemTable->RuntimeServices->QueryVariableInfo = VariableServiceQueryVariableInfo;
//
// Now install the Variable Runtime Architectural protocol on a new handle.
//
Status = gBS->InstallProtocolInterface (
&mHandle,
&gEfiVariableArchProtocolGuid,
EFI_NATIVE_INTERFACE,
NULL
);
ASSERT_EFI_ERROR (Status);
if (!PcdGetBool (PcdEmuVariableNvModeEnable)) {
//
// Register FtwNotificationEvent () notify function.
//
EfiCreateProtocolNotifyEvent (
&gEfiFaultTolerantWriteProtocolGuid,
TPL_CALLBACK,
FtwNotificationEvent,
(VOID *)SystemTable,
&mFtwRegistration
);
} else {
//
// Emulated non-volatile variable mode does not depend on FVB and FTW.
//
VariableWriteServiceInitializeDxe ();
}
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
VariableClassAddressChangeEvent,
NULL,
&gEfiEventVirtualAddressChangeGuid,
&mVirtualAddressChangeEvent
);
ASSERT_EFI_ERROR (Status);
//
// Register the event handling function to reclaim variable for OS usage.
//
Status = EfiCreateEventReadyToBootEx (
TPL_NOTIFY,
OnReadyToBoot,
NULL,
&ReadyToBootEvent
);
ASSERT_EFI_ERROR (Status);
//
// Register the event handling function to set the End Of DXE flag.
//
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
OnEndOfDxe,
NULL,
&gEfiEndOfDxeEventGroupGuid,
&EndOfDxeEvent
);
ASSERT_EFI_ERROR (Status);
// Register and initialize the VariablePolicy engine.
Status = InitVariablePolicyLib (VariableServiceGetVariable);
ASSERT_EFI_ERROR (Status);
Status = VarCheckRegisterSetVariableCheckHandler (ValidateSetVariable);
ASSERT_EFI_ERROR (Status);
Status = gBS->InstallMultipleProtocolInterfaces (
&mHandle,
&gEdkiiVariablePolicyProtocolGuid,
&mVariablePolicyProtocol,
NULL
);
ASSERT_EFI_ERROR (Status);
return EFI_SUCCESS;
}
Variable.c
根据之前提到的入口函数VariableServiceInitialize中对UEFI提供的四个接口函数的定义可以观察到,四个接口函数的具体实现其实是在Variable.c文件夹下的VariableServiceInitialize、VariableServiceInitialize、VariableServiceInitialize、VariableServiceInitialize。
其中着重观察Set与Get。
VariableServiceSetVariable
从函数源码来看,该函数有比较繁琐的检查机制,检查入口参数是否合规,检查是否有溢出情况,可以看到,直到调用加锁函数AcquireLockOnlyAtBootTime()之前都是在做检查,具体先不深究,随后便是调用FindVariable()函数去查找要设置的variable是否已经存在,具体要看FindVariable函数返回的CurrPtr值,紧接着便调用UpdateVariable进行创建新variable,该函数本身即是一些内存的拷贝,只不过需要做一系列的检查。
UpdateVariable中有创建新variable的操作,该操作主要分4步:
1、写Variable_Header
2、将Variable_Header->state设置为0x7f即有效
3、写variable数据
4、将Variable_Header->state设置为0x3f即已添加
操作主要通过UpdateVariableStore函数实现,该函数的主要作用就是向FWH写入数据,利用FVB的Write向指定地址写入数据,最后将内存更新到flash区域。
/**
This code sets variable in storage blocks (Volatile or Non-Volatile).
Caution: This function may receive untrusted input.
This function may be invoked in SMM mode, and datasize and data are external input.
This function will do basic validation, before parse the data.
This function will parse the authentication carefully to avoid security issues, like
buffer overflow, integer overflow.
This function will check attribute carefully to avoid authentication bypass.
@param VariableName Name of Variable to be found.
@param VendorGuid Variable vendor GUID.
@param Attributes Attribute value of the variable found
@param DataSize Size of Data found. If size is less than the
data, this value contains the required size.
@param Data Data pointer.
@return EFI_INVALID_PARAMETER Invalid parameter.
@return EFI_SUCCESS Set successfully.
@return EFI_OUT_OF_RESOURCES Resource not enough to set variable.
@return EFI_NOT_FOUND Not found.
@return EFI_WRITE_PROTECTED Variable is read-only.
**/
EFI_STATUS
EFIAPI
VariableServiceSetVariable (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN UINT32 Attributes,
IN UINTN DataSize,
IN VOID *Data
)
{
VARIABLE_POINTER_TRACK Variable;
EFI_STATUS Status;
VARIABLE_HEADER *NextVariable;
EFI_PHYSICAL_ADDRESS Point;
UINTN PayloadSize;
BOOLEAN AuthFormat;
AuthFormat = mVariableModuleGlobal->VariableGlobal.AuthFormat;
//
// Check input parameters.
//
if ((VariableName == NULL) || (VariableName[0] == 0) || (VendorGuid == NULL)) {
return EFI_INVALID_PARAMETER;
}
if ((DataSize != 0) && (Data == NULL)) {
return EFI_INVALID_PARAMETER;
}
//
// Check for reserverd bit in variable attribute.
// EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS is deprecated but we still allow
// the delete operation of common authenticated variable at user physical presence.
// So leave EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS attribute check to AuthVariableLib
//
if ((Attributes & (~(EFI_VARIABLE_ATTRIBUTES_MASK | EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS))) != 0) {
return EFI_INVALID_PARAMETER;
}
//
// Check if the combination of attribute bits is valid.
//
if ((Attributes & (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)) == EFI_VARIABLE_RUNTIME_ACCESS) {
//
// Make sure if runtime bit is set, boot service bit is set also.
//
if ((Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) != 0) {
return EFI_UNSUPPORTED;
} else {
return EFI_INVALID_PARAMETER;
}
} else if ((Attributes & EFI_VARIABLE_ATTRIBUTES_MASK) == EFI_VARIABLE_NON_VOLATILE) {
//
// Only EFI_VARIABLE_NON_VOLATILE attribute is invalid
//
if ((Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) != 0) {
return EFI_UNSUPPORTED;
} else {
return EFI_INVALID_PARAMETER;
}
} else if ((Attributes & VARIABLE_ATTRIBUTE_AT_AW) != 0) {
if (!mVariableModuleGlobal->VariableGlobal.AuthSupport) {
//
// Not support authenticated variable write.
//
return EFI_INVALID_PARAMETER;
}
} else if ((Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) != 0) {
if (PcdGet32 (PcdHwErrStorageSize) == 0) {
//
// Not support harware error record variable variable.
//
return EFI_INVALID_PARAMETER;
}
}
//
// EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS and EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute
// cannot be set both.
//
if ( ((Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) == EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
&& ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) == EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS))
{
return EFI_UNSUPPORTED;
}
if ((Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) == EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) {
//
// If DataSize == AUTHINFO_SIZE and then PayloadSize is 0.
// Maybe it's the delete operation of common authenticated variable at user physical presence.
//
if (DataSize != AUTHINFO_SIZE) {
return EFI_UNSUPPORTED;
}
PayloadSize = DataSize - AUTHINFO_SIZE;
} else if ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) == EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
//
// Sanity check for EFI_VARIABLE_AUTHENTICATION_2 descriptor.
//
if ((DataSize < OFFSET_OF_AUTHINFO2_CERT_DATA) ||
(((EFI_VARIABLE_AUTHENTICATION_2 *)Data)->AuthInfo.Hdr.dwLength > DataSize - (OFFSET_OF (EFI_VARIABLE_AUTHENTICATION_2, AuthInfo))) ||
(((EFI_VARIABLE_AUTHENTICATION_2 *)Data)->AuthInfo.Hdr.dwLength < OFFSET_OF (WIN_CERTIFICATE_UEFI_GUID, CertData)))
{
return EFI_SECURITY_VIOLATION;
}
//
// The VariableSpeculationBarrier() call here is to ensure the above sanity
// check for the EFI_VARIABLE_AUTHENTICATION_2 descriptor has been completed
// before the execution of subsequent codes.
//
VariableSpeculationBarrier ();
PayloadSize = DataSize - AUTHINFO2_SIZE (Data);
} else {
PayloadSize = DataSize;
}
if ((UINTN)(~0) - PayloadSize < StrSize (VariableName)) {
//
// Prevent whole variable size overflow
//
return EFI_INVALID_PARAMETER;
}
//
// The size of the VariableName, including the Unicode Null in bytes plus
// the DataSize is limited to maximum size of PcdGet32 (PcdMaxHardwareErrorVariableSize)
// bytes for HwErrRec#### variable.
//
if ((Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) == EFI_VARIABLE_HARDWARE_ERROR_RECORD) {
if (StrSize (VariableName) + PayloadSize >
PcdGet32 (PcdMaxHardwareErrorVariableSize) - GetVariableHeaderSize (AuthFormat))
{
return EFI_INVALID_PARAMETER;
}
} else {
//
// The size of the VariableName, including the Unicode Null in bytes plus
// the DataSize is limited to maximum size of Max(Auth|Volatile)VariableSize bytes.
//
if ((Attributes & VARIABLE_ATTRIBUTE_AT_AW) != 0) {
if (StrSize (VariableName) + PayloadSize >
mVariableModuleGlobal->MaxAuthVariableSize -
GetVariableHeaderSize (AuthFormat))
{
DEBUG ((
DEBUG_ERROR,
"%a: Failed to set variable '%s' with Guid %g\n",
__func__,
VariableName,
VendorGuid
));
DEBUG ((
DEBUG_ERROR,
"NameSize(0x%x) + PayloadSize(0x%x) > "
"MaxAuthVariableSize(0x%x) - HeaderSize(0x%x)\n",
StrSize (VariableName),
PayloadSize,
mVariableModuleGlobal->MaxAuthVariableSize,
GetVariableHeaderSize (AuthFormat)
));
return EFI_INVALID_PARAMETER;
}
} else if ((Attributes & EFI_VARIABLE_NON_VOLATILE) != 0) {
if (StrSize (VariableName) + PayloadSize >
mVariableModuleGlobal->MaxVariableSize - GetVariableHeaderSize (AuthFormat))
{
DEBUG ((
DEBUG_ERROR,
"%a: Failed to set variable '%s' with Guid %g\n",
__func__,
VariableName,
VendorGuid
));
DEBUG ((
DEBUG_ERROR,
"NameSize(0x%x) + PayloadSize(0x%x) > "
"MaxVariableSize(0x%x) - HeaderSize(0x%x)\n",
StrSize (VariableName),
PayloadSize,
mVariableModuleGlobal->MaxVariableSize,
GetVariableHeaderSize (AuthFormat)
));
return EFI_INVALID_PARAMETER;
}
} else {
if (StrSize (VariableName) + PayloadSize >
mVariableModuleGlobal->MaxVolatileVariableSize - GetVariableHeaderSize (AuthFormat))
{
DEBUG ((
DEBUG_ERROR,
"%a: Failed to set variable '%s' with Guid %g\n",
__func__,
VariableName,
VendorGuid
));
DEBUG ((
DEBUG_ERROR,
"NameSize(0x%x) + PayloadSize(0x%x) > "
"MaxVolatileVariableSize(0x%x) - HeaderSize(0x%x)\n",
StrSize (VariableName),
PayloadSize,
mVariableModuleGlobal->MaxVolatileVariableSize,
GetVariableHeaderSize (AuthFormat)
));
return EFI_INVALID_PARAMETER;
}
}
}
//
// Special Handling for MOR Lock variable.
//
Status = SetVariableCheckHandlerMor (VariableName, VendorGuid, Attributes, PayloadSize, (VOID *)((UINTN)Data + DataSize - PayloadSize));
if (Status == EFI_ALREADY_STARTED) {
//
// EFI_ALREADY_STARTED means the SetVariable() action is handled inside of SetVariableCheckHandlerMor().
// Variable driver can just return SUCCESS.
//
return EFI_SUCCESS;
}
if (EFI_ERROR (Status)) {
return Status;
}
Status = VarCheckLibSetVariableCheck (VariableName, VendorGuid, Attributes, PayloadSize, (VOID *)((UINTN)Data + DataSize - PayloadSize), mRequestSource);
if (EFI_ERROR (Status)) {
return Status;
}
AcquireLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
//
// Consider reentrant in MCA/INIT/NMI. It needs be reupdated.
//
if (1 < InterlockedIncrement (&mVariableModuleGlobal->VariableGlobal.ReentrantState)) {
Point = mVariableModuleGlobal->VariableGlobal.NonVolatileVariableBase;
//
// Parse non-volatile variable data and get last variable offset.
//
NextVariable = GetStartPointer ((VARIABLE_STORE_HEADER *)(UINTN)Point);
while (IsValidVariableHeader (NextVariable, GetEndPointer ((VARIABLE_STORE_HEADER *)(UINTN)Point))) {
NextVariable = GetNextVariablePtr (NextVariable, AuthFormat);
}
mVariableModuleGlobal->NonVolatileLastVariableOffset = (UINTN)NextVariable - (UINTN)Point;
}
//
// Check whether the input variable is already existed.
//
Status = FindVariable (VariableName, VendorGuid, &Variable, &mVariableModuleGlobal->VariableGlobal, TRUE);
if (!EFI_ERROR (Status)) {
if (((Variable.CurrPtr->Attributes & EFI_VARIABLE_RUNTIME_ACCESS) == 0) && AtRuntime ()) {
Status = EFI_WRITE_PROTECTED;
goto Done;
}
if ((Attributes != 0) && ((Attributes & (~EFI_VARIABLE_APPEND_WRITE)) != Variable.CurrPtr->Attributes)) {
//
// If a preexisting variable is rewritten with different attributes, SetVariable() shall not
// modify the variable and shall return EFI_INVALID_PARAMETER. Two exceptions to this rule:
// 1. No access attributes specified
// 2. The only attribute differing is EFI_VARIABLE_APPEND_WRITE
//
Status = EFI_INVALID_PARAMETER;
DEBUG ((DEBUG_INFO, "[Variable]: Rewritten a preexisting variable(0x%08x) with different attributes(0x%08x) - %g:%s\n", Variable.CurrPtr->Attributes, Attributes, VendorGuid, VariableName));
goto Done;
}
}
if (!FeaturePcdGet (PcdUefiVariableDefaultLangDeprecate)) {
//
// Hook the operation of setting PlatformLangCodes/PlatformLang and LangCodes/Lang.
//
Status = AutoUpdateLangVariable (VariableName, Data, DataSize);
if (EFI_ERROR (Status)) {
//
// The auto update operation failed, directly return to avoid inconsistency between PlatformLang and Lang.
//
goto Done;
}
}
if (mVariableModuleGlobal->VariableGlobal.AuthSupport) {
Status = AuthVariableLibProcessVariable (VariableName, VendorGuid, Data, DataSize, Attributes);
} else {
Status = UpdateVariable (VariableName, VendorGuid, Data, DataSize, Attributes, 0, 0, &Variable, NULL);
}
Done:
InterlockedDecrement (&mVariableModuleGlobal->VariableGlobal.ReentrantState);
ReleaseLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
if (!AtRuntime ()) {
if (!EFI_ERROR (Status)) {
SecureBootHook (
VariableName,
VendorGuid
);
}
}
return Status;
}
FindVariable
该函数在Set和Get中都有用到,该函数有五个传入参数,可以看到4个输入,1个输出。
FindVariable (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
OUT VARIABLE_POINTER_TRACK *PtrTrack,
IN VARIABLE_GLOBAL *Global,
IN BOOLEAN IgnoreRtCheck
)
其中第三个输出参数的结构体类型如下
typedef struct {
VARIABLE_HEADER *CurrPtr;
//
// If both ADDED and IN_DELETED_TRANSITION variable are present,
// InDeletedTransitionPtr will point to the IN_DELETED_TRANSITION one.
// Otherwise, CurrPtr will point to the ADDED or IN_DELETED_TRANSITION one,
// and InDeletedTransitionPtr will be NULL at the same time.
//
VARIABLE_HEADER *InDeletedTransitionPtr;
VARIABLE_HEADER *EndPtr;
VARIABLE_HEADER *StartPtr;
BOOLEAN Volatile;
} VARIABLE_POINTER_TRACK;
这个输出的结构反映了当前的变量保存区域。立方体区域实际上有两个,一个存放易失变量一个存放非易失变量,可以看到整个结构其实就是两部分:
1、Variable_Store_Header
这是变量存储区的头部,用来声明变量存储区属性。
2、Variable_Header+Data
(实际上应该是Variable_Header+Name+Data),Variable_Header结构体中就声明了变量的属性、名称大小、数据大小、GUID,随后的Name便是变量名,Data则是变量数据。
Attributes
该属性一般为下列三种:
EFI_VARIABLE_NON_VOLATILE:表示设置变量时将变量写入到非易失介质中,比如SpiFlash;
EFI_VARIABLE_BOOTSERVICE_ACCESS:表示设置的变量在UEFI阶段都可以访问,更具体的说是在调用gBS->ExitBootServices()之前都可以访问;
EFI_VARIABLE_RUNTIME_ACCESS:表示设置的变量在OS下都可以访问,当然前提是OS是UEFI兼容的。
有时可以看到该项属性为0x07,其实实际代表着EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,4+2+1。
///
/// Attributes of variable.
///
#define EFI_VARIABLE_NON_VOLATILE 0x00000001
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
根据查看FindVariable函数内部以及前面的变量存放结构,可以看到PtrTrack的StartPtr与EndPtr的指向位置就是第一个Variable的起始位置、以及最后一个Variable的截止位置,而Volatile应该就是决定了到底是去Volatile区找还是Non-Volatile区找。
可以看到在函数内最后调用了FindVariableEx函数,这个函数其实不用看就已经知道里面具体是写什么了,利用CurrPtr指针从StartPtr遍历到EndPtr,在这期间不停对比VendorGuid与VariableName,如果一致,就意味着找到了,将CurrPtr返回,如果直到EndPtr都没有,说明没找到。
UpdateVariable
前面FindVariable后,就已经知道变量存储区到底有没有要找的变量了,接下来便是调用UpdateVariable进行下一步操作,该函数操作比较繁琐,因此采取比较简洁的概括如图
VariableServiceGetVariable
相较于Set长篇大论,Get明显精简了不少,没有了繁琐的检查,只是简单的检查了一下入口参数的判定,紧接着便是用FindVariable查找是否存在该变量,如果不存在就直接退出,如果存在,则获取一下variable data的大小,判断Buffer size与variable data size相比是否足够大,如果足够,则使用CopyMem函数将GetVariableDataPtr,也就是指向variable data的指针,赋给了指向Data的指针,随后UpdateVariableInfo。
/**
This code finds variable in storage blocks (Volatile or Non-Volatile).
Caution: This function may receive untrusted input.
This function may be invoked in SMM mode, and datasize is external input.
This function will do basic validation, before parse the data.
@param VariableName Name of Variable to be found.
@param VendorGuid Variable vendor GUID.
@param Attributes Attribute value of the variable found.
@param DataSize Size of Data found. If size is less than the
data, this value contains the required size.
@param Data The buffer to return the contents of the variable. May be NULL
with a zero DataSize in order to determine the size buffer needed.
@return EFI_INVALID_PARAMETER Invalid parameter.
@return EFI_SUCCESS Find the specified variable.
@return EFI_NOT_FOUND Not found.
@return EFI_BUFFER_TO_SMALL DataSize is too small for the result.
**/
EFI_STATUS
EFIAPI
VariableServiceGetVariable (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
OUT UINT32 *Attributes OPTIONAL,
IN OUT UINTN *DataSize,
OUT VOID *Data OPTIONAL
)
{
EFI_STATUS Status;
VARIABLE_POINTER_TRACK Variable;
UINTN VarDataSize;
if ((VariableName == NULL) || (VendorGuid == NULL) || (DataSize == NULL)) {
return EFI_INVALID_PARAMETER;
}
if (VariableName[0] == 0) {
return EFI_NOT_FOUND;
}
AcquireLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
Status = FindVariable (VariableName, VendorGuid, &Variable, &mVariableModuleGlobal->VariableGlobal, FALSE);
if (EFI_ERROR (Status) || (Variable.CurrPtr == NULL)) {
goto Done;
}
//
// Get data size
//
VarDataSize = DataSizeOfVariable (Variable.CurrPtr, mVariableModuleGlobal->VariableGlobal.AuthFormat);
ASSERT (VarDataSize != 0);
if (*DataSize >= VarDataSize) {
if (Data == NULL) {
Status = EFI_INVALID_PARAMETER;
goto Done;
}
CopyMem (Data, GetVariableDataPtr (Variable.CurrPtr, mVariableModuleGlobal->VariableGlobal.AuthFormat), VarDataSize);
*DataSize = VarDataSize;
UpdateVariableInfo (VariableName, VendorGuid, Variable.Volatile, TRUE, FALSE, FALSE, FALSE, &gVariableInfo);
Status = EFI_SUCCESS;
goto Done;
} else {
*DataSize = VarDataSize;
Status = EFI_BUFFER_TOO_SMALL;
goto Done;
}
Done:
if ((Status == EFI_SUCCESS) || (Status == EFI_BUFFER_TOO_SMALL)) {
if ((Attributes != NULL) && (Variable.CurrPtr != NULL)) {
*Attributes = Variable.CurrPtr->Attributes;
}
}
ReleaseLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
return Status;
}
VariableServiceGetNextVariableName
该函数主要功能为找到下一个变量,从源码来看,主要先对入口参数进行检查,随后调用VariableServiceGetNextVariableInternal,调用的函数所做的事情就是检查所给定的变量存放表单中是否存在variable,其实现过程大致为通过遍历链表式的方法遍历变量存储区,直到找到对应variable后返回。在执行完VariableServiceGetNextVariableInternal函数后,若函数返回成功,则使用CopyMem将GetVariableNamePtr指针赋给VariableName,同样GetVendorGuidPtr指针赋给VendorGuid。
/**
This code Finds the Next available variable.
Caution: This function may receive untrusted input.
This function may be invoked in SMM mode. This function will do basic validation, before parse the data.
@param VariableNameSize The size of the VariableName buffer. The size must be large
enough to fit input string supplied in VariableName buffer.
@param VariableName Pointer to variable name.
@param VendorGuid Variable Vendor Guid.
@retval EFI_SUCCESS The function completed successfully.
@retval EFI_NOT_FOUND The next variable was not found.
@retval EFI_BUFFER_TOO_SMALL The VariableNameSize is too small for the result.
VariableNameSize has been updated with the size needed to complete the request.
@retval EFI_INVALID_PARAMETER VariableNameSize is NULL.
@retval EFI_INVALID_PARAMETER VariableName is NULL.
@retval EFI_INVALID_PARAMETER VendorGuid is NULL.
@retval EFI_INVALID_PARAMETER The input values of VariableName and VendorGuid are not a name and
GUID of an existing variable.
@retval EFI_INVALID_PARAMETER Null-terminator is not found in the first VariableNameSize bytes of
the input VariableName buffer.
**/
EFI_STATUS
EFIAPI
VariableServiceGetNextVariableName (
IN OUT UINTN *VariableNameSize,
IN OUT CHAR16 *VariableName,
IN OUT EFI_GUID *VendorGuid
)
{
EFI_STATUS Status;
UINTN MaxLen;
UINTN VarNameSize;
BOOLEAN AuthFormat;
VARIABLE_HEADER *VariablePtr;
VARIABLE_STORE_HEADER *VariableStoreHeader[VariableStoreTypeMax];
if ((VariableNameSize == NULL) || (VariableName == NULL) || (VendorGuid == NULL)) {
return EFI_INVALID_PARAMETER;
}
AuthFormat = mVariableModuleGlobal->VariableGlobal.AuthFormat;
//
// Calculate the possible maximum length of name string, including the Null terminator.
//
MaxLen = *VariableNameSize / sizeof (CHAR16);
if ((MaxLen == 0) || (StrnLenS (VariableName, MaxLen) == MaxLen)) {
//
// Null-terminator is not found in the first VariableNameSize bytes of the input VariableName buffer,
// follow spec to return EFI_INVALID_PARAMETER.
//
return EFI_INVALID_PARAMETER;
}
AcquireLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
//
// 0: Volatile, 1: HOB, 2: Non-Volatile.
// The index and attributes mapping must be kept in this order as FindVariable
// makes use of this mapping to implement search algorithm.
//
VariableStoreHeader[VariableStoreTypeVolatile] = (VARIABLE_STORE_HEADER *)(UINTN)mVariableModuleGlobal->VariableGlobal.VolatileVariableBase;
VariableStoreHeader[VariableStoreTypeHob] = (VARIABLE_STORE_HEADER *)(UINTN)mVariableModuleGlobal->VariableGlobal.HobVariableBase;
VariableStoreHeader[VariableStoreTypeNv] = mNvVariableCache;
Status = VariableServiceGetNextVariableInternal (
VariableName,
VendorGuid,
VariableStoreHeader,
&VariablePtr,
AuthFormat
);
if (!EFI_ERROR (Status)) {
VarNameSize = NameSizeOfVariable (VariablePtr, AuthFormat);
ASSERT (VarNameSize != 0);
if (VarNameSize <= *VariableNameSize) {
CopyMem (
VariableName,
GetVariableNamePtr (VariablePtr, AuthFormat),
VarNameSize
);
CopyMem (
VendorGuid,
GetVendorGuidPtr (VariablePtr, AuthFormat),
sizeof (EFI_GUID)
);
Status = EFI_SUCCESS;
} else {
Status = EFI_BUFFER_TOO_SMALL;
}
*VariableNameSize = VarNameSize;
}
ReleaseLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
return Status;
}
VariableServiceQueryVariableInfo
该函数的功能在于返回EFI variables的信息,其内部实现和前面的GetNextVariable有点像,也是调用了一个类似的函数,VariableServiceQueryVariableInfoInternal来实现其功能,
/**
This code returns information about the EFI variables.
Caution: This function may receive untrusted input.
This function may be invoked in SMM mode. This function will do basic validation, before parse the data.
@param Attributes Attributes bitmask to specify the type of variables
on which to return information.
@param MaximumVariableStorageSize Pointer to the maximum size of the storage space available
for the EFI variables associated with the attributes specified.
@param RemainingVariableStorageSize Pointer to the remaining size of the storage space available
for EFI variables associated with the attributes specified.
@param MaximumVariableSize Pointer to the maximum size of an individual EFI variables
associated with the attributes specified.
@return EFI_INVALID_PARAMETER An invalid combination of attribute bits was supplied.
@return EFI_SUCCESS Query successfully.
@return EFI_UNSUPPORTED The attribute is not supported on this platform.
**/
EFI_STATUS
EFIAPI
VariableServiceQueryVariableInfo (
IN UINT32 Attributes,
OUT UINT64 *MaximumVariableStorageSize,
OUT UINT64 *RemainingVariableStorageSize,
OUT UINT64 *MaximumVariableSize
)
{
EFI_STATUS Status;
if ((MaximumVariableStorageSize == NULL) || (RemainingVariableStorageSize == NULL) || (MaximumVariableSize == NULL) || (Attributes == 0)) {
return EFI_INVALID_PARAMETER;
}
if ((Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) != 0) {
//
// Deprecated attribute, make this check as highest priority.
//
return EFI_UNSUPPORTED;
}
if ((Attributes & EFI_VARIABLE_ATTRIBUTES_MASK) == 0) {
//
// Make sure the Attributes combination is supported by the platform.
//
return EFI_UNSUPPORTED;
} else if ((Attributes & EFI_VARIABLE_ATTRIBUTES_MASK) == EFI_VARIABLE_NON_VOLATILE) {
//
// Only EFI_VARIABLE_NON_VOLATILE attribute is invalid
//
return EFI_INVALID_PARAMETER;
} else if ((Attributes & (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)) == EFI_VARIABLE_RUNTIME_ACCESS) {
//
// Make sure if runtime bit is set, boot service bit is set also.
//
return EFI_INVALID_PARAMETER;
} else if (AtRuntime () && ((Attributes & EFI_VARIABLE_RUNTIME_ACCESS) == 0)) {
//
// Make sure RT Attribute is set if we are in Runtime phase.
//
return EFI_INVALID_PARAMETER;
} else if ((Attributes & (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_HARDWARE_ERROR_RECORD)) == EFI_VARIABLE_HARDWARE_ERROR_RECORD) {
//
// Make sure Hw Attribute is set with NV.
//
return EFI_INVALID_PARAMETER;
} else if ((Attributes & VARIABLE_ATTRIBUTE_AT_AW) != 0) {
if (!mVariableModuleGlobal->VariableGlobal.AuthSupport) {
//
// Not support authenticated variable write.
//
return EFI_UNSUPPORTED;
}
} else if ((Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) != 0) {
if (PcdGet32 (PcdHwErrStorageSize) == 0) {
//
// Not support harware error record variable variable.
//
return EFI_UNSUPPORTED;
}
}
AcquireLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
Status = VariableServiceQueryVariableInfoInternal (
Attributes,
MaximumVariableStorageSize,
RemainingVariableStorageSize,
MaximumVariableSize
);
ReleaseLockOnlyAtBootTime (&mVariableModuleGlobal->VariableGlobal.VariableServicesLock);
return Status;
}
Variable实践
该实践总共调用了 SetVariable和GetVariable两个函数,去设置与获取变量PhytiumEnable。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/PcdLib.h>
//ShellCEntryLib call user interface ShellAppMain
EFI_STATUS
EFIAPI
MyHVariableAppEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_SUCCESS;
UINTN DataSize;
UINT64 PhytiumEnable = 1;
EFI_GUID gEfiPhytiumEnableGuid = { 0xb71604d2, 0xb8ba, 0x4d9c, { 0x78, 0x88, 0xac, 0xcf, 0x1d, 0xa2, 0x26, 0xc3 }};
DataSize = sizeof (PhytiumEnable);
Status = gRT->SetVariable (
L"PhytiumEnable",
&gEfiPhytiumEnableGuid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
DataSize,
&PhytiumEnable
);
ASSERT(Status);
DEBUG ((EFI_D_ERROR," [Phytium]: Setvariable PhytiumEnable 0x%x.\n", PhytiumEnable));
PhytiumEnable = 0;
DataSize = sizeof (PhytiumEnable);
Status = gRT->GetVariable (
L"PhytiumEnable",
&gEfiPhytiumEnableGuid,
NULL,
&DataSize,
&PhytiumEnable
);
ASSERT(Status);
if (PhytiumEnable) {
DEBUG ((EFI_D_ERROR, "[Phytium] PhytiumEnable\n"));
}
return EFI_SUCCESS;
}
实现结果如下图。