《UEFI内核导读》PEI CORE的二个阶段以及的3次执行流程简介
liebian365 2024-11-02 13:37 102 浏览 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字营”
点击右下角“分享”,快乐更多人
==============================
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)