Step to UEFI (242)写一个输出当前 IP 的函数

前面的文章中,我们写过输出当前IP 的代码,这次,我们要尝试将这个代码编写为C的函数方便我们调用。

因为 X64 无法内嵌汇编,我们值得参考其他的函数,用如下三步可以实现:

1. \MdePkg\Include\Library\BaseLib.h 中加入函数头定义

/**
Output current RIP to debug port (0x402 for QEMU)

**/
VOID
EFIAPI
AsmCurrentRIP(
	VOID
);

2. \MdePkg\Library\BaseLib\X64\CurrentRIP.nasm 加入我们的函数汇编实现

;------------------------------------------------------------------------------
;
; Copyright (c) 2006 - 2008, Intel Corporation. All rights reserved.<BR>
; SPDX-License-Identifier: BSD-2-Clause-Patent
;
; Module Name:
;
;   CurrentRIP.Asm
;
; Abstract:
;
;  Output current RIP to debug port (0x402 for QEMU)
;  
;
; Notes:
;
;------------------------------------------------------------------------------

    DEFAULT REL
    SECTION .text

;------------------------------------------------------------------------------
; VOID
; EFIAPI
; AsmCurrentRIP (
;   VOID
;   );
;------------------------------------------------------------------------------
global ASM_PFX(AsmCurrentRIP)
ASM_PFX(AsmCurrentRIP):
    lea     rax,[$]
    mov     dx,0x402
	
	; 7-0 Bits
    out     dx,al       

    ; 15-8 Bits
	shr     rax,8
    out     dx,al       
	
	; 23-16 Bits
	shr     rax,8
    out     dx,al       

	; 31-24 Bits
	shr     rax,8
    out     dx,al       

	; 39-32 Bits
	shr     rax,8
    out     dx,al       

	; 47-40 Bits
	shr     rax,8
    out     dx,al       

	; 55-48 Bits
	shr     rax,8
    out     dx,al       

	; 63-56 Bits
	shr     rax,8
    out     dx,al     

ret

3. \MdePkg\Library\BaseLib\BaseLib.inf 加入上面的文件

[Sources.X64]
  X64/Thunk16.nasm
  X64/CpuIdEx.nasm
  X64/CpuId.nasm
  X64/LongJump.nasm
  X64/SetJump.nasm
  X64/SwitchStack.nasm
  X64/EnableCache.nasm
  X64/DisableCache.nasm
  X64/WriteTr.nasm
  X64/Lfence.nasm
  X64/CurrentRIP.nasm
  X64/CpuBreakpoint.c | MSFT
  X64/WriteMsr64.c | MSFT
  X64/ReadMsr64.c | MSFT
  X64/CpuPause.nasm| MSFT
  X64/DisableInterrupts.nasm| MSFT

接下来就可以使用这个函数了,比如,我们在\OvmfPkg\Sec\SecMain.c中使用这个函数。

  if (!SevEsIsEnabled ()) {
    //
    // For non SEV-ES guests, just load the IDTR.
    //
    AsmWriteIdtr (&IdtDescriptor);
  } else {
    //
    // Under SEV-ES, the hypervisor can't modify CR0 and so can't enable
    // caching in order to speed up the boot. Enable caching early for
    // an SEV-ES guest.
    //
    AsmEnableCache ();
  }
AsmCurrentRIP();
  DEBUG ((DEBUG_INFO,
    "SecCoreStartupWithStack(0x%x, 0x%x)\n",
    (UINT32)(UINTN)BootFv,
    (UINT32)(UINTN)TopOfCurrentStack
));

之后的运行中,我们也可以在串口 Log 中刚看到输出的 IP。

实际上这样的作法是错误的,有兴趣的朋友可以思考一下:我想输出当前代码运行的IP,上面的方法错在哪里?

接下来,我们在 SecMain.inf 中加入如下命令,生成 SecMain.cod 进行查看:

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /FAsc /Od

在 SecMain.cod 中我们看到的结果如下:
; 908  :     //
; 909  :     // Under SEV-ES, the hypervisor can't modify CR0 and so can't enable
; 910  :     // caching in order to speed up the boot. Enable caching early for
; 911  :     // an SEV-ES guest.
; 912  :     //
; 913  :     AsmEnableCache ();

  00131	e8 00 00 00 00	 call	 AsmEnableCache
$LN25@SecCoreSta:

; 914  :   }
; 915  : AsmCurrentRIP();

  00136	e8 00 00 00 00	 call	 AsmCurrentRIP
$LN13@SecCoreSta:

; 916  :   DEBUG ((DEBUG_INFO,

  0013b	e8 00 00 00 00	 call	 DebugPrintEnabled
  00140	0f b6 c0	 movzx	 eax, al
  00143	85 c0		 test	 eax, eax
  00145	74 38		 je	 SHORT $LN26@SecCoreSta
$LN16@SecCoreSta:

; 917  :     "SecCoreStartupWithStack(0x%x, 0x%x)\n",
; 918  :     (UINT32)(UINTN)BootFv,
; 919  :     (UINT32)(UINTN)TopOfCurrentStack
; 920  :     ));

那么问题来了,我们是在 AsmCurrentRIP() 中输出的当前 IP。作为一个函数,它的地址是确定的。因此,你在不同的地方调用这个函数,实际上会得到同样的结果。因此,上面的方法是错误的。继续思考,当我们 Call 到这个函数中之后,堆栈中存着调用处的信息(更准确的说是返回位置的信息)。因此,稍微修改我们之前的代码,输出堆栈中的信息即可。

首先,修改后的代码如下:

;------------------------------------------------------------------------------
;
; Copyright (c) 2006 - 2008, Intel Corporation. All rights reserved.<BR>
; SPDX-License-Identifier: BSD-2-Clause-Patent
;
; Module Name:
;
;   CurrentRIP.Asm
;
; Abstract:
;
;  Output current RIP to debug port (0x402 for QEMU)
;  
;
; Notes:
;
;------------------------------------------------------------------------------

    DEFAULT REL
    SECTION .text

;------------------------------------------------------------------------------
; VOID
; EFIAPI
; AsmCurrentRIP (
;   VOID
;   );
;------------------------------------------------------------------------------
global ASM_PFX(AsmCurrentRIP)
ASM_PFX(AsmCurrentRIP):
    mov     rax,[rsp]
    mov     dx,0x402
	
	; 7-0 Bits
    out     dx,al       

    ; 15-8 Bits
	shr     rax,8
    out     dx,al       
	
	; 23-16 Bits
	shr     rax,8
    out     dx,al       

	; 31-24 Bits
	shr     rax,8
    out     dx,al       

	; 39-32 Bits
	shr     rax,8
    out     dx,al       

	; 47-40 Bits
	shr     rax,8
    out     dx,al       

	; 55-48 Bits
	shr     rax,8
    out     dx,al       

	; 63-56 Bits
	shr     rax,8
    out     dx,al     

ret

在 SecMain.c 中调用如下:

//
  // Find PEI Core entry point. It will report SEC and Pei Core debug information if remote debug
  // is enabled.
  //
  BootFv = (EFI_FIRMWARE_VOLUME_HEADER *)SecCoreData->BootFirmwareVolumeBase;
  FindAndReportEntryPoints (&BootFv, &PeiCoreEntryPoint);
  SecCoreData->BootFirmwareVolumeBase = BootFv;
  SecCoreData->BootFirmwareVolumeSize = (UINTN) BootFv->FvLength;
  AsmCurrentRIP(); //ZivDebug
  DEBUG ((DEBUG_INFO,
    "Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
	(UINT64)BootFv,
    (UINT64)(PeiCoreEntryPoint),
	(UINT64)(*PeiCoreEntryPoint)
    ));
  AsmCurrentRIP(); //ZivDebug	
  //
  // Transfer the control to the PEI core
  //
  (*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);

在对应的 Cod 文件中查看到的结果如下:

; 1016 :   AsmCurrentRIP(); //ZivDebug

  00050	e8 00 00 00 00	 call	 AsmCurrentRIP
$LN4@SecStartup:

; 1017 :   DEBUG ((DEBUG_INFO,

  00055	e8 00 00 00 00	 call	 DebugPrintEnabled
  0005a	0f b6 c0	 movzx	 eax, al
  0005d	85 c0		 test	 eax, eax
  0005f	74 3c		 je	 SHORT $LN11@SecStartup
$LN7@SecStartup:

; 1018 :     "Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
; 1019 : 	(UINT64)BootFv,
; 1020 :     (UINT64)(PeiCoreEntryPoint),
; 1021 : 	(UINT64)(*PeiCoreEntryPoint)
; 1022 :     ));

  00061	b9 40 00 00 00	 mov	 ecx, 64			; 00000040H
  00066	e8 00 00 00 00	 call	 DebugPrintLevelEnabled
  0006b	0f b6 c0	 movzx	 eax, al
  0006e	85 c0		 test	 eax, eax
  00070	74 25		 je	 SHORT $LN12@SecStartup
  00072	48 8b 44 24 40	 mov	 rax, QWORD PTR PeiCoreEntryPoint$[rsp]
  00077	48 89 44 24 20	 mov	 QWORD PTR [rsp+32], rax
  0007c	4c 8b 4c 24 40	 mov	 r9, QWORD PTR PeiCoreEntryPoint$[rsp]
  00081	4c 8b 44 24 30	 mov	 r8, QWORD PTR BootFv$[rsp]
  00086	48 8d 15 00 00
	00 00		 lea	 rdx, OFFSET FLAT:??_C@_0FL@FMODEEHL@Check?5?$FLBootFirmwareVolumeBase?$FN?90@
  0008d	b9 40 00 00 00	 mov	 ecx, 64			; 00000040H
  00092	e8 00 00 00 00	 call	 DebugPrint
$LN12@SecStartup:
  00097	33 c0		 xor	 eax, eax
  00099	85 c0		 test	 eax, eax
  0009b	75 c4		 jne	 SHORT $LN7@SecStartup
$LN11@SecStartup:

; 1017 :   DEBUG ((DEBUG_INFO,

  0009d	33 c0		 xor	 eax, eax
  0009f	85 c0		 test	 eax, eax
  000a1	75 b2		 jne	 SHORT $LN4@SecStartup

; 1023 :   AsmCurrentRIP(); //ZivDebug	

  000a3	e8 00 00 00 00	 call	 AsmCurrentRIP

第一次call 是在 AsmCurrentRIP 00050, 第二次是call 是在 000a3的位置,二者相差 (a3-50=53)。接下来查看 Log 中的输出结果:

红色位置就是2次 Call 输出结果

前面是 FFFC E309 后一个是 FFFC E35C二者相差也是 53,因此,可以确定函数输出没问题。

晚上上述实验之后,我开始翻阅 《Step To UEFI》系列文章,找到了《Step to UEFI (201)直接取得函数返回地址》【参考1】就是说可以直接通过 VC 内置函数取得调用处下一条指令的地址。

参考:

1.https://www.lab-z.com/stu201aor/

发表评论

您的电子邮箱地址不会被公开。