获取呼叫者的 Return 地址

Getting the caller's Return Address

我想弄清楚如何在 MSVC 中获取调用者的 return 地址。我可以使用 _ReturnAddress() 来获取函数的 return 地址,但我似乎无法找到获取调用方地址的方法。

我试过使用 CaptureStackBackTrace,但由于某种原因,它在多次调用后崩溃了。我也更喜欢通过内联汇编的解决方案。

void my_function(){
    cout << "return address of caller_function: " << [GET CALLER'S RETURN VALUE];
} // imaginary return address: 0x15AF7C0

void caller_function(){
     my_function();
}// imaginary return address: 0x15AFA70

输出: return address of caller_function: 0x15AFA70

在Windows中,您可以使用RtlCaptureStackBackTraceRtlWalkFrameChain安全地而不依赖debug-modecode-gen。参见

在GNU C/C++ (docs)中,等价于
void * __builtin_return_address (unsigned int level)。所以 __builtin_return_address(0) 得到你自己的,__builtin_return_address(1) 得到你的 parent。该手册警告说,使用 0 的 arg 只有 100% 安全,并且可能会因更高的值而崩溃,但是许多平台确实具有可以使用的 stack-unwind 元数据。


MSVC 32 位 debug/unoptimized 仅构建

如果有保留的调用堆栈(即在调试构建或优化不存在时)并考虑将 MSVC x86 作为目标 PE,您可以执行以下操作:

void *__cdecl get_own_retaddr_debugmode()
{
   // consider you can put this asm inline snippet inside the function you want to get its return address
   __asm
   {
       MOV EAX, DWORD PTR SS:[EBP + 4]
   }
   // fall off the end of a non-void function after asm writes EAX:
   // supported by MSVC but not clang's -fasm-blocks option
}

在调试版本中,当优化在编译器上被禁用时(MSVC 编译器参数:/Od)并且当帧指针未被省略时(MSVC 编译器参数:/Oy-)函数调用 cdecl 函数将始终将 return 地址保存在被调用者堆栈帧的偏移量 +4 处。寄存器 EBP 存储 运行 函数堆栈帧的头部。所以在上面的代码中 foo 将 return 其调用者的 return 地址。

启用优化后,即使这样也会中断:它可以内联到调用者中,并且 MSVC 甚至没有将 EBP 设置为该函数的帧指针(Godbolt compiler explorer) 因为 asm 不引用任何 C 局部变量。使用 mov eax, [esp]naked 函数; ret 可以可靠地工作。


通过再次阅读您的问题,我认为您可能需要来电者的来电者的 return 地址。您可以通过访问直接调用者的堆栈帧然后获取其 return 地址来执行此操作。像这样:

// only works if *the caller* was compiled in debug mode
// as well as this function
void *__cdecl get_caller_retaddr_unsafe_debug_mode_only()
{
   __asm
   {
       MOV ECX, DWORD PTR SS:[EBP + 0] // [EBP+0] points to caller stack frame pointer
       MOV EAX, DWORD PTR SS:[ECX + 4] // get return address of the caller of the caller
   }
}

请务必注意,这需要 调用者 将 EBP 设置为具有传统 stack-frame 布局的帧指针。这不是现代操作系统中调用约定或 ABI 的一部分;异常的堆栈展开使用不同的元数据。但是如果对调用者禁用优化,就会出现这种情况。

正如 Michael Petch 所指出的,MSVC 不允许在 x86-64 C/C++ 代码上使用 asm inline 构造。尽管如此,编译器允许一整套 intrinsic functions 来处理这个问题。

从上面给出的示例来看,此处使用的调用约定 __cdecl 的顺序不正确。在当前的 MVSC++ 编译器代码规范中,它应该是这样的。

// getting our own return address is easy, and should always work
// using inline asm at all forces MSVC to set up EBP as a frame pointer even with optimization enabled
// But this function might still inline into its caller

void __cdecl *get_own_retaddr()
{
   // consider you can put this asm inline snippet inside the function you want to get its return address
   __asm
   {
       MOV EAX, DWORD PTR SS:[EBP + 4]
   }
   // fall off the end of a non-void function after asm writes EAX:
   // supported by MSVC but not clang's -fasm-blocks option
}

同样适用于上面提供的其他示例。