《UEFI内核导读》PEI CORE的二个阶段以及的3次执行流程简介
liebian365 2024-11-02 13:37 9 浏览 0 评论
==========================================
敬请关注微信公众号:“固件C字营”
PeiCore()函数作为PEI阶段最重要的组成部分,在整个PEI生命周期中分为二个阶段,分别是主内存初始化之前(PreMemory)和内存初始化完成之后(PostMemory),在这二个阶段中PeiCore()函数会被调用3次,简化的调用流程如下。示例描述了二个阶段内存以及堆栈的分布情况。蓝色的地址空间被映射到BIOS ROM,黄色的被映射栈空间,紫色被映射到堆空间,高地址是PreMemory阶段,低地址是PostMemory阶段。
- PeiCore() //第1次在temp ram运行
- PeiDispatcher() //查找并调度PEIM
- PeiCheckAndSwitchStack() //切换堆栈
- PeiCore() //第2次在主内存运行
- ShadowedPeiCore() //加载并重映射到主内存
- PeiCore() //第3次在主内存运行
一.PeiCore()第1次执行流程
第1次调用PeiCore (SecCoreDataPtr, PpiList, NULL);此时PeiCore存储于flash,主内存未初始化,堆栈映射到Temp RAM。
VOID
EFIAPI
PeiCore (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreDataPtr,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList,
IN VOID *Data
)
- 在栈空间分配内存,用来临时保存PEI_CORE_INSTANCE实例(PrivateData)、EFI_SEC_PEI_HAND_OFF(NewSecCoreData)及其他数据。
代码:
PEI_CORE_INSTANCE PrivateData;
EFI_SEC_PEI_HAND_OFF NewSecCoreData;
OldCoreData = (PEI_CORE_INSTANCE *)Data;//等于NULL
SecCoreData = (EFI_SEC_PEI_HAND_OFF *)SecCoreDataPtr; //等于NULL
- 此时Data==NULL,PeiCore()是是第1次进入。
- 初始化PEI_CORE_INSTANCE(PrivateData)实例,复制EFI_PEI_SERVICES表到上述内存地址。
代码:CopyMem (&PrivateData.ServiceTableShadow, &gPs, sizeof (gPs));由
MdePkg\Library\BaseMemoryLibRepStr\Ia32\CopyMem.nasm实现拷贝。
- 重定位PeiCore()的私有数据地址,PrivateData.Ps 指向上述#3所述地址。
代码:PrivateData.Ps = &PrivateData.ServiceTableShadow;
- 保存PEI service表指针Ps到中断描述符表IDT地址前4个字节位置。
代码: SetPeiServicesTablePointer ((CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
- 初始化PeiCore()用到的所有库,调用由工具自动生成AutoGen.c文件内的初始化函数。
代码:ProcessLibraryConstructorList (NULL, (CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
- 初始化调试及性能追踪服务,可以用来调试、追踪优化代码流程。
- 初始化PeiCore服务。
代码:
InitializeMemoryServices (&PrivateData, SecCoreData, OldCoreData);
InitializeSecurityServices (&PrivateData.Ps, OldCoreData);
InitializeDispatcherData (&PrivateData, OldCoreData, SecCoreData);
InitializeImageServices (&PrivateData, OldCoreData);
- 如果是第1次进入,且PpiList不为NULL时则可能安装SecCore提供的PPI
代码:ProcessPpiListFromSec ((CONST EFI_PEI_SERVICES **)&PrivateData.Ps, PpiList);
- 完成了基本初始化之后,开始执行PEI dispatch,PeiDispatcher()之后的代码在第1次PeiCore()时不会执行到函数返回。PEI dispatch调度其他的PEIM完成基本的硬件初始化,比如查找并调用FSP提供的服务来初始化内存,芯片组等信息。
代码:
PeiDispatcher (SecCoreData, &PrivateData);
VOID
PeiCheckAndSwitchStack (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreData,
IN PEI_CORE_INSTANCE *Private
)
以上完成了第1次进入PeiCore的全部详细流程,#10步完成之后,主内存会被FSP初始化完成,然后再次进入PEI CORE,而不是函数返回,也就是进行第2次执行PeiCore (;;)函数,不同于第1次的是,第2次执行的时候第三个参数Data!=NULL,因此执行流程会稍有不同。
二.PeiCore()第2次执行流程
第2次调用PeiCore (SecCoreDataPtr, NULL, Private)。此时PeiCore存储于flash,主内存已初始化,栈映射到主内存,堆映射到Temp RAM。
VOID
EFIAPI
PeiCore (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreDataPtr,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList,
IN VOID *Data
)
- 在新栈空间分配内存(未使用)来临时保存PEI_CORE_INSTANCE实例(PrivateData)、EFI_SEC_PEI_HAND_OFF(NewSecCoreData)及其他数据。实际这里并不使用PrivateData而是使用函数调用过程传递过来的实参指针Private。
代码:
PEI_CORE_INSTANCE PrivateData;
EFI_SEC_PEI_HAND_OFF NewSecCoreData;
OldCoreData = (PEI_CORE_INSTANCE *)Data;//不等于NULL
SecCoreData = (EFI_SEC_PEI_HAND_OFF *)SecCoreDataPtr;//不等于NULL
- 此时Data!=NULL,PeiCore()是是第2次进入,意味着PrivateData可以使用PeiCore第1次执行时已经初始化好的实例,也就是使用第三个形参传递过来的数据。
- 此时OldCoreData->ShadowedPeiCore == NULL表示PeiCore.efi未被加载到主内存,需要从flash被加载并进行重定位PeiCore.efi到内存中。
- 重定位PeiCore的私有数据地址,在进入#5步前切换堆到主内存。这里OldCoreData是第1次运行的实例,新的OldCoreData数据指向主内存,这里HeapOffset指主内存和临时内存之间的地址差(图1)。完成之后HobList(堆)、UnknownFvInfo、CurrentFvFileHandles、PpiData.PpiList、PpiData.CallbackNotifyList、PpiData.DispatchNotifyList、Fv[].PeimState、Fv[].FvFileHandles、TempFileGuid、TempFileHandles、MemoryBaseAddress、PPI descriptor's pointer等指向主内存,x86架构HeapOffsetPositive=FALSE,也就是主内存地址比临时内存(CAR)地址小。PeiMemoryInstalled = TRUE,PeimDispatcherReenter =TRUE。
代码:
OldCoreData->Ps = &OldCoreData->ServiceTableShadow;
OldCoreData->CpuIo = &OldCoreData->ServiceTableShadow.CpuIo;
OldCoreData->HobList.Raw = (VOID *)(OldCoreData->HobList.Raw - OldCoreData->HeapOffset);
OldCoreData->PeiMemoryInstalled = TRUE;
OldCoreData->PeimDispatcherReenter = TRUE;
VOID ConvertPointer (VOID **Pointer,UINTN TempBottom,UINTN TempTop,UINTN Offset)
{
*Pointer = (VOID *)((UINTN)*Pointer - Offset); //Offset等于临时RAM和主内存地址差
}
- 查找PeiCore.efi所在FV,一般默认会存储在BFV,也可使用如下的PPI去查找其地址。
代码:
PeiServicesLocatePpi (
&gEfiPeiCoreFvLocationPpiGuid,
0,
NULL,
(VOID **)&PeiCoreFvLocationPpi
);
- 找到所在FV之后,使用FindFileByType查找PEI_CORE类型FFS所在位置。
代码:
FvPpi->FindFileByType (
PrivateData->Fv[PeiCoreFvIndex].FvPpi,
EFI_FV_FILETYPE_PEI_CORE,
PrivateData->Fv[PeiCoreFvIndex].FvHandle,
&PeiCoreFileHandle );
- 加载并重定位PeiCore.efi到主内存,返回PeiCore()的入口地址。使用PeiLoadImage()方法,把FFS读取到主内存(类型:EfiBootServicesCode),并返回PeiCore.efi这个PE image的入口地址_ModuleEntryPoint(地址赋值给EntryPoint)。这里的我们需要注意的是我们第3次执行的时候并不想从这个地址开始,而是从PeiCore()这个函数地址开始,因此我们需要修正一下这个地址。具体方法是在EntryPoint的基础上增加一个PeiCore()到_ModuleEntryPoint()之间偏移 ,(PEICORE_FUNCTION_POINTER)((UINTN)EntryPoint + (UINTN)PeiCore - (UINTN)_ModuleEntryPoint);
代码:
PeiLoadImage (
GetPeiServicesTablePointer (),
*((EFI_PEI_FILE_HANDLE *)&PeiCoreFileHandle),
PEIM_STATE_REGISTER_FOR_SHADOW,
&EntryPoint,
&AuthenticationState
);
- 调用PeiCore (SecCoreData, PpiList, OldCoreData)进入第3次调用。
typedef struct {
UINT32 VirtualAddress;
UINT32 SizeOfBlock;
} EFI_IMAGE_BASE_RELOCATION;
第#8步之后,结束第2次调用,该过程通过使用来自于EFI_PEI_LOAD_FILE_PPI提供的服务PeiLoadImage()把PeiCore.efi加载并重定位到主内存。过程中会依次把PeCoff文件的txt、date、reloc段分别加载定位到主内存中新的地址。需要注意的时UEFI当中通常会把所有的数据段合并成一个数据段,因此类似bss段没有单独出现。
重定位过程中会读取reloc段内的重定位数据EFI_IMAGE_BASE_RELOCATION重定位项,逐个地址修正所有代码段和数据段中的地址,完成上述步骤之后就可以直接跳转到PeiCore()进行第3次调用。因此可以说PEI分二个阶段分别是PreMemmory和PostMemory,但是PeiCore()这个函数却是被调用了3次,每一次都没用执行到该函数的最后一行代码,也就是函数都没用返回,而是嵌套调用,这里面更多涉及到堆栈的切换和堆栈再平衡。
第1次调用时主内存初始化完成后,也即是InstallPeiMemory()被调用后,PeiDispatcher()会调用PeiCheckAndSwitchStack()把堆栈切换到主内存,而这也是第2次调用的基础。
三.PeiCore()第3次执行流程
第3次调用PeiCore (SecCoreData, PpiList, OldCoreData),此时主内存已初始化,PeiCore被读取并加载并重定位到主内存,堆栈映射在主内存,OldCoreData != NULL, OldCoreData->ShadowedPeiCore != NULL。
VOID
EFIAPI
PeiCore (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreDataPtr,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList,
IN VOID *Data
)
PEICORE_FUNCTION_POINTER
ShadowPeiCore (
IN PEI_CORE_INSTANCE *PrivateData
)
- 在新栈空间分配内存,用来临时保存PEI_CORE_INSTANCE实例(PrivateData)、EFI_SEC_PEI_HAND_OFF(NewSecCoreData)及其他数据。
代码:
PEI_CORE_INSTANCE PrivateData;
EFI_SEC_PEI_HAND_OFF NewSecCoreData;
OldCoreData = (PEI_CORE_INSTANCE *)Data;//不等于NULL
SecCoreData = (EFI_SEC_PEI_HAND_OFF *)SecCoreDataPtr;//不等于NULL
- 此时PeiCore.efi已经被加载到内存,且此时主内存已经初始化完成。使用第2次调用PeiCore()传递过来的值对PrivateData进行初始化。
代码:
CopyMem (&NewSecCoreData, SecCoreDataPtr, sizeof (NewSecCoreData));
SecCoreData = &NewSecCoreData;
CopyMem (&PrivateData, OldCoreData, sizeof (PrivateData));
CpuIo = (VOID *)PrivateData.ServiceTableShadow.CpuIo;
PciCfg = (VOID *)PrivateData.ServiceTableShadow.PciCfg;
CopyMem (&PrivateData.ServiceTableShadow, &gPs, sizeof (gPs));
PrivateData.ServiceTableShadow.CpuIo = CpuIo;
PrivateData.ServiceTableShadow.PciCfg = PciCfg;
- 重定位PeiCore的私有数据地址,PrivateData.Ps 指向主内存地址。
代码:PrivateData.Ps = &PrivateData.ServiceTableShadow;
- 使用中断描述符表IDT来保存PEI service表指针Ps。
代码: SetPeiServicesTablePointer ((CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
- 初始化被链接到PeiCore的所有库,调用自动生成AutoGen.c文件内的库初始化函数。
代码:ProcessLibraryConstructorList (NULL, (CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
- 初始化调试及性能追踪服务,可以用来调试、追踪优化代码流程。
代码:
PERF_EVENT ("SEC")
PERF_CROSSMODULE_BEGIN ("PEI")/PERF_CROSSMODULE_END ("PEI");
PERF_INMODULE_BEGIN ("PreMem")/PERF_INMODULE_END ("PreMem")
PERF_INMODULE_BEGIN ("DisMem")/PERF_INMODULE_END ("DisMem")
PERF_INMODULE_BEGIN ("PostMem")/PERF_INMODULE_END ("PostMem")
- 初始化PeiCore服务。
代码:
InitializeMemoryServices (&PrivateData, SecCoreData, OldCoreData);
InitializeSecurityServices (&PrivateData.Ps, OldCoreData);
InitializeDispatcherData (&PrivateData, OldCoreData, SecCoreData);
InitializeImageServices (&PrivateData, OldCoreData);
- 若PcdMigrateTemporaryRamFirmwareVolumes==Ture,把所有的PEIM和PeiCore重新加载到主内存,默认为FALSE,即只在S0/S3时候加载到主内存(PcdShadowPeimOnBoot=True)。
代码:EvacuateTempRam (&PrivateData, SecCoreData);
- 禁用Temporary RAM,安装mMemoryDiscoveredPpi并通知、调度所有监听该PPI的Notify。
代码:TemporaryRamDonePpi->TemporaryRamDone ()
- 安装Memory Discovered PPI通知并调用所有相关的notifies
代码:ProcessDispatchNotifyList (&PrivateData);
- 再次执行PEI dispatch,查找、加载并执行PEIM,此时PeiCore在主内存内执行。
代码:PeiDispatcher (SecCoreData, &PrivateData);
- 所有的PEIM被执行完成之后,使用EFI_DXE_IPL_PPI,调用DxeIpl->Entry (DxeIpl,&PrivateData.Ps, PrivateData.HobList)服务加载DxeCore,并跳转到DxeCore,不再从PeiCore()函数返回。
EFI_STATUS
(EFIAPI *EFI_DXE_IPL_ENTRY)(
IN CONST EFI_DXE_IPL_PPI *This,
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_HOB_POINTERS HobList
);
以上完成了第3次进入PeiCore()的全部流程,后续进入DxeCore进行下一阶段的初始化流程。
参考代码:
MdeModulePkg\Core\Pei\PeiMain\PeiMain.c
MdeModulePkg\Core\Pei\Dispatcher\Dispatcher.c
个人观点.欢迎斧正
==============================
敬请猛戳下面链接,关注&转发
敬请关注微信公众号:“固件C字营”
敬请收录公众号话题:“固件C字营”
点击右下角“分享”,快乐更多人
==============================
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)