Step to UEFI (238)OVMF 从第一条指令到 SecMain

最近抽空研究了一下 OVMF 的代码。它是为 QEMU虚拟机设计的UEFI BIOS内置在 EDK2 的代码,对于研究 UEFI EDK2架构非常有意义。分析代码是非常枯燥的事情,相信读者也会有这样的感觉。但是“所有的答案都在代码中”,通过这样的过程能够让我们对EDK2有着更深入的理解。

这次从“上电”开始,就是下图的最左侧 “SEC”阶段,研究一下 OVMF 是如何运行的。

图片来自【参考1】

使用的代码是edk202108,可以在【参考2】看到介绍。

首先使用 build -a X64 -p OvmfPkg\OvmfPkgX64.dsc 编译生成 ovmf.fd ,之后使用下面的命令启动 QEMU:

qemu-system-x86_64 -bios "ovmf.fd" -debugcon file:debug.log -global isa-debugcon.iobase=0x402

运行之后,即可在QEMU的目录下看到 debug.log,其中的前几条记录如下:

SecCoreStartupWithStack(0xFFFCC000, 0x820000)
Register PPI Notify: DCD0BE23-9586-40F4-B643-06522CED4EDE
Install PPI: 8C8CE578-8A3D-4F1C-9935-896185C32DD3
Install PPI: 5473C07A-3DCB-4DCA-BD6F-1E9689E7349A
The 0th FV start address is 0x00000820000, size is 0x000E0000, handle is 0x820000
Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Register PPI Notify: EA7CA24B-DED5-4DAD-A389-BF827E8F9B38
Install PPI: B9E0ABFE-5979-4914-977F-6DEE78C278A6

很容易可以在 \OvmfPkg\Sec\SecMain.c 的SecCoreStartupWithStack()函数中找到下面的语句:

  DEBUG ((DEBUG_INFO,
    "SecCoreStartupWithStack(0x%x, 0x%x)\n",
    (UINT32)(UINTN)BootFv,
    (UINT32)(UINTN)TopOfCurrentStack
));

这就是我们 Log 中看到的第一条。但是很明显,这里并不是上电的第一条语句,这里作为本次研究的终点。

使用 HxD 打开 OVMF.FD, 在最后可以看到如下字样,这里是CPU 上电之后运行的第一条指令:

这里是 QEMU 执行的第一条语句

使用UEFITool NE 打开 OVMF.FD查看,这个位置处于 Volume Top File 中。

工具查看它所在的 VTF

在\Build\OvmfX64\DEBUG_VS2015x86\FV\SECFV.inf 中可以看到定义了下面2个 FFS, 换句话说 SecMain + Pad-File + Volume Top File = 第一个FV (上图中 GUID 是 753BE…. 的这个)

EFI_READ_LOCK_CAP = TRUE
EFI_READ_LOCK_STATUS = TRUE
EFI_FVB2_ALIGNMENT_16 = TRUE
EFI_FV_EXT_HEADER_FILE_NAME = d:\stable202108\Build\OvmfX64\DEBUG_VS2015x86\FV\SECFV.ext
[files]
EFI_FILE_NAME = d:\stable202108\Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\df1ccef6-f301-4a63-9661-fc6030dcc880SecMain\df1ccef6-f301-4a63-9661-fc6030dcc880.ffs
EFI_FILE_NAME = d:\stable202108\Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\1BA0062E-C779-4582-8566-336AE8F78F09ResetVector\1BA0062E-C779-4582-8566-336AE8F78F09.ffs

对于这部分生成方法感兴趣的朋友可以在【参考3】中看到介绍。

接下来我们查看1BA0062E-C779-4582-8566-336AE8F78F09.ffs的代码,在\OvmfPkg\ResetVector\Ia16\ResetVectorVtf0.asm 中:

;
; The VTF signature
;
; VTF-0 means that the VTF (Volume Top File) code does not require
; any fixups.
;
vtfSignature:
    DB      'V', 'T', 'F', 0

ALIGN   16

resetVector:
;
; Reset Vector
;
; This is where the processor will begin execution
;
    nop
    nop
    jmp     EarlyBspInitReal16
ALIGN   16
fourGigabytes:

为了证明代码确实来自这里,在    jmp     EarlyBspInitReal16 语句后面添加2条作为标记

	db 0x12
	db 0x34

