前面代码中调用了 AsmCurrentRIP ,这次我们使用 WinDBG查看 X64 Mode 下 Call的具体实现。 为了更好的进行查看,我们需要对代码进行一些修改。在 SecStartupPhase2() 中加入下面的代码(SecCoreStartupWithStack()函数太早,尚且运行在 ROM 中的,另外此时的Debug环境还没有配置好无法halt ):
//
// 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;
DEBUG ((DEBUG_INFO,
"Check [BootFirmwareVolumeBase]-0x%X [PeiCoreEntryPoint]=0x%X BootFirmwareVolumeBase=0x%X \n",
(UINT64)BootFv,
(UINT64)(PeiCoreEntryPoint),
(UINT64)(*PeiCoreEntryPoint)
));
//LabzDebug_Start
CpuBreakpoint();
AsmCurrentRIP();
//LabzDebug_End
//
// Transfer the control to the PEI core
//
(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);
编译完成后,运行NewDbg.bat 批处理,会自动调用 QEMU 还有 WinDBG 然后会自动 halt住。第一次 Halt并非我们需要的位置,因此,我们用g 让它继续运行。再次 Halt 之后是停在 int3 中,使用u反编译可以看一下。接下来连续运行两次t 命令。再使用 u 查看:
0: kd> t
00000000`fffcedd7 c3 ret
0: kd> t
00000000`fffce7a7 e8c8ddffff call 00000000`fffcc574
0: kd> u
00000000`fffce7a7 e8c8ddffff call 00000000`fffcc574
00000000`fffce7ac 488d15098e0100 lea rdx,[00000000`fffe75bc]
00000000`fffce7b3 488b4c2438 mov rcx,qword ptr [rsp+38h]
00000000`fffce7b8 ff542440 call qword ptr [rsp+40h]
00000000`fffce7bc e8d30b0000 call 00000000`fffcf394
00000000`fffce7c1 0fb6c0 movzx eax,al
00000000`fffce7c4 85c0 test eax,eax
00000000`fffce7c6 741f je 00000000`fffce7e7
这里的信息可以和 SecMain.cod 对起来:
; 1016 : DEBUG ((DEBUG_INFO,
00098 33 c0 xor eax, eax
0009a 85 c0 test eax, eax
0009c 75 b2 jne SHORT $LN4@SecStartup
; 1022 : //LabzDebug_Start
; 1023 : CpuBreakpoint();
0009e e8 00 00 00 00 call CpuBreakpoint
; 1024 : AsmCurrentRIP();
000a3 e8 00 00 00 00 call AsmCurrentRIP
; 1025 :
; 1026 : //
; 1027 : // Transfer the control to the PEI core
; 1028 : //
; 1029 : (*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable);
000a8 48 8d 15 00 00
00 00 lea rdx, OFFSET FLAT:mPrivateDispatchTable
000af 48 8b 4c 24 38 mov rcx, QWORD PTR SecCoreData$[rsp]
000b4 ff 54 24 40 call QWORD PTR PeiCoreEntryPoint$[rsp]
$LN10@SecStartup:
下面着重分析 上面调用 AsmCurrentRIP() 这个函数:
00000000`fffce7a7 e8c8ddffff call 00000000`fffcc574
指令是: e8c8dd ffff , 从手册可以看到 E8 是 “Call near”的意思:

对于 near call 动作如下:

最重要的动作是: push(RIP)和 RIP=tempRIP。特别注意,这里的 RIP 不是当前 call 所在的 RIP(不是 00000000`fffce7a7),而是这条指令的下一条(手册中有描述Near Call. When executing a near call, the processor pushes the value of the EIP register (which contains the offset of the instruction following the CALL instruction) on the stack (for use later as a return-instruction pointer). The processor then branches to the address in the current code segment specified by the target operand. The target operand specifies either an absolute offset in the code segment (an offset from the base of the code segment) or a relative offset (a signed displacement relative to the current value of the instruction pointer in the EIP register; this value points to the instruction following the CALL instruction). The CS register is not changed on near calls.)就是说执行e8 c8dd ffff操作的时候,RIP=00000000 fffce7ac, 于是 Push RIP到堆栈中,然后修改 RIP。 其中的 0xFFFF DDC8(-0x2238)是相对位置,于是跳转到的位置是 00000000 FFFC E7AC+(-0x2238)= 00000000 FFFC C574。也就是右侧 call 00000000`fffcc574的由来。
因此,我们修改代码,直接输出 ESP 指向的8个字节即可实现输出调用位置下一条指令的IP。
接下来,我们再查看call调用前后堆栈的情况。下面是执行 call 之前,使用r rsp 和 dd rsp 查看堆栈寄存器和对应的内存位置:
0: kd> r rsp
rsp=000000000081fb10
0: kd> dd rsp
00000000`0081fb10 00000040 00000000 fffe4424 00000000
00000000`0081fb20 00820000 00000000 008207a0 00000000
00000000`0081fb30 008207a0 00000000 0000001f 00000000
00000000`0081fb40 00820000 00000000 0081fd50 00000000
00000000`0081fb50 008207a0 00000000 00000000 00000000
00000000`0081fb60 0081fb98 00000000 fffd23ab 00000000
00000000`0081fb70 0081fd50 00000000 00000001 00000000
00000000`0081fb80 00000000 00000000 fffdf860 00000000
接下来使用 t 这里就跳入了 AsmCurrentRIP 函数中。再查看 rsp 和对应的内存位置:
0: kd> t
00000000`fffcc574 488d05f9ffffff lea rax,[00000000`fffcc574]
0: kd> r rsp
rsp=000000000081fb08
0: kd> dd rsp
00000000`0081fb08 fffce7ac 00000000 00000040 00000000
00000000`0081fb18 fffe4424 00000000 00820000 00000000
00000000`0081fb28 008207a0 00000000 008207a0 00000000
00000000`0081fb38 0000001f 00000000 00820000 00000000
00000000`0081fb48 0081fd50 00000000 008207a0 00000000
00000000`0081fb58 00000000 00000000 0081fb98 00000000
00000000`0081fb68 fffd23ab 00000000 0081fd50 00000000
00000000`0081fb78 00000001 00000000 00000000 00000000
0: kd> r rip
rip=00000000fffcc574
0: kd> u rip
00000000`fffcc574 488d05f9ffffff lea rax,[00000000`fffcc574]
00000000`fffcc57b 66ba0204 mov dx,402h
00000000`fffcc57f ee out dx,al
00000000`fffcc580 48c1e808 shr rax,8
00000000`fffcc584 ee out dx,al
00000000`fffcc585 48c1e808 shr rax,8
00000000`fffcc589 ee out dx,al
00000000`fffcc58a 48c1e808 shr rax,8
可以看到堆栈存放了call 指令下一条的 RIP,同时 RSP 减8。
参考:
1. https://stackoverflow.com/questions/10376787/need-help-understanding-e8-asm-call-instruction-x86