SEC(Security Phase)- 安全阶段

一、SEC阶段主要功能

SEC阶段是平台初始话的第一个阶段,计算机系统加电后首先进入这个阶段。

SEC阶段的功能:UEFI系统开机或重启后首先进入SEC阶段,SEC阶段系统执行以下四种任务:

  1. 接收并处理系统启动和重启信号,系统加电信号、系统重启信号、系统运行过程中的异常信号。
  2. 初始化临时存储区域:系统运行在SEC阶段时,仅CPU和CPU内部资源被初始化,而各种外部设备和内存都没有被初始化。因此系统需要一部分临时内存用于代码和数据的存储,一般称为临时RAM,临时RAM只能位于CPU内部(CPU和CPU内部的资源最先被初始化)。最常用的临时RAM是Cache,通过将Cache设置为no-eviction模式(noeviction:不删除策略,不淘汰,如果内存已满,添加数据是报错的),来把其当成内存使用(此时读取命中则返回Cache中的数据,读取缺失并不会向主存发出缺失事件;写命中时写入Cache,写缺失时也不会向主存发出缺失事件),这种技术称为CAR(Cache As RAM)。
  3. SEC阶段是可信系统的根:作为系统启动的第一部分,只有SEC能被系统信任,以后的各个阶段才有被信任的基础。因此,大部分情况下SEC再转交控制权给PEI前可以验证PEI是否可信。
  4. 传递系统参数给下一阶段:SEC阶段的一切工作都是为PEI阶段做准备的,最重要把系统的控制权转交给PEI,并将SEC阶段的运行信息汇报给PEI。SEC通过将以下信息作为参数传递给PEI的入口程序来向PEI汇报信息:
    • 系统当前状态,PEI根据状态值判断系统当前的健康情况。
    • 可启动固件(Boot Firmware Volume)的地址和大小,PEI据此判断可用硬件。
    • 临时RAM区域的地址和大小。
    • 栈的地址和大小。

二、SEC阶段执行流程

根据临时RAM是否初始化为界限,SEC阶段分为两部分:临时RAM初始化前称为Reset Vector阶段;临时RAM初始化后调用SEC入口函数从而进入SEC功能区。我们简单理下从上电到SEC阶段的大致流程:

第一条指令所在位置被称为 Reset Vector,之后进入Reset Vector阶段。

SEC代码流程图如下

其中BP启动AP参考

2.1 Reset Vector

Reset Vector的执行流程如下:

  1. 进入固件入口。
  2. 从实模式转换到32位平坦模式(包含模式)。
  3. 定位固件中的BFV(Boot Firmware Volume)。
  4. 定位BFV中的SEC映像。
  5. 如果是64位系统,则从32位模式转换至64位模式。
  6. 调用SEC入口函数。

resetVector

追踪下代码执行流程,复位之后第一条指令位置对应:
UefiCpuPkg\ResetVector\Vtf0\Ia16\ResetVectorVtf0.asmresetVector位置

BITS    16 ;该伪指令说明程序是以16位的方式运行ALIGN   16 ;按照 16 个字节的倍数对齐下一个符号,空隙默认用0 来填充resetVector:
;
; Reset Vector
;
; This is where the processor will begin execution
;nopnopjmp     EarlyBspInitReal16

通过jmp跳转到 UefiCpuPkg\ResetVector\Vtf0\Ia16\Init16.asmEarlyBspInitReal16位置

EarlyBspInitReal16

; DI(destination index)是目的变址寄存器,用做隐含的目的串地址,默认在ES中;ES叫做额外的段寄存器. 它通常跟DI一起用来做指针使用.
; BP是基指针, 通常BP用来保存使用局部变量的地址.
EarlyBspInitReal16:mov     di, 'BP'jmp     short Main16

可以看出,最开始指令是两个啥也不干的NOP和一个短跳转,那么:
1. 为什么开局是CPU不做操作(有时候作为占位符和微调timing用)的NOP,而不是直接有用的JMP开局?

关于为什么是NOP在IA32手册有说明

简单来说就是内核可以顺利执行NOP的变成BSP;而其他的变成AP,被放在wait-for-SIPI的状态,等待BSP调度。

2. 为啥一个NOP不够,而要两个?

第二个NOP是占位符,为了让随后的JMP可以16位地址对齐,这样性能高一些。

3. JMP后面的占位符是谁Fixed UP的?

这里实际是要跳转到UEFI的SEC Core中去,但在写代码时候是不知道SEC Core在Flash哪里的,所以这里仅仅占位两个字节。其后,UEFI的BaseTools在产生FV的时候会找到SEC Core的入口,然后填写一个相对地址在这里。具体在Edk2\BaseTools\Source\C\GenFv\GenFvInternalLib.c

//
// Write SecCore Entry point relative address into the jmp instruction in reset vector.
//Ia32ResetAddressPtr  = (UINT32 *) ((UINTN) FvImage->Eof - IA32_SEC_CORE_ENTRY_OFFSET);Ia32SecEntryOffset   = (INT32) (SecCorePhysicalAddress - (FV_IMAGES_TOP_ADDRESS - IA32_SEC_CORE_ENTRY_OFFSET + 2));if (Ia32SecEntryOffset <= -65536) {Error (NULL, 0, 3000, "Invalid", "The SEC EXE file size is too large, it must be less than 64K.");return STATUS_ERROR;}

Main16

通过jmp跳转到UefiCpuPkg\ResetVector\Vtf0\Main.asm下的Main16位置

BITS    16; Modified:  EBX, ECX, EDX, EBP
;
; @param[in,out]  RAX/EAX  Initial value of the EAX register
;                          (BIST: Built-in Self Test)
; @param[in,out]  DI       'BP': boot-strap processor, or
;                          'AP': application processor
; @param[out]     RBP/EBP  Address of Boot Firmware Volume (BFV)
; @param[out]     DS       Selector allowing flat access to all addresses
; @param[out]     ES       Selector allowing flat access to all addresses
; @param[out]     FS       Selector allowing flat access to all addresses
; @param[out]     GS       Selector allowing flat access to all addresses
; @param[out]     SS       Selector allowing flat access to all addresses
;
; @return         None  This routine jumps to SEC and does not returnMain16:; ESP - EAX 寄存器的初始值(BIST:内置自检); OneTimeCall是宏OneTimeCall EarlyInit16; 从实模式转换到32位平坦模式; 平坦模式:直接用一个地址寄存器来线性访问4G的内存.32位的CPU最多可以寻址4GB的内存空间,如果物理内存大于4GB,超出的部分CPU是无法寻址到的。; 保护模式: 在这种状态下, 一切程序都可以用线性地址(不分段)访问自己所拥有的4G的内存空间, 但是不能访问其他程序的空间. ; 实模式:寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。OneTimeCall TransitionFromReal16To32BitFlat BITS    32; Search for the Boot Firmware Volume (BFV);定位固件中的 BFV; FV:固件卷,指在FD上一个连续的部分,我们可以把它看成一个逻辑设备,因为我们代码真正操作的是FV,而非FD。; FFS的概念也是以FV的形式存在,它描述了FV中的文件组织方式。FV之于FD,类似于thread之于package。; OneTimeCall Flat32SearchForBfvBase  ; 搜索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; Jump to the 32-bit SEC entry point; 跳转到 32 位 SEC 入口点jmp     esi%else; Transition the processor from 32-bit flat mode to 64-bit flat mode; 从32位模式转换为64位模式OneTimeCall Transition32FlatTo64FlatBITS    64; Some values were calculated in 32-bit mode.  Make sure the upper; 32-bits of 64-bit registers are zero for these values.; 一些值是在 32 位模式下计算的。 对于这些值,32 位 64 位寄存器的上高32位为零。mov     rax, 0x00000000ffffffffand     rsi, raxand     rbp, raxand     rsp, rax; RSI - SEC Core entry point; RBP - Start of BFV; RSI - SEC 核心入口点,RBP - BFV 的开始; Restore initial EAX value into the RAX register; 将初始 EAX 值恢复到 RAX 寄存器中mov     rax, rsp; Jump to the 64-bit SEC entry point; 跳转到 64 位 SEC 入口点jmp     rsi%endif

在Reset Vector部分,因为系统还没有RAM,因而不能使用基于栈的程序设计,所有函数调用都使用 jmp 指令模拟。OneTimeCall是宏,用于模拟call指令。例:

OneTimeCall Flat32SearchForBfvBase 怎么定位 BFV ?

BFV其实就是uefi image的起点,这里就是BFV,结构可以参考uefi手册3.2.1章节
edk2\BaseTools\Source\C\Include\Common\PiFirmwareVolume.h

typedef struct {UINT32 NumBlocks;UINT32 Length;
} EFI_FV_BLOCK_MAP_ENTRY;//
// Describes the features and layout of the firmware volume.
//
typedef struct {UINT8                     ZeroVector[16];EFI_GUID                  FileSystemGuid;UINT64                    FvLength;UINT32                    Signature;EFI_FVB_ATTRIBUTES_2      Attributes;UINT16                    HeaderLength;UINT16                    Checksum;UINT16                    ExtHeaderOffset;UINT8                     Reserved[1];UINT8                     Revision;EFI_FV_BLOCK_MAP_ENTRY    BlockMap[1];
} EFI_FIRMWARE_VOLUME_HEADER;#define EFI_FVH_SIGNATURE SIGNATURE_32 ('_', 'F', 'V', 'H')


最后ebp寄存器存放了BFV的起始地址

OneTimeCall Flat32SearchForSecEntryPoint 怎么定位 BFV 的 SEC

最后esi 寄存器存放了SEC 的入口地址

2.2 SEC Core

2.2.1 SEC Core功能分析

进入SEC功能区后,首先利用 CAR 技术初始化栈,初始化 IDT,初始化EFI_SEC_PEI_HAND_OFF,将控制权转交给PEI,并将 EFI_SEC_PEI_HAND_OFF 传递给PEI。

  1. CAR (Cache ASRAM),在Cashe上开辟一段空间作为内存使用(此时内存尚未初始化,相关C语言运行需要内存和栈的空间) ;
  2. IDT ( Interrupt Descriptor Table ) 中断描述表,,记录了0~255的中断号和调用函数之间的关系。结构体如下所述:
_SEC_IDT_TABLE

edk2\UefiCpuPkg\SecCore\SecMain.h

#define SEC_IDT_ENTRY_COUNT  34typedef 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实际上就是一个结构体,是UEFI当中在SEC阶段最重要的一个数据结构,将环境从以汇编语言执行转向C语言执行。
edk2\MdePkg\Include\Pi\PiPeiCis.h

/// EFI_SEC_PEI_HAND_OFF 结构包含有关PEI核心的运行环境,如位置大小、临时 RAM、堆栈位置和 BFV 位置。
typedef struct _EFI_SEC_PEI_HAND_OFF {UINT16  DataSize;  //数据结构的大小。VOID    *BootFirmwareVolumeBase; //指向BFV的第一个字节,PEI Dispatcher应该搜索PEI模块UINTN   BootFirmwareVolumeSize;  //BFV的大小,以字节为单位。VOID    *TemporaryRamBase;  //指向临时RAM的第一个字节。UINTN   TemporaryRamSize;  //临时RAM的大小,以字节为单位/// 指向PEI可以使用的临时RAM的第一个字节。/// PeiTemporaryRamBase 和 PeiTemporaryRamSize 描述的区域不得超出 TemporaryRamBase & TemporaryRamSize 描述的区域之外。/// 这个区域不应该与StackBase和StackSize返回的区域重叠。VOID    *PeiTemporaryRamBase;UINTN   PeiTemporaryRamSize;  //PEI使用的可用临时RAM的大小,以字节为单位。VOID    *StackBase;  //指向堆栈的第一个字节。这可能是由TemporaryRamBase和TemporaryRamSize描述的内存的一部分,也可能是一个完全独立的区域。UINTN   StackSize;  //堆栈的大小,以字节为单位。
} EFI_SEC_PEI_HAND_OFF;

2.2.1 SEC Core 执行实例

OVMF实例

不同硬件平台,SEC代码会有不同实现方式,但大致过程相似。下面以OVMF为例,介绍SEC功能区执行过程。

edk2\OvmfPkg\Sec\X64\SecEntry.nasm

extern ASM_PFX(SecCoreStartupWithStack); SecCore 入口点global ASM_PFX(_ModuleEntryPoint)
ASM_PFX(_ModuleEntryPoint):; 临时RAM已经初始化,设置栈地址,PcdOvmfSecPeiTempRamBase和PcdOvmfSecPeiTempRamSize在OvmfPkgIa32X64.fdf; 用初始堆栈值填充临时 RAM。mov     rax, (FixedPcdGet32 (PcdInitValueInTempStack) << 32) | FixedPcdGet32 (PcdInitValueInTempStack)mov     rdi, FixedPcdGet32 (PcdOvmfSecPeiTempRamBase)     ; 相对于ES, qword来存储基址mov     rcx, FixedPcdGet32 (PcdOvmfSecPeiTempRamSize) / 8 ; qword从基地计数存储cld                                                                                                       rep stosq; 基于 PCD 加载临时 RAM 堆栈%define SEC_TOP_OF_STACK (FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) + \FixedPcdGet32 (PcdOvmfSecPeiTempRamSize))mov     rsp, SEC_TOP_OF_STACKnop;   rcx: BootFirmwareVolumePtr  BFV首地址;   rdx: TopOfCurrentStack  栈起始地址;  设置参数并调用 SecCoreStartupWithStack,mov     rcx, rbp    ; BFV 首地址,rbp 为传参数mov     rdx, rsp    ; 栈起始地址sub     rsp, 0x20call    ASM_PFX(SecCoreStartupWithStack)  ;此时栈已可用,故可使用 call 指令
Sec入口函数

根据最后的call指令找到跳转到SecCoreStartupWithStack函数,在edk2\OvmfPkg\Sec\SecMain.c中的SecCoreStartupWithStack函数

edk2\MdePkg\Include\Library\BaseLib.h

#pragma pack (1)
typedef struct {UINT16  Limit;UINTN   Base;
} IA32_DESCRIPTOR;
#pragma pack ()

edk2\OvmfPkg\Sec\SecMain.c

  1. 初始化浮点寄存器
  2. 初始化 IDT
  3. 初始化 SecCoreData, 将临时 RAM 地址、栈地址、BFV地址赋值给 SecCoreData
VOID
EFIAPI
SecCoreStartupWithStack (IN EFI_FIRMWARE_VOLUME_HEADER       *BootFv,IN VOID                             *TopOfCurrentStack)
{EFI_SEC_PEI_HAND_OFF        SecCoreData;SEC_IDT_TABLE               IdtTableInStack;IA32_DESCRIPTOR             IdtDescriptor;UINT32                      Index;volatile UINT8              *Table;// 为确保 SMM 在 S3 恢复时不会受到损害,我们必须强制重新初始化 BaseExtractGuidedSectionLib。 // 由于这是在调用库构造函数之前,我们必须使用循环而不是 SetMem。Table = (UINT8*)(UINTN)FixedPcdGet64 (PcdGuidedExtractHandlerTableAddress);for (Index = 0;Index < FixedPcdGet32 (PcdGuidedExtractHandlerTableSize);++Index) {Table[Index] = 0;}// 初始化 IDT - 由于这是在调用库构造函数之前,我们使用循环而不是 CopyMem。IdtTableInStack.PeiService = NULL;for (Index = 0; Index < SEC_IDT_ENTRY_COUNT; Index ++) {UINT8  *Src;UINT8  *Dst;UINTN  Byte;Src = (UINT8 *) &mIdtEntryTemplate;Dst = (UINT8 *) &IdtTableInStack.IdtTable[Index];for (Byte = 0; Byte < sizeof (mIdtEntryTemplate); Byte++) {Dst[Byte] = Src[Byte];}}IdtDescriptor.Base  = (UINTN)&IdtTableInStack.IdtTable;IdtDescriptor.Limit = (UINT16)(sizeof (IdtTableInStack.IdtTable) - 1);if (SevEsIsEnabled ()) {SevEsProtocolCheck ();// 目前是运行在flash芯片里的,内存此时还用不了。// InitializeCpuExceptioInHandlers就是把idt entry里的所有handler赋值为CommonInterruptEntry//定义在UefiCpuPkg\Library\CpuExceptionHandlerLib\Ia32\ExceptionHandlerAsm.nasm。    AsmWriteIdtr (&IdtDescriptor);InitializeCpuExceptionHandlers (NULL);}ProcessLibraryConstructorList (NULL, NULL);if (!SevEsIsEnabled ()) {// 对于非 SEV-ES guests,只需加载 IDTR。AsmWriteIdtr (&IdtDescriptor);} else {// 在 SEV-ES 下,管理程序无法修改 CR0,因此无法启用缓存以加快启动速度。 // 尽早为 SEV-ES guest启用缓存。AsmEnableCache ();}DEBUG ((DEBUG_INFO,"SecCoreStartupWithStack(0x%x, 0x%x)\n",(UINT32)(UINTN)BootFv,(UINT32)(UINTN)TopOfCurrentStack));// 初始化浮点操作环境以符合 UEFI 规范。InitializeFloatingPointUnits ();#if defined (MDE_CPU_X64)// 断言页表由复位向量代码设置为我们期望的地址。ASSERT (AsmReadCr3 () == (UINTN) PcdGet32 (PcdOvmfSecPageTablesBase));
#endif//// |-------------|       <-- TopOfCurrentStack// |   Stack     | 32k// |-------------|// |    Heap     | 32k// |-------------|       <-- SecCoreData.TemporaryRamBase//ASSERT ((UINTN) (PcdGet32 (PcdOvmfSecPeiTempRamBase) +PcdGet32 (PcdOvmfSecPeiTempRamSize)) ==(UINTN) TopOfCurrentStack);// 初始化 SECOND 切换状态SecCoreData.DataSize = sizeof(EFI_SEC_PEI_HAND_OFF);SecCoreData.TemporaryRamSize       = (UINTN) PcdGet32 (PcdOvmfSecPeiTempRamSize);SecCoreData.TemporaryRamBase       = (VOID*)((UINT8 *)TopOfCurrentStack - SecCoreData.TemporaryRamSize);SecCoreData.PeiTemporaryRamBase    = SecCoreData.TemporaryRamBase;SecCoreData.PeiTemporaryRamSize    = SecCoreData.TemporaryRamSize >> 1;SecCoreData.StackBase              = (UINT8 *)SecCoreData.TemporaryRamBase + SecCoreData.PeiTemporaryRamSize;SecCoreData.StackSize              = SecCoreData.TemporaryRamSize >> 1;SecCoreData.BootFirmwareVolumeBase = BootFv;SecCoreData.BootFirmwareVolumeSize = (UINTN) BootFv->FvLength;// 确保在初始化调试代理之前屏蔽 8259 并启用调试计时器IoWrite8 (0x21, 0xff);IoWrite8 (0xA1, 0xff);// 在初始化调试代理和启用调试定时器之前,初始化本地 APIC 定时器硬件并禁用本地 APIC 定时器中断。InitializeApicTimer (0, MAX_UINT32, TRUE, 5);DisableApicTimerInterrupt ();// 在内存准备好之前,初始化调试代理以支持 SEC/PEI 阶段中的源代码级调试。InitializeDebugAgent (DEBUG_AGENT_INIT_PREMEM_SEC, &SecCoreData, SecStartupPhase2);
}
IA32实例

上述是 OVMF 平台的入口函数相关内容,我们再看下 IA32 平台的入口函数相关内容,在 IA32 平台入口函数是 SecStartup

  ;; Pass Control into the PEI Core;call ASM_PFX(SecStartup)
/**
SEC的C语言阶段的入口点。SEC汇编代码初始化一些临时内存并建立堆栈后,控制被转移到这个函数。@param SizeOfRam           可用的临时内存的大小。@param TempRamBase         临时内存的基址@param BootFirmwareVolume  BFV的基本地址。
**/
VOID
NORETURN
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);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//  初始化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地址赋值给SecCoreDataSecCoreData.DataSize               = (UINT16) sizeof (EFI_SEC_PEI_HAND_OFF);SecCoreData.BootFirmwareVolumeBase = BootFirmwareVolume;SecCoreData.BootFirmwareVolumeSize = (UINTN)((EFI_FIRMWARE_VOLUME_HEADER *) BootFirmwareVolume)->FvLength;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);// Should not come here.UNREACHABLE ();
}
参与子函数 InitializeDebugAgent
/**初始化调试代理。此函数用于设置调试环境,以支持源代码级的调试。如果某些调试代理库实例有一些私人数据保存在堆栈中,这个函数必须在此模式工作不返回给调用者,然后调用者需要结束后所有其他逻辑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 wasoptional.
**/
VOID
EFIAPI
InitializeDebugAgent (IN UINT32                InitFlag,IN VOID                  *Context, OPTIONALIN DEBUG_AGENT_CONTINUE  Function  OPTIONAL);

2.3 跳转到PEI 入口地址

SecStartupPhase2()-> PeiCore ()

SecStartup 或者 SecStartupPhase2 最后会调用:
InitializeDebugAgent (DEBUG_AGENT_INIT_PREMEM_SEC, &SecCoreData, SecStartupPhase2),
SecStartupPhase2 最后会调用 PEI 入口函数 :
(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);
以 OVMF为例,PATH:edk2\OvmfPkg\Sec\SecMain.c

EFI_PEI_PPI_DESCRIPTOR mPrivateDispatchTable[] = {{(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),&gEfiTemporaryRamSupportPpiGuid,&mTemporaryRamSupportPpi},
};/**调用方提供了在 InitializeDebugAgent() 结束时调用的函数。SEC 的 C 语言阶段的入口点。 SEC 大会后代码已经初始化了一些临时内存并设置了堆栈,控制权转移到此功能。@param[in] Context InitializeDebugAgent() 的第一个输入参数。**/
VOID
EFIAPI
SecStartupPhase2(IN VOID                     *Context)
{EFI_SEC_PEI_HAND_OFF        *SecCoreData;EFI_FIRMWARE_VOLUME_HEADER  *BootFv;EFI_PEI_CORE_ENTRY_POINT    PeiCoreEntryPoint;SecCoreData = (EFI_SEC_PEI_HAND_OFF *) Context;// 找到 PEI Core 入口点。 如果启用远程调试,它将报告 SEC 和 Pei Core 调试信息。BootFv = (EFI_FIRMWARE_VOLUME_HEADER *)SecCoreData->BootFirmwareVolumeBase;FindAndReportEntryPoints (&BootFv, &PeiCoreEntryPoint);SecCoreData->BootFirmwareVolumeBase = BootFv;SecCoreData->BootFirmwareVolumeSize = (UINTN) BootFv->FvLength;// 将控制权转移到 PEI 核心(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);// 如果我们到达这里,则 PEI 核心返回,这是不可恢复的。ASSERT (FALSE);CpuDeadLoop ();
}

最后传入EFI_SEC_PEI_HAND_OFF(前面已写) 和 EFI_PEI_PPI_DESCRIPTOR这个类型指针到pei阶段

// PEI Ppi 服务列表描述符
#define EFI_PEI_PPI_DESCRIPTOR_PIC              0x00000001
#define EFI_PEI_PPI_DESCRIPTOR_PPI              0x00000010
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK  0x00000020
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH  0x00000040
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_TYPES     0x00000060
#define EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST   0x80000000//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;

FindAndReportEntryPoints()

/*找到并返回PEI核心入口点。它还可以查找SEC和PEI核心文件的调试信息。如果启用了远程调试,它将报告它们。@param   BootFirmwareVolumePtr    指向BFV。@param   PeiCoreEntryPoint        PEI核心的入口.
**/
VOID
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 baseStatus = FindImageBase (*BootFirmwareVolumePtr, &SecCoreImageBase);ASSERT_EFI_ERROR (Status);FindPeiCoreImageBase (BootFirmwareVolumePtr, &PeiCoreImageBase);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 = (EFI_PHYSICAL_ADDRESS)(UINTN)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;
}

PE部分参考:PE详解

PeiCoreEntryPoin这个值就是一个64位的虚拟地址,这个地址是Pei阶段的Entry point函数的入口地址,然后通过(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable); 跳转到Pei阶段。

2.4 PEI 入口函数

UEFI最重要的特点就是模块化设计,模块载入内存生成Image。Image的入口函数是_ModuleEntryPoint。而PEI也是一个模块,PEI入口函数在:
edk2\MdePkg\Library\PeiCoreEntryPoint\PeiCoreEntryPoint.c
_ModuleEntryPoint

/**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 ();
}

build ovmf (参考UEFI开发环境的 使用qemu虚拟机调试OvmfPkg部分) 找到其 ProcessModuleEntryPointList , 位于:edk2\Build\OvmfX64\RELEASE_VS2019\X64\MdeModulePkg\Core\Pei\PeiMain\DEBUG\AutoGen.c

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);
}

最终调用PEI入口函数 PeiCore ,其位于
edk2\MdeModulePkg\Core\Pei\PeiMain\PeiMain.c

/**该例程在转换过程中由 PeiMain 模块的主入口调用从 SEC 到 PEI。在PEI核心切换堆栈后,会重启与旧的核心数据。@param SecCoreDataPtr  指向包含有关 PEI 核心的操作信息的数据结构环境,例如临时 RAM 的大小和位置、堆栈位置和BFV 位置。@param PpiList         指向由 PEI 核心最初安装的一个或多个 PPI 描述符的列表。一个空的 PPI 列表由一个带有结束标签的描述符组成EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST。 作为其初始化的一部分阶段,PEI 基金会会将这些 SEC 托管的 PPI 添加到其 PPI 数据库中,例如PEI 基金会和任何模块都可以利用相关服务这些早期 PPI 中的调用和/或代码@param Data            指向旧核心数据的指针,用于初始化核心的数据区域。如果为NULL,则首先进入PeiCore。**/
VOID
EFIAPI
PeiCore (IN CONST EFI_SEC_PEI_HAND_OFF        *SecCoreDataPtr,IN CONST EFI_PEI_PPI_DESCRIPTOR      *PpiList,IN VOID                              *Data);

本文主要来源:

  1. EDKII
  2. 《UEFI原理与编程》
  3. UEFI社区
  4. 《Intel® 64 and IA-32 Architectures Software Developer’s Manual》

UEFI BIOS —— SEC阶段分析相关推荐

  1. UEFI BIOS —— PEI阶段分析

    PEI(Pre-EFI Initialization) -预先EFI初始化)阶段 一.PEI 主要功能 PEI阶段资源依然十分有限,内存到了PEI后期才被初始化.其主要功能是为DXE准备执行环境,将需 ...

  2. UEFI BIOS —— 开机上电阶段分析

    当我们按下电源键后,CPU就开始reset vecto执行BIOS程序了吗? 按下电源键,在CPU执行代码之前,由上电时序工作. 据了解服务器或者部分台式机的上电时序都是由cpld实现,笔电则由EC实 ...

  3. UEFI BIOS和 传统BIOS 启动模式对比

    传统BIOS开机流程 从你按下主机机壳上的电源键,到进入作业系统的期间,储存於主机板上那颗EEPROM(电气可抹除暨可程式化唯读记忆体)裡的BIOS便会开始执行以下的工作: 1. 初始化: 当电脑打开 ...

  4. 操作系统安装必备基础知识----浅谈电脑系统里的那些UEFI, BIOS, MBR, GPT。

    操作系统安装也是一门简单学问,要想真正搞懂,还是需要一点基础知识做铺垫.前两天耍手机看到了这篇关于装机的这些基础理论知识,总结的还是不错的.所以拿来既是自己收藏也是分享看我博客的人.之后再抽出时间写一 ...

  5. 【转】重新打包DebianISO实现无人应答安装(UEFI+BIOS)

    转自:重新打包DebianISO实现无人应答安装(UEFI+BIOS) - 全部 - 真不是你的 之前我写过打包DebianISO的文章,但是那种打包的方法只能用在引导是BIOS的机器上,按照正常的情 ...

  6. 服务器bios修改uefi,服务器 uefi bios设置

    服务器 uefi bios设置 内容精选 换一换 对于不同的硬件设备,通过在BIOS中设置一些高级选项,可以有效提升服务器性能.服务器上的SMMU一般用来完成设备的地址转换,并且可以实现设备隔离,在虚 ...

  7. linux bios 禁用usb设备,当USB在UEFI / BIOS中工作时,为什么USB在Linux中不工作?

    作为背景,我刚刚用现代硬件制造了一台新机器,包括: AMD FX-8350 技嘉GA-990FXA-UD3主板 16GB RAM 英伟达GTX 650 Ti 金士顿固态硬盘 鉴于此,我尝试在SSD上安 ...

  8. 计算机取消uefi启动项,如何使用老毛桃winpe删除或添加UEFI BIOS启动项?

    说到电脑开机的过程,就不得不提及启动项的概念了.电脑在启动的时候需要通过UEFI BIOS启动项来启动整个电脑.而启动项的设置,一般需要进入BIOS界面操作.而最近有位朋友想要删除电脑的其中一个开机首 ...

  9. 惠普服务器装系统无法识别u盘,惠普uefi bios无法识别u盘的解决方法

    3.如何解决惠普电脑uefi识别不了u盘 2.进入bios后,切换到system configuration选项卡,在按"↓"键移动到boot options并按回车键,如图所示: ...

最新文章

  1. 今天你(L)China了吗?
  2. vue-cli3.0 移动端适配
  3. python答案2019版_程序设计语言Python_2019答案章节答案期末答案
  4. 利用WCF的双工通讯实现一个简单的心跳监控系统
  5. Redis(九):Redis特殊类型之geospatial
  6. 作者:朱扬勇,博士,复旦大学计算机科学技术学院教授、学术委员会主任,上海市数据科学重点实验室主任。...
  7. 设计模式09_代理模式
  8. RxJava操作符serialize 笔记-二十六
  9. Introduction to Computer Networking学习笔记(十五):End to End Delay 端对端延迟
  10. 微信开发源代码详细分析-微信开发教程6
  11. 专业PE优盘启动制作工具 - 优启通
  12. npm/cnpm install 报错 platform unsupported
  13. python课设参考文献_Python课程设计任务书
  14. postgresql点云las_三维点云目标提取总结【转】
  15. 前端笔记 -- 不重复造轮子(遇到就更新内容)
  16. Java - constants
  17. Linux下的Chm文件阅读器
  18. java万年历的设计总结_java万年历设计报告
  19. 当今世界最牛的25位顶尖大数据科学家
  20. QT状态栏(statusbar)用法

热门文章

  1. 用手机快速制作真人手办模型
  2. php文字左右滚动通告,微信小程序左右滚动公告栏效果代码实例
  3. (收藏必备)git clone命令下载github资源速度慢解决方法
  4. 去掉SXS.DLL的输出日志
  5. jvm工具系列之 -- jmap
  6. 软件测试中的“银行信贷项目“讲解
  7. springSecurity重定义Bad credentials信息
  8. 民俗解读:初八“放生”
  9. WordPress短信宝短信插件
  10. 时隔近10年,让我们再来看3Q大战