前面的文章中,我们写过输出当前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 中的输出结果:
前面是 FFFC E309 后一个是 FFFC E35C二者相差也是 53,因此,可以确定函数输出没问题。
晚上上述实验之后,我开始翻阅 《Step To UEFI》系列文章,找到了《Step to UEFI (201)直接取得函数返回地址》【参考1】就是说可以直接通过 VC 内置函数取得调用处下一条指令的地址。
参考: