单纯的函数调用从动作上来说,Call 指令可以看作保存寄存器压栈外加 JMP 的过程(复杂的说调用 Call 会有有实模式,保护模式, Near/Far,gated等等差别,在 IA32 手册上描述足有8页之多)。因此,可以使用压栈保存寄存器外加一个跳转来实现。在 UEFI 中可以通过SetJump() 和 LongJump()函数组合来实现。

在 \MdePkg\Include\Library\BaseLib.h 可以看到下面的定义。

/**
  Saves the current CPU context that can be restored with a call to LongJump()
  and returns 0.

  Saves the current CPU context in the buffer specified by JumpBuffer and
  returns 0. The initial call to SetJump() must always return 0. Subsequent
  calls to LongJump() cause a non-zero value to be returned by SetJump().

  If JumpBuffer is NULL, then ASSERT().
  For Itanium processors, if JumpBuffer is not aligned on a 16-byte boundary, then ASSERT().

  NOTE: The structure BASE_LIBRARY_JUMP_BUFFER is CPU architecture specific.
  The same structure must never be used for more than one CPU architecture context.
  For example, a BASE_LIBRARY_JUMP_BUFFER allocated by an IA-32 module must never be used from an x64 module.
  SetJump()/LongJump() is not currently supported for the EBC processor type.

  @param  JumpBuffer  A pointer to CPU context buffer.

  @retval 0 Indicates a return from SetJump().

**/
RETURNS_TWICE
UINTN
EFIAPI
SetJump (
  OUT     BASE_LIBRARY_JUMP_BUFFER  *JumpBuffer
  );

从介绍上来看,SetJump能保存调用处的全部寄存器,然后返回到调用的位置。

/**
  Restores the CPU context that was saved with SetJump().

  Restores the CPU context from the buffer specified by JumpBuffer. This
  function never returns to the caller. Instead is resumes execution based on
  the state of JumpBuffer.

  If JumpBuffer is NULL, then ASSERT().
  For Itanium processors, if JumpBuffer is not aligned on a 16-byte boundary, then ASSERT().
  If Value is 0, then ASSERT().

  @param  JumpBuffer  A pointer to CPU context buffer.
  @param  Value       The value to return when the SetJump() context is
                      restored and must be non-zero.

**/
VOID
EFIAPI
LongJump (
  IN      BASE_LIBRARY_JUMP_BUFFER  *JumpBuffer,
  IN      UINTN                     Value
  );

从介绍上来看,LongJump恢复SetJump函数能保存调用处的全部寄存器,然后返回到调用SetJump函数的下一条指令的位置。

为了验证上面的说法,编写一个Application。主要代码如下:

UINTN RunMark   =       0;

void
ShowString()
{
        Print(L"www.lab-z.com [%d]\n",RunMark);
}

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        BASE_LIBRARY_JUMP_BUFFER  JumpBuffer;

        SetJump(&JumpBuffer);
        RunMark++;
        ShowString();
        if (RunMark==1) {
                LongJump (&JumpBuffer, (UINTN)-1);
        }
        
        return(0);
}

运行结果如下:

第一次运行 SetJump() 会将当前寄存器存放在 JumpBuffer 中。然后 RunMark 加一,从0变成1.接着,判断这个值等于1,再执行 LongJump,执行的结果会回到  SetJump(); 的下一条指令,也就是 RunMark++。这样,RunMark 变成了2,不会运行 LongJump() 接下来顺利退出了。

最后一个问题,SetJump 和  LongJump的具体实现在哪里?我这边通过 Build 目录里面 MakeFile 内容来确认。

1.找到Application 的 MakeFile 在 \AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\JumpCall\JumpCall\Makefile 其中有如下代码

#
# Build Macro
#
STATIC_LIBRARY_FILES =  \
    $(BIN_DIR)\MdePkg\Library\BaseDebugPrintErrorLevelLib\BaseDebugPrintErrorLevelLib\OUTPUT\BaseDebugPrintErrorLevelLib.lib \
    $(BIN_DIR)\MdePkg\Library\BasePrintLib\BasePrintLib\OUTPUT\BasePrintLib.lib \
    $(BIN_DIR)\MdePkg\Library\BasePcdLibNull\BasePcdLibNull\OUTPUT\BasePcdLibNull.lib \
$(BIN_DIR)\MdePkg\Library\BaseLib\BaseLib\OUTPUT\BaseLib.lib \

2.确定 BaseLib.Lib 的生成方式,在C:\BuildBs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\Makefile 可以看到

$(OUTPUT_DIR)\X64\SetJump.obj : $(MAKE_FILE)
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\Base.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\X64\Nasm.inc
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\X64\ProcessorBind.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\Library\PcdLib.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(DEBUG_DIR)\AutoGen.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Library\BaseLib\X64\SetJump.nasm
	"$(PP)" $(PP_FLAGS) $(INC) c:\buildbs\201903\MdePkg\Library\BaseLib\X64\SetJump.nasm > c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.i
	Trim --trim-long --source-code -o c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.iii c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.i
	"$(NASM)" -Ic:\buildbs\201903\MdePkg\Library\BaseLib\X64\ $(NASM_INC) $(NASM_FLAGS) -o c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.obj c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.iii
就是说使用了c:\buildbs\201903\MdePkg\Library\BaseLib\X64\SetJump.nasm 进行编译。

3.具体代码在C:\BuildBs\201903\MdePkg\Library\BaseLib\X64\SetJump.nasm

;------------------------------------------------------------------------------
; UINTN
; EFIAPI
; SetJump (
;   OUT     BASE_LIBRARY_JUMP_BUFFER  *JumpBuffer
;   );
;------------------------------------------------------------------------------
global ASM_PFX(SetJump)
ASM_PFX(SetJump):
    push    rcx
    add     rsp, -0x20
    call    ASM_PFX(InternalAssertJumpBuffer)

完整的例子代码在这里下载

Leave a Reply

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

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>