Step to UEFI (126)ReturnAddress 研究

最近查看代码发现 Base.h 中新添加了一个ReturnAddress的函数(称作宏更恰当)。对应代码如下:

#if defined(_MSC_EXTENSIONS) && !defined (__INTEL_COMPILER) && !defined (MDE_CPU_EBC)
  void * _ReturnAddress(void);
  #pragma intrinsic(_ReturnAddress)
  /**
    Get the return address of the calling function.

    Based on intrinsic function _ReturnAddress that provides the address of
    the instruction in the calling function that will be executed after
    control returns to the caller.

    @param L    Return Level.

    @return The return address of the calling function or 0 if L != 0.

  **/
  #define RETURN_ADDRESS(L)     ((L == 0) ? _ReturnAddress() : (VOID *) 0)
#elif defined(__GNUC__)
  void * __builtin_return_address (unsigned int level);
  /**
    Get the return address of the calling function.

    Based on built-in Function __builtin_return_address that returns
    the return address of the current function, or of one of its callers.

    @param L    Return Level.

    @return The return address of the calling function.

  **/
  #define RETURN_ADDRESS(L)     __builtin_return_address (L)
#else
  /**
    Get the return address of the calling function.

    @param L    Return Level.

    @return 0 as compilers don't support this feature.

  **/
  #define RETURN_ADDRESS(L)     ((VOID *) 0)
#endif

 

为了一探究竟,编写一个Application ,将上面的内容直接移植到上面:

/** @file
  Support routines for RDRAND instruction access.

Copyright (c) 2013, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>

  #pragma intrinsic(_ReturnAddress)
  /**
    Get the return address of the calling function.

    Based on intrinsic function _ReturnAddress that provides the address of
    the instruction in the calling function that will be executed after
    control returns to the caller.

    @param L    Return Level.

    @return The return address of the calling function or 0 if L != 0.

  **/
  #define RETURN_ADDRESS(L)     ((L == 0) ? _ReturnAddress() : (VOID *) 0)

/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        Print (L"return here [%X] \n",UefiMain);
        Print (L"return here [%X] \n",RETURN_ADDRESS(0));
        
        return EFI_SUCCESS;
}

 

为了更方便的研究编译之后的结果,在INF中打开生成汇编代码的功能:

 [BuildOptions]
  MSFT:*_*_X64_CC_FLAGS = /Oi- /FAcs /Od

 

上面的代码运行结果如下(NT32模拟器,X64编译)

rtadd

首先查看MAP文件,生成的代码中UefiMain 的地址是4dc,这就是运行结果中 41DB4DC的来源

\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\ReturnAddress\returnaddress\OUTPUT\returnadd.map
0001:000001f8       ProcessModuleEntryPointList 00000000000004b8 f   returnadd:AutoGen.obj
 0001:0000021c       UefiMain                   00000000000004dc f   returnadd:returnaddress.obj
 0001:00000258       DebugPrint                 0000000000000518 f   BaseDebugLibNull:DebugLib.obj

 

进一步观察 COD 文件,UefiMain 编译的结果如下,就是:48 89 54 24 10 48 89 4c 24 08……

\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\ReturnAddress\returnaddress\returnaddress.cod

UefiMain PROC						; COMDAT

; 50   : {

$LN3:
  00000	48 89 54 24 10	 mov	 QWORD PTR [rsp+16], rdx
  00005	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  0000a	48 83 ec 28	 sub	 rsp, 40			; 00000028H

; 51   :         Print (L"return here [%X] \n",UefiMain);

  0000e	48 8d 15 00 00
	00 00		 lea	 rdx, OFFSET FLAT:UefiMain
  00015	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_1CG@PNEGEHON@?$AAr?$AAe?$AAt?$AAu?$AAr?$AAn?$AA?5?$AAh?$AAe?$AAr?$AAe?$AA?5?$AA?$FL?$AA?$CF?$AAX?$AA?$FN?$AA?5?$AA?6?$AA?$AA@
  0001c	e8 00 00 00 00	 call	 Print

 

打开十六进制编辑软件,可以看到 0x4DC偏移处就是上面的机器码:

rtadd2

我们再查看调用 UefiMain的地方,可以在 AutoGen.C 中找到

\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\ReturnAddress\returnaddress\AutoGen.cod

ProcessModuleEntryPointList PROC			; COMDAT

; 224  : {

$LN3:
  00000	48 89 54 24 10	 mov	 QWORD PTR [rsp+16], rdx
  00005	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  0000a	48 83 ec 28	 sub	 rsp, 40			; 00000028H

; 225  :   return UefiMain (ImageHandle, SystemTable);

  0000e	48 8b 54 24 38	 mov	 rdx, QWORD PTR SystemTable$[rsp]
  00013	48 8b 4c 24 30	 mov	 rcx, QWORD PTR ImageHandle$[rsp]
  00018	e8 00 00 00 00	 call	 UefiMain

 

我们再使用上面生成的机器码进行查找,可以发现是从 0x4B8开始的

rtadd3

0x4D5 对应的位置在下面

  0001d	48 83 c4 28	 add	 rsp, 40			; 00000028H
  00021	c3		 ret	 0
ProcessModuleEntryPointList ENDP

 

就是 call UefiMain 之后的位置。
因此 Return_Address 的作用就是返回当前所在函数返回的位置。比如:下面的示例代码中 Return_Address() 取得的地址就是 RT: 的地址。

Int Foo()
{
Return_Address()
}
Call Foo()
Rt:

 

本文提到的完整代码在这里:
ReturnAddress

参考:
1. https://msdn.microsoft.com/en-us/library/64ez38eh.aspx

Step to UEFI (126)ReturnAddress 研究》上有 1 条评论

  1. arkhe

    本质上就是编译器生成针对堆栈中caller返回地址的offset,使用rsp + offset 抓取返回地址,然后取值;你可以再讲深入点的。佩服你有毅力学习新东西,并经常更新 blog,然后分享给别人。

    回复

发表评论

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