重新编译之后,可以在 OVMF.FD中看到多出了 12 34 (如果你在IBV 的代码中使用这个方法验证第一条语句的话可能无法成功,原因是IBV 的工具在写入跳转地址的时候会完全覆盖这一行的其他内容,所以你代码中修改之后,生成打包过程中你的标记会被覆盖)。

可以在生成的 OVMF.FD 中看到设置的标记

其中 90 90 E9 53 FF 是跳转指令,会跳转到 0xFFFF FF48

反编译结果

代码在\UefiCpuPkg\ResetVector\Vtf0\Ia16\Init16.asm 中(这里同样可以用上面加标志的方法进行验证,有兴趣的朋友可以自行尝试):

;
; @param[out] DI    'BP' to indicate boot-strap processor
;
EarlyBspInitReal16:
    mov     di, 'BP'
    jmp     short Main16

;
; @param[out] DI    'AP' to indicate application processor
;
EarlyApInitReal16:
    mov     di, 'AP'
jmp     short Main16

接下来跳转到 \stable202108\UefiCpuPkg\ResetVector\Vtf0\Main.asm的Main16 处继续执行。

;
; 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 return
;
Main16:
OneTimeCall EarlyInit16

其中 EarlyInit16在Init16.asm 中:

;
; Modified:  EAX
;
; @param[in]  EAX   Initial value of the EAX register (BIST: Built-in Self Test)
; @param[out] ESP   Initial value of the EAX register (BIST: Built-in Self Test)
;
EarlyInit16:
    ;
    ; ESP -  Initial value of the EAX register (BIST: Built-in Self Test)
    ;
    mov     esp, eax

    debugInitialize   //这是空的宏

OneTimeCallRet EarlyInit16

返回 EarlyInit16 后继续:

    ;
    ; Transition the processor from 16-bit real mode to 32-bit flat mode
    ;
OneTimeCall TransitionFromReal16To32BitFlat
    ;
    ; Search for the Boot Firmware Volume (BFV)
    ;
OneTimeCall Flat32SearchForBfvBase

这个函数是在查找EFI_FIRMWARE_FILE_SYSTEM2_GUID的FV:

;#define EFI_FIRMWARE_FILE_SYSTEM2_GUID \
;  { 0x8c8ce578, 0x8a3d, 0x4f1c, { 0x99, 0x35, 0x89, 0x61, 0x85, 0xc3, 0x2d, 0xd3 } }
%define FFS_GUID_DWORD0 0x8c8ce578
%define FFS_GUID_DWORD1 0x4f1c8a3d
%define FFS_GUID_DWORD2 0x61893599
%define FFS_GUID_DWORD3 0xd32dc385

找到之后继续查找 SEC 的入口:

    ;
    ; EBP - Start of BFV
    ;

    ;
    ; Search for the SEC entry point
    ;
OneTimeCall Flat32SearchForSecEntryPoint

最后,跳转到前面找到的 SEC Entry Point(放在 RSI寄存器中)

    ;
    ; Jump to the 64-bit SEC entry point
    ;
jmp     rsi

接下来代码就跳转到 SecEntry.nasm 的代码中,主要动作是用于指定 Stack的内存:

    ;
    ; Load temporary RAM stack based on PCDs
    ;
    %define SEC_TOP_OF_STACK (FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) + \
                          FixedPcdGet32 (PcdOvmfSecPeiTempRamSize))
    mov     rsp, SEC_TOP_OF_STACK
    nop

    ;
    ; Setup parameters and call SecCoreStartupWithStack
    ;   rcx: BootFirmwareVolumePtr
    ;   rdx: TopOfCurrentStack
    ;
    mov     rcx, rbp
    mov     rdx, rsp
    sub     rsp, 0x20
call    ASM_PFX(SecCoreStartupWithStack)

接下来就是C语言的代码了,位于 \OvmfPkg\Sec\SecMain.c 的 SecCoreStartupWithStack() 函数中。

参考:

  1. https://raw.githubusercontent.com/tianocore/tianocore.github.io/master/images/PI_Boot_Phases.JPG
  2. https://www.lab-z.com/edk202108/
  3. https://www.lab-z.com/ovmffv/
  4. https://onlinedisassembler.com/odaweb/OAs8VKE4/0

《Step to UEFI (238)OVMF 从第一条指令到 SecMain》有一个想法

  1. Pingback: UEFI入门 – 低语

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注