BIOS追code之路
刚涉足BIOS这个领域,只是随遇而安的应届生,什么都不懂,前辈很厉害,可以一起探讨,资料都是什么QQ群或者百度找的,侵删
在uefi_artisan这位博主关于源码分析的博客下,有着这么一个想法,如果用C语言(伪)代码描述整个BIOS 的执行流程,那么流程会是这样的:
main()
{
SecStartup ( SizeOfRam, TempRamBase, *BootFirmwareVolume)
{
// Update the base address and length of Pei temporary memory
PeiCore (&SecCoreData, mPeiSecPlatformInformationPpi);
}
PeiCore()
{
PeiDispatcher (SecCoreData, &PrivateData);
}
DxeMain(VOID *HobStart)
{
CoreDispatcher ();
~~~~~~~~~~~~~~~~~~
}
gBds->Entry (gBds);
}
————————————————
原文链接:https://blog.csdn.net/robinsongsog/article/details/51177600
综述
SEC(Security Phase)阶段是平台初始化的第一个阶段,计算机系统加电后首先进入这个阶段。
SEC阶段的功能:
SEC作为整个系统的起点,可能会遇到各种异常,就需要设置IDT,有了中断描述符表接收异常,就能让系统遭遇意外情况时不至于崩溃,同时它还为PEI阶段的代码设置临时内存的基地址和长度,并传给PEI,还需要找到PEI代码的入口点,将控制权移交过去,并且处理临时内存。
UEFI系统开机或重启后首先进入SEC阶段,SEC阶段系统执行以下四种任务:
- 接收并处理系统启动和重启信号,系统加电信号、系统重启信号、系统运行过程中的异常信号。
- 初始化临时存储区域:系统运行在SEC阶段时,仅CPU和CPU内部资源被初始化,而各种外部设备和内存都没有被初始化,主要是出了最初的汇编代码,SEC阶段还有C代码。因此系统需要一部分临时内存用于代码和数据的存储,一般称为临时RAM,临时RAM只能位于CPU内部(CPU和CPU内部的资源最先被初始化)。最常用的临时RAM是Cache,通过将Cache设置为no-eviction模式,(noeviction:不删除策略,不淘汰,如果内存已满,添加数据是报错的),来把其当成内存使用(此时读取命中则返回Cache中的数据,读取缺失并不会向主存发出缺失事件;写命中时写入Cache,写缺失时也不会向主存发出缺失事件),这种技术称为CAR(Cache As RAM)。
- SEC阶段是可信系统的根:作为系统启动的第一部分,只有SEC能被系统信任,以后的各个阶段才有被信任的基础。因此,大部分情况下SEC再转交控制权给PEI前可以验证PEI是否可信。
- 传递系统参数给下一阶段:SEC阶段的一切工作都是为PEI阶段做准备的,最重要把系统的控制权转交给PEI,并将SEC阶段的运行信息汇报给PEI。SEC通过将以下信息作为参数传递给PEI的入口程序来向PEI汇报信息:
- 系统当前状态,PEI根据状态值判断系统当前的健康情况。
- 可启动固件(Boot Firmware Volume)的地址和大小,PEI据此判断可用硬件。
- 临时RAM区域的地址和大小。
- 栈的地址和大小。
SEC阶段执行流程及流程图
无论是在操作系统点击了重启还是直接按下电脑的重启电源键,计算机本质上是给CPU发送了一个ResetVector 信号,也就是重置,那么CPU就会从SEC阶段把固件的代码再跑一遍,这跟关机再开机不同,
以临时RAM初始化为界,SEC的执行又分为两大部分:临时RAM生效之前称为Reset
Vector阶段,临时RAM生效后调用SEC人口函数从而进人SEC功能区。
其中Reset Vector的执行流程如下:
进入固件接口。
1. 从实模式转换到32位平坦模式。
2. 定位固件中的BFV(Boot Firmware Volume)。
3. 定位BFV中的SEC影响。
4. 如果是64位系统,则从32位模式转换至64位模式。
5. 调用SEC入口函数。
整体流程图:
Initialize No-Eviction Mode NEM::在发现平台上的内存之前,在cpu缓存中建立一个数据区域,以便在初始化早期使用基于堆栈的编程语言
Various early BSP/AP interactions:一系列包含一些固定延迟事件的标准步骤,如:发送INIT IPI到所有ap,发送Start-up IPI (SIPI)到所有ap,从ap收集BIST数据
关于BP启动AP的相关知识:https://blog.csdn.net/mengkevin/article/details/72875899
Resect Vector部分
代码体现:最先开始运行的代码文件为UefiCpuPkg\ResetVector\Vtf0\Ia16\ResetVectorVtf0.asm
BITS 16 //该伪指令说明程序是以16位的方式运行
ALIGN 16//按照 16 个字节的倍数对齐下一个符号,空隙默认用0 来填充
;
;当页表在VTF0中时,将图像大小调整为4k。如果VTF0图像内置了页表,那么我们需要确保VTF0的末尾是页表结束位置上方的4k。
;这是必需的,以便当VTF0位于固件设备中0x100000000 (4GB)以下时,页表将是4k对齐的。
;
%ifdef ALIGN_TOP_TO_4K_FOR_PAGING
TIMES (0x1000 - ($ - EndOfPageTables) - 0x20) DB 0
%endif
;
;应用程序处理器入口点
;GenFv生成在4k边界上对齐的代码,该边界将跳转到此位置。(0xffffffe0)这允许使用本地APIC启动IPI来唤醒应用程序处理器。
;
applicationProcessorEntryPoint:
jmp EarlyApInitReal16
ALIGN 8
DD 0
EarlyBspInitReal16
通过jmp跳转到UefiCpuPkg\ResetVector\Vtf0\Ia16\Init16.asm下的EarlyBspInitReal16:位置
;
; DI(destination index)是目的变址寄存器,用做隐含的目的串地址,默认在ES中;ES叫做额外的段寄存器. 它通常跟DI一起用来做指针使用.
; BP是基指针, 通常BP用来保存使用局部变量的地址.
;
EarlyBspInitReal16:
mov di, 'BP'
jmp short Main16
;下面的函数说明
;OneTimeCall是宏,用于模拟call指令,与OneTimeCallRet 配合使用,因为在Reset Vector部分还没有RAM可用
;* %macro是定义多行宏,定义一个名为OneTimeCall 的宏,接收一个参数(nasm汇编)*/
/* %macro 宏名 参数名列表
宏体
%endmacro */
%macro OneTimeCall 1
jmp %1
%1 %+ OneTimerCallReturn:
%endmacro
%macro OneTimeCallRet 1
jmp %1 %+ OneTimerCallReturn
%endmacro
那么OneTimeCall EarlyInit16相当于:
jmp EarlyInit16
EarlyInit16OneTimerCallReturn: / 需要注意的是%+可以连接字符串 /
Main16
通过jmp跳转到UefiCpuPkg\ResetVector\Vtf0\Main.asm下的Main16:位置
BITS 16
Main16:
;
;OneTimeCall是宏,用于模拟call指令,与OneTimeCallRet 配合使用,因为在Reset Vector部分还没有RAM可用
;
OneTimeCall EarlyInit16
;
; 将处理器从16位实模式转换为32位平面模式
; 平面模式:直接用一个地址寄存器来线性访问4G的内存.32位的CPU最多可以寻址4GB的内存空间,如果物理内存大于4GB,超出的部分CPU是无法寻址到的。
; 保护模式: 在这种状态下, 一切程序都可以用线性地址(不分段)访问自己所拥有的4G的内存空间, 但是不能访问其他程序的空间.
; 实模式:寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。
;
OneTimeCall TransitionFromReal16To32BitFlat
BITS 32
;
; 定位固件中的BFV
; FV:固件卷,指在FD上一个连续的部分,我们可以把它看成一个逻辑设备,因为我们代码真正操作的是FV,而非FD。
; FFS的概念也是以FV的形式存在,它描述了FV中的文件组织方式。FV之于FD,类似于thread之于package。
;
OneTimeCall Flat32SearchForBfvBase
;
; EBP - BFV的开始
; EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
;
;
; 搜索SEC入口点
; 定位BFV中的SEC映像
;
OneTimeCall Flat32SearchForSecEntryPoint
;
; ESI - SEC Core entry point
; ESI称为源变址寄存器,通常存放 要处理的数据的内存地址。
; EBP - Start of BFV
; esi存放了SEC的入口地址,EBP存放了BFV起始地址
;
%ifdef ARCH_IA32
;
; 将EAX初始值恢复到EAX寄存器
; ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
;
mov eax, esp
;
; 跳到32-bit sec的入口点
jmp esi
%else
;
; 将处理器从32位平面模式转换为64位平面模式
;
OneTimeCall Transition32FlatTo64Flat
BITS 64
;
; 一些值是在32位模式下计算的。对于这些值,确保64位寄存器的上高32位为零。
;
mov rax, 0x00000000ffffffff
and rsi, rax
and rbp, rax
and rsp, rax
;
; RSI - SEC Core entry point
; RBP - Start of BFV
;
;
; 将初始EAX值恢复到RAX寄存器
;
mov rax, rsp
;
; 跳到64-bit sec的入口点
;
jmp rsi
%endif
进入SEC功能区
工作内容:进入功能区后,首先利用CAR技术初始化栈,初始化IDT,初始化EFI_SEC_PEI_HAND_OFF,将控制权交给PEI,并将EFI_SEC_PEI_HAND_OFF传递给PEI.这个结构体很重要!!!
CAR (Cache ASRAM),在Cashe上开辟一段空间作为内存使用(此时内存尚未初始化,相关C语言运行需要内存和栈的空间) ;
IDT = Interrupt Descriptor Table 中断描述表,,记录了0~255的中断号和调用函数之间的关系。结构体如下所述:
下面是EFI_SEC_PEI_HAND_OFF,这个是UEFI当中在SEC阶段最重要的一个数据结构,将环境从以汇编语言执行转向C语言执行。
#define SEC_IDT_ENTRY_COUNT 34
typedef struct _SEC_IDT_TABLE {
//
// 在IDT之前保留8个字节来存储EFI_PEI_SERVICES**,因为IDT基址应该是8字节对齐。
// 注意:对于IA32,只有IDT前面的4个字节用于存储EFI_PEI_SERVICES**
//
UINT64 PeiService;
UINT64 IdtTable[SEC_IDT_ENTRY_COUNT];
} SEC_IDT_TABLE;
**
EFI_SEC_PEI_HAND_OFF!!!★★★★★
**
/// EFI_SEC_PEI_HAND_OFF结构体保存有关PEI核操作环境的信息,例如临时RAM位置的大小、堆栈位置和BFV位置。
typedef struct _EFI_SEC_PEI_HAND_OFF {
/// 数据结构的大小
UINT16 DataSize;
/// **指向BFV的第一个字节,PEI Dispatcher应该搜索PEI模块。**
VOID *BootFirmwareVolumeBase;
/// BFV的大小,以字节为单位。
UINTN BootFirmwareVolumeSize;
/// 指向临时RAM的第一个字节。
VOID *TemporaryRamBase;
/// 临时RAM的大小,以字节为单位。
UINTN TemporaryRamSize;
/// 指向PEI可以使用的临时RAM的第一个字节。PeiTemporaryRamBase和PeiTemporaryRamSize所描述的区域不能超出TemporaryRamBase和TemporaryRamSize所描述的区域。
/// 这个区域不应该与StackBase和StackSize返回的区域重叠。
VOID *PeiTemporaryRamBase;
///PEI使用的可用临时RAM的大小,以字节为单位。
UINTN PeiTemporaryRamSize;
///指向堆栈的第一个字节。这可能是由TemporaryRamBase和TemporaryRamSize描述的内存的一部分,也可能是一个完全独立的区域。
VOID *StackBase;
/// 堆栈的大小,以字节为单位。
UINTN StackSize;
} EFI_SEC_PEI_HAND_OFF;
OVMF下的SEC功能区执行过程
不同硬件平台,SEC代码会有不同实现方式,但大致过程相似。下面以OVMF为例,介绍SEC功能区执行过程。
打开文件edk2\OvmfPkg\Sec\X64\SecEntry.nasm
ASM_PFX(_ModuleEntryPoint):
;
; 用初始堆栈值填充临时RAM。
; The loop below will seed the heap as well, but that's harmless.
;
mov rax, (FixedPcdGet32 (PcdInitValueInTempStack) << 32) | FixedPcdGet32 (PcdInitValueInTempStack)
mov rdi, FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) ; 相对于ES, qword来存储基址
mov rcx, FixedPcdGet32 (PcdOvmfSecPeiTempRamSize) / 8 ; qword从基地计数存储
cld
rep stosq
;
; 加载基于PCDs的临时RAM堆栈
; 临时 RAM 已经初始化,设置栈地址。PcdOvmfSecPeiTempRamBase 和 PcdOvmfSecPeiTempRamSize在 OvmfPkgIa32X64 . fdf 中定义
; 分别为 0x010000 和 0x008000
%define SEC_TOP_OF_STACK (FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) + \
FixedPcdGet32 (PcdOvmfSecPeiTempRamSize))
mov rsp, SEC_TOP_OF_STACK
nop
;
; 设置参数并调用SecCoreStartupWithStack
; rcx: BootFirmwareVolumePtr
; rdx: TopOfCurrentStack
;
mov rcx, rbp ; BFV 首地址,rbp 为传参数
mov rdx, rsp ; 栈起始地址
sub rsp, 0x20
call ASM_PFX(SecCoreStartupWithStack) ; 此时栈已可用,故可使用 call 指令
;或者call ASM_PFX(SecStartup ) ;
//
//IDTR, GDTR, LDTR描述符的字节打包结构
//
typedef struct {
UINT16 Limit;
UINTN Base;
} IA32_DESCRIPTOR;
IA32下的Sec入口函数
根据最后的call指令找到跳转到SecCoreStartupWithStack函数,在edk2\OvmfPkg\Sec\SecMain.c中:这是在Ovmf平台下的下的,在IA32平台下有与文章开头一致的入口函数在IA32FamilyCpuPkg\SecCore\SecMain.c中,具体函数内容为:
/**
SEC的C语言阶段的入口点。SEC汇编代码初始化一些临时内存并建立堆栈后,控制被转移到这个函数。
@param SizeOfRam 可用的临时内存的大小。
@param TempRamBase 临时内存的基址
@param BootFirmwareVolume BFV的基本地址。
**/
VOID
EFIAPI
SecStartup (
IN UINT32 SizeOfRam,
IN UINT32 TempRamBase,
IN VOID *BootFirmwareVolume
)
{
EFI_SEC_PEI_HAND_OFF SecCoreData;
IA32_DESCRIPTOR IdtDescriptor;
SEC_IDT_TABLE IdtTableInStack;
UINT32 Index;
UINT32 PeiStackSize;
EFI_STATUS Status;
//
// Report Status Code to indicate entering SEC core
// 报告状态码指示进入SEC核心
// 如果状态代码类型已启用,则报告带有最小参数的状态代码。
// 如果由type指定的状态码类型在PcdReportStatusCodeProperyMask中启用,就调用ReportStatusCode()传入类型和值。
//
REPORT_STATUS_CODE (
EFI_PROGRESS_CODE,
EFI_SOFTWARE_SEC | EFI_SW_SEC_PC_ENTRY_POINT
);
//
//PcdPeiTemporaryRamStackSize的值是指定临时RAM中的堆栈大小。0表示临时ramsize的一半。
//
PeiStackSize = PcdGet32 (PcdPeiTemporaryRamStackSize);//|0|UINT32|0x10001003
if (PeiStackSize == 0) {
PeiStackSize = (SizeOfRam >> 1);
}
ASSERT (PeiStackSize < SizeOfRam);
//
// Process all libraries constructor function linked to SecCore.
// 处理所有链接到SecCore的库构造函数。
// 为模块的所有依赖库调用库构造函数的自动生成函数。一旦建立了堆栈,SEC核心必须调用这个函数。
//
ProcessLibraryConstructorList ();
//
// Initialize floating point operating environment to be compliant with UEFI spec.
// 初始化浮点操作环境以符合UEFI规范。初始化浮点寄存器
// 这个函数将浮点控制字初始化为0x027F(所有异常都被屏蔽,双精度,四舍五入到最接近),
// 多媒体扩展控制字(如果支持)初始化为0x1F80(所有异常都被屏蔽,四舍五入到最接近,对于被屏蔽的下流,flush为零)。
//
InitializeFloatingPointUnits ();
// |-------------------|---->
// |Idt Table |
// |-------------------|
// |PeiService Pointer | PeiStackSize
// |-------------------|
// | |
// | Stack |
// |-------------------|---->
// | |
// | |
// | Heap | PeiTemporayRamSize
// | |
// | |
// |-------------------|----> TempRamBase
//关于堆栈的相关知识:https://blog.csdn.net/myqq1418/article/details/81584761
//
// 初始化IDT
// 在IDT之前保留8个字节来存储EFI_PEI_SERVICES**,因为IDT基址应该是8字节对齐。
// 注意:对于IA32,只有IDT前面的4个字节用于存储EFI_PEI_SERVICES**
//
IdtTableInStack.PeiService = 0;
for (Index = 0; Index < SEC_IDT_ENTRY_COUNT; Index ++) {
// mIdtEntryTemplate IA32_IDT_GATE_DESCRIPTOR IDT 中断门描述符
// IDT里的描述符就是描述中断处理程序的数据结构
CopyMem ((VOID*)&IdtTableInStack.IdtTable[Index], (VOID*)&mIdtEntryTemplate, sizeof (UINT64));
}
//IdtDescriptor IA32_DESCRIPTOR IDTR, GDTR, LDTR描述符的字节打包结构
//描述符是存储描述信息的数据结构。GDT/LDT里描述符就是描述段地址和门的描述符。
IdtDescriptor.Base = (UINTN) &IdtTableInStack.IdtTable;
IdtDescriptor.Limit = (UINT16)(sizeof (IdtTableInStack.IdtTable) - 1);
//
// 写入当前中断描述符表寄存器(GDTR)描述符。
// 写入当前IDTR描述符并在IDTR中返回它。此功能仅适用于IA-32和X64。
// 如果Idtr为NULL,则ASSERT()。
//
AsmWriteIdtr (&IdtDescriptor);
//
// Setup the default exception handlers
// 初始化调试代理。
// 该功能用于为SMM代码的源代码调试设置调试环境。
// 如果InitFlag为DEBUG_AGENT_INIT_SMM,则会覆盖IDT表项,初始化调试端口。它将从GUIDed HOB获得调试代理邮箱,
// 如果它存在,调试代理将把它复制到SMM空间的本地邮箱中。它将覆盖IDT表项并初始化调试端口。Context将为空。
// 如果“InitFlag”为“DEBUG_AGENT_INIT_ENTER_SMI”,则调试代理将保存调试寄存器并在SMM空间中获取本地邮箱。Context将为空。
// 当“InitFlag”为“DEBUG_AGENT_INIT_EXIT_SMI”时,调试代理将恢复调试寄存器。Context将为空。
//
Status = InitializeCpuExceptionHandlers (NULL);
ASSERT_EFI_ERROR (Status);
//
// Update the base address and length of Pei temporary memory
// 初始化SecCoreData,将临时的RAM地址,栈地址、BFV地址赋值给SecCoreData
//
SecCoreData.DataSize = (UINT16) sizeof (EFI_SEC_PEI_HAND_OFF);
SecCoreData.BootFirmwareVolumeBase = BootFirmwareVolume;
SecCoreData.BootFirmwareVolumeSize = (UINTN)(0x100000000ULL - (UINTN) BootFirmwareVolume);
SecCoreData.TemporaryRamBase = (VOID*)(UINTN) TempRamBase;
SecCoreData.TemporaryRamSize = SizeOfRam;
SecCoreData.PeiTemporaryRamBase = SecCoreData.TemporaryRamBase;
SecCoreData.PeiTemporaryRamSize = SizeOfRam - PeiStackSize;
SecCoreData.StackBase = (VOID*)(UINTN)(TempRamBase + SecCoreData.PeiTemporaryRamSize);
SecCoreData.StackSize = PeiStackSize;
//
// Initialize Debug Agent to support source level debug in SEC/PEI phases before memory ready.
// 在内存准备好之前,初始化调试代理以支持SEC/PEI阶段的源级调试。
//
InitializeDebugAgent (DEBUG_AGENT_INIT_PREMEM_SEC, &SecCoreData, SecStartupPhase2);
}
InitializeDebugAgent()->SecStartupPhase2()
在最后一行代码处,函数在MdeModulePkg\Library\DebugAgentLibNull\DebugAgentLibNull.c中,具体内容为:
/**
初始化调试代理。
此函数用于设置调试环境,以支持源代码级的调试。
如果某些调试代理库实例有一些私人数据保存在堆栈中,这个函数必须在此模式工作不返回给调用者,
然后调用者需要结束后所有其他逻辑InitializeDebugAgent()成一个函数并将其传递到InitializeDebugAgent () .
InitializeDebugAgent()负责调用传入函数结束时InitializeDebugAgent()。
如果参数函数不为空,调试代理库实例将通过在上下文中传递参数来调用它。
如果Function()为空,调试代理库实例将在安装调试环境后返回。
@param[in] InitFlag Init flag is used to decide the initialize process.
@param[in] Context Context needed according to InitFlag; it was optional.
@param[in] Function Continue function called by debug agent library; it was
optional.
**/
VOID
EFIAPI
InitializeDebugAgent (
IN UINT32 InitFlag,
IN VOID *Context, OPTIONAL
IN DEBUG_AGENT_CONTINUE Function OPTIONAL
)
{
if (Function != NULL) {
Function (Context);
}
}
SecStartupPhase2()-> PeiCore ()
紧接着跳转到SecStartupPhase2()函数;先看一下涉及到的数据结构
///
/// PEIM用来描述PEI Foundation可用服务的数据结构。
///
typedef struct {
///
/// 这个字段是一组标记,描述这个导入的表条目的特征。
/// 所有标记都定义为EFI_PEI_PPI_DESCRIPTOR_***,也可以组合为一个标记。
/// Flags描述了PPI的特征
UINTN Flags;
///
/// 命名接口的EFI_GUID的地址。
/// Guid是PPI的名字。
EFI_GUID *Guid;
///
/// 指向PPI的指针。它包含安装服务所需的信息。
/// Ppi是service的实体,这个是PPI的真正意义。
VOID *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;
/**
在InitializeDebugAgent()结束时调用调用者提供的函数。
SEC的C语言阶段的入口点。SEC汇编代码初始化一些临时内存并建立堆栈后,
控制被转移到这个函数。
@param[in] Context 初始化debugagent()的第一个输入参数。
**/
VOID
NORETURN
EFIAPI
SecStartupPhase2(
IN VOID *Context
)
{
EFI_SEC_PEI_HAND_OFF *SecCoreData;
EFI_PEI_PPI_DESCRIPTOR *PpiList;
UINT32 Index;
EFI_PEI_PPI_DESCRIPTOR *AllSecPpiList;
EFI_PEI_CORE_ENTRY_POINT PeiCoreEntryPoint;
SecCoreData = (EFI_SEC_PEI_HAND_OFF *) Context;
AllSecPpiList = (EFI_PEI_PPI_DESCRIPTOR *) SecCoreData->PeiTemporaryRamBase;
//
// Find Pei Core entry point. It will report SEC and Pei Core debug information if remote debug is enabled.
// 找到Pei核心入口点。如果启用了远程调试,它将报告SEC和Pei核心调试信息。
//
FindAndReportEntryPoints ((EFI_FIRMWARE_VOLUME_HEADER *) SecCoreData->BootFirmwareVolumeBase, &PeiCoreEntryPoint);
if (PeiCoreEntryPoint == NULL)
{
CpuDeadLoop ();
}
//
// 在进入PeiCore之前执行特定平台的初始化。
//
PpiList = SecPlatformMain (SecCoreData);
if (PpiList != NULL) {
//
// 从终端PPI中移除终端标志
//
CopyMem (AllSecPpiList, mPeiSecPlatformInformationPpi, sizeof (mPeiSecPlatformInformationPpi));
Index = sizeof (mPeiSecPlatformInformationPpi) / sizeof (EFI_PEI_PPI_DESCRIPTOR) - 1;
AllSecPpiList[Index].Flags = AllSecPpiList[Index].Flags & (~EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);
//
// 向平台添加额外的PPI列表
//
Index += 1;
while (((PpiList->Flags & EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST) != EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST)) {
CopyMem (&AllSecPpiList[Index], PpiList, sizeof (EFI_PEI_PPI_DESCRIPTOR));
Index++;
PpiList++;
}
//
// 添加终端PPI
//
CopyMem (&AllSecPpiList[Index ++], PpiList, sizeof (EFI_PEI_PPI_DESCRIPTOR));
//
// 将PpiList设置为总PPI
//
PpiList = AllSecPpiList;
//
// 调整PEI TEMP RAM范围。
//
ASSERT (SecCoreData->PeiTemporaryRamSize > Index * sizeof (EFI_PEI_PPI_DESCRIPTOR));
SecCoreData->PeiTemporaryRamBase = (VOID *)((UINTN) SecCoreData->PeiTemporaryRamBase + Index * sizeof (EFI_PEI_PPI_DESCRIPTOR));
SecCoreData->PeiTemporaryRamSize = SecCoreData->PeiTemporaryRamSize - Index * sizeof (EFI_PEI_PPI_DESCRIPTOR);
} else {
//
// 没有添加PPI, PpiList直接指向通用PPI列表。
//
PpiList = &mPeiSecPlatformInformationPpi[0];
}
DEBUG ((
DEBUG_INFO,
"%a() Stack Base: 0x%p, Stack Size: 0x%x\n",
__FUNCTION__,
SecCoreData->StackBase,
(UINT32) SecCoreData->StackSize
));
//
// 报告状态代码,指示转移到PEI核心
//
REPORT_STATUS_CODE (
EFI_PROGRESS_CODE,
EFI_SOFTWARE_SEC | EFI_SW_SEC_PC_HANDOFF_TO_NEXT
);
//
// 将控制转移到PEI核心
//
ASSERT (PeiCoreEntryPoint != NULL);
(*PeiCoreEntryPoint) (SecCoreData, PpiList);
//
// Should not come here.
//
UNREACHABLE ();
}
子函数具体说明之SecPlatformMain()
关于Windows架构的一些知识可以戳https://blog.csdn.net/freeking101/article/details/102752048
/**
A developer supplied function to perform platform specific operations.
开发人员提供了执行平台特定操作的函数。
SecPlatformMain()是开发人员提供的函数,用于执行适合于给定平台的任何操作。
在SEC core将控制权移交给PEI core之前,它会被调用。
平台开发人员可以修改传递给PEI核心的SecCoreData。
它返回一个平台特定的PPI列表,平台希望传递给PEI core。
通用的SEC core模块将合并该名单,加入到传递给PEI core的最终名单中。
@param[in,out] SecCoreData 与传递给PEI core的参数相同。它可以被这个函数覆盖。
@return 平台特定的PPI列表将被传递给PEI core,如果不需要这种平台特定的PPI列表,则为NULL。
**/
EFI_PEI_PPI_DESCRIPTOR *
EFIAPI
SecPlatformMain (
IN OUT EFI_SEC_PEI_HAND_OFF *SecCoreData
)
{
EFI_PEI_PPI_DESCRIPTOR *PpiList;
DEBUG((DEBUG_INFO, "SecPlatformMain\n"));
DEBUG((DEBUG_INFO, "BootFirmwareVolumeBase - 0x%x\n", SecCoreData->BootFirmwareVolumeBase));
DEBUG((DEBUG_INFO, "BootFirmwareVolumeSize - 0x%x\n", SecCoreData->BootFirmwareVolumeSize));
DEBUG((DEBUG_INFO, "TemporaryRamBase - 0x%x\n", SecCoreData->TemporaryRamBase));
DEBUG((DEBUG_INFO, "TemporaryRamSize - 0x%x\n", SecCoreData->TemporaryRamSize));
DEBUG((DEBUG_INFO, "PeiTemporaryRamBase - 0x%x\n", SecCoreData->PeiTemporaryRamBase));
DEBUG((DEBUG_INFO, "PeiTemporaryRamSize - 0x%x\n", SecCoreData->PeiTemporaryRamSize));
DEBUG((DEBUG_INFO, "StackBase - 0x%x\n", SecCoreData->StackBase));
DEBUG((DEBUG_INFO, "StackSize - 0x%x\n", SecCoreData->StackSize));
// 初始化本地APIC定时器。
// 初始化并使能本地APIC定时器。
InitializeApicTimer (0, (UINT32) -1, TRUE, 5);
//
// 使用堆中间作为临时缓冲区,它将被调用者复制。
// 不要使用Stack,因为它会导致PeiCore对Stack的计算错误
//
PpiList = (VOID *)((UINTN)SecCoreData->PeiTemporaryRamBase + (UINTN)SecCoreData->PeiTemporaryRamSize/2);
CopyMem (PpiList, mPeiSecPlatformPpi, sizeof(mPeiSecPlatformPpi));
//
// TopOfTemporaryRamPpi补丁
//
PpiList[0].Ppi = (VOID *)((UINTN)SecCoreData->TemporaryRamBase + SecCoreData->TemporaryRamSize);
return PpiList;
}
子函数说明之FindAndReportEntryPoints()
/**
找到并返回PEI核心入口点。
它还可以查找SEC和PEI核心文件的调试信息。如果启用了远程调试,它将报告它们。
@param BootFirmwareVolumePtr 指向BFV。
@param PeiCoreEntryPoint PEI核心的入口.
**/
VOID
EFIAPI
FindAndReportEntryPoints (
IN EFI_FIRMWARE_VOLUME_HEADER *BootFirmwareVolumePtr,
OUT EFI_PEI_CORE_ENTRY_POINT *PeiCoreEntryPoint
)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS SecCoreImageBase;
EFI_PHYSICAL_ADDRESS PeiCoreImageBase;
PE_COFF_LOADER_IMAGE_CONTEXT ImageContext;
//
// 查找SEC核心和PEI核心Image base
//
Status = FindImageBase (BootFirmwareVolumePtr, &SecCoreImageBase, &PeiCoreImageBase);
ASSERT_EFI_ERROR (Status);
ZeroMem ((VOID *) &ImageContext, sizeof (PE_COFF_LOADER_IMAGE_CONTEXT));
//
// 当启用远程调试时,报告SEC核心调试信息
//
ImageContext.ImageAddress = SecCoreImageBase;
ImageContext.PdbPointer = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageContext.ImageAddress);
PeCoffLoaderRelocateImageExtraAction (&ImageContext);
//
// 当开启远程调试时,上报PEI核心调试信息
//
ImageContext.ImageAddress = PeiCoreImageBase;
ImageContext.PdbPointer = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageContext.ImageAddress);
PeCoffLoaderRelocateImageExtraAction (&ImageContext);
//
// 找到PEI核心入口点
//
Status = PeCoffLoaderGetEntryPoint ((VOID *) (UINTN) PeiCoreImageBase, (VOID**) PeiCoreEntryPoint);
if (EFI_ERROR (Status)) {
*PeiCoreEntryPoint = 0;
}
return;
}
PeiCoreEntryPoin这个值就是一个64位的虚拟地址,这个地址是Pei阶段的Entry point函数的入口地址,然后通过(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable); 这段代码跳转到Pei阶段。函数位于MdePkg/Library/PeiCoreEntryPoint/PeiCoreEntryPoint.c:
/**
PE/COFF图像的入口点为PEI核心。
这个函数是PEI Foundation的入口点,它允许SEC阶段传递关于堆栈、临时RAM和引导固件卷的信息。
此外,它还允许SEC阶段以一个或多个ppi的形式传递服务和数据,以供PEI阶段使用。
从SEC传递到PEI Foundation的额外PPIs数量没有限制。作为初始化阶段的一部分,
PEI Foundation将把这些sec托管的PPIs添加到其PPI数据库中,
这样PEI Foundation和任何模块都可以利用这些早期PPIs中的相关服务调用and/or代码。
这个函数需要调用ProcessModuleEntryPointList(),并将上下文参数设置为NULL。ProcessModuleEntryPoint()永远不会返回。
PEI核心负责在PEI服务表和PEI核心本身的文件句柄建立之后调用ProcessLibraryConstructorList()。
如果ProcessModuleEntryPointList()返回,则ASSERT()并停止系统。
@param SecCoreData 指向一个包含PEI核心操作环境信息的数据结构,
例如临时RAM的大小和位置,堆栈位置和BFV位置
@param PpiList 指向PEI核心最初要安装的一个或多个PPI描述符的列表。
空的PPI列表由单个描述符组成,其结束标记为EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST。
作为初始化阶段的一部分,PEI Foundation将把这些sec托管的PPIs添加到其PPI数据库中,
这样PEI Foundation和任何模块都可以利用这些早期PPIs中的相关服务调用和/或代码。
**/
VOID
EFIAPI
_ModuleEntryPoint(
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreData,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList
)
{
ProcessModuleEntryPointList (SecCoreData, PpiList, NULL);
//
// Should never return
//
ASSERT(FALSE);
CpuDeadLoop ();
}
这个函数又调用ProcessModuleEntryPointList (SecCoreData, PpiList, NULL);函数,这个函数位于AutoGen.c中,这个文件是脚本生成的,在这个函数中调用了PeiCore入口函数,这样就连接了PEI阶段。
VOID
EFIAPI
ProcessModuleEntryPointList (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreData,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList,
IN VOID *Context
)
{
PeiCore (SecCoreData, PpiList, Context);
}