Step to UEFI (195)谁动了我的 RET

前面的文章“EFI 文件研究(1)”提到了入口地方有一个奇怪的现象,直接从ProcessLibraryConstructorList 函数跳到了ProcessModuleEntryPointList。百思不得其解之后咨询天杀,他提到有编译器有一种优化方式,针对“连续两个函数的调用 ,在优化后可能会将第一个函数的尾部返回优化成对第二个函数的跳转,然后由第二个函数来进行返回”。经过这样的提醒后,我在代码中查找,找到了非常类似的代码。

在\MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c 定义了Application 的入口:

/**
  Entry point to UEFI Application.

  This function is the entry point for a UEFI Application. This function must call
  ProcessLibraryConstructorList(), ProcessModuleEntryPointList(), and ProcessLibraryDestructorList().
  The return value from ProcessModuleEntryPointList() is returned.
  If _gUefiDriverRevision is not zero and SystemTable->Hdr.Revision is less than _gUefiDriverRevison,
  then return EFI_INCOMPATIBLE_VERSION.

  @param  ImageHandle                The image handle of the UEFI Application.
  @param  SystemTable                A pointer to the EFI System Table.

  @retval  EFI_SUCCESS               The UEFI Application exited normally.
  @retval  EFI_INCOMPATIBLE_VERSION  _gUefiDriverRevision is greater than SystemTable->Hdr.Revision.
  @retval  Other                     Return value from ProcessModuleEntryPointList().

**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                 Status;

  if (_gUefiDriverRevision != 0) {
    //
    // Make sure that the EFI/UEFI spec revision of the platform is >= EFI/UEFI spec revision of the application.
    //
    if (SystemTable->Hdr.Revision < _gUefiDriverRevision) {
      return EFI_INCOMPATIBLE_VERSION;
    }
  }
  //
  // Call constructor for all libraries.
  //
  ProcessLibraryConstructorList (ImageHandle, SystemTable);
  //
  // Call the module's entry point
  //
  Status = ProcessModuleEntryPointList (ImageHandle, SystemTable);

  //
  // Process destructor for all libraries.
  //
  ProcessLibraryDestructorList (ImageHandle, SystemTable);

  //
  // Return the return status code from the driver entry point
  //
  return Status;
}

因此,前面_gUefiDriverRevision == 0 在编译期内部代码直接会被优化掉,剩下的就是连续两次调用ProcessLibraryConstructorList 和ProcessModuleEntryPointList 函数。

为了证明这一点,我在 Inf 文件中加入关闭优化的指令 /Od:

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

加入之后再次编译,

在 \Build\AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\EFIStudy\EFIStudy\ApplicationEntryPoint.cod 中可以看到完整的图景:

_ModuleEntryPoint PROC					; COMDAT

; 45   : {

$LN25:
  00000	48 89 5c 24 08	 mov	 QWORD PTR [rsp+8], rbx
  00005	48 89 74 24 10	 mov	 QWORD PTR [rsp+16], rsi
  0000a	57		 push	 rdi
  0000b	48 83 ec 20	 sub	 rsp, 32			; 00000020H
  0000f	48 8b fa	 mov	 rdi, rdx
  00012	48 8b f1	 mov	 rsi, rcx

; 46   :   EFI_STATUS                 Status;
; 47   : 
; 48   :   if (_gUefiDriverRevision != 0) {
; 49   :     //
; 50   :     // Make sure that the EFI/UEFI spec revision of the platform is >= EFI/UEFI spec revision of the application.
; 51   :     //
; 52   :     if (SystemTable->Hdr.Revision < _gUefiDriverRevision) {
; 53   :       return EFI_INCOMPATIBLE_VERSION;
; 54   :     }
; 55   :   }
; 56   : 
; 57   :   //
; 58   :   // Call constructor for all libraries.
; 59   :   //
; 60   :   ProcessLibraryConstructorList (ImageHandle, SystemTable);

  00015	e8 00 00 00 00	 call	 ProcessLibraryConstructorList

; 61   : 
; 62   :   //
; 63   :   // Call the module's entry point
; 64   :   //
; 65   :   Status = ProcessModuleEntryPointList (ImageHandle, SystemTable);

  0001a	48 8b d7	 mov	 rdx, rdi
  0001d	48 8b ce	 mov	 rcx, rsi
  00020	e8 00 00 00 00	 call	 ProcessModuleEntryPointList

; 66   : 
; 67   :   //
; 68   :   // Process destructor for all libraries.
; 69   :   //
; 70   :   ProcessLibraryDestructorList (ImageHandle, SystemTable);

  00025	48 8b d7	 mov	 rdx, rdi
  00028	48 8b ce	 mov	 rcx, rsi
  0002b	48 8b d8	 mov	 rbx, rax
  0002e	e8 00 00 00 00	 call	 ProcessLibraryDestructorList

; 71   : 
; 72   :   //
; 73   :   // Return the return status code from the driver entry point
; 74   :   //
; 75   :   return Status;
; 76   : }

  00033	48 8b 74 24 38	 mov	 rsi, QWORD PTR [rsp+56]
  00038	48 8b c3	 mov	 rax, rbx
  0003b	48 8b 5c 24 30	 mov	 rbx, QWORD PTR [rsp+48]
  00040	48 83 c4 20	 add	 rsp, 32			; 00000020H
  00044	5f		 pop	 rdi
  00045	c3		 ret	 0
_ModuleEntryPoint ENDP

这里可以清楚的看到分别调用了2个函数,并且和我们上面找到的位置代码是一致的。

从上面的试验可以得知:

  1. _ModuleEntryPoint ()是每个 Application 的起点,如果有需要可以在其中添加代码;
  2. 编译器有时候会将连续的2个函数调用优化。

发表回复

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