最近查看代码发现 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编译)
首先查看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偏移处就是上面的机器码:
我们再查看调用 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开始的
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
本质上就是编译器生成针对堆栈中caller返回地址的offset,使用rsp + offset 抓取返回地址,然后取值;你可以再讲深入点的。佩服你有毅力学习新东西,并经常更新 blog,然后分享给别人。