获取来电者的回信地址

Getting the caller's Return Address

本文关键字:地址 来电 获取      更新时间:2023-10-16

我正在尝试弄清楚如何在MSVC中获取呼叫者的返回地址。我可以使用 _ReturnAddress() 来获取函数的返回地址,但我似乎找不到获取调用者的方法。

我尝试使用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安全地执行此操作,而无需依赖调试模式代码生成。 在评论中查看RBMN的答案

在 GNU C/C++(文档)中,等效项是
void * __builtin_return_address (unsigned int level). 所以__builtin_return_address(0)得到你自己的,__builtin_return_address(1)得到你父母的。 该手册警告说,在 arg 为0的情况下,它只有 100% 安全,并且可能会因更高的值而崩溃,但许多平台确实有可以使用的堆栈展开元数据。


仅限 MSVC 32 位调试/未优化版本

如果存在保留的调用堆栈(即在调试版本或不存在优化时),并将 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函数的函数调用将始终将返回地址保存在被调用方堆栈帧的偏移+4。寄存器EBP存储正在运行的函数堆栈帧的头部。因此,在上面的代码中,foo将返回其调用者的返回地址。

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


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

// 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 设置为具有传统堆栈帧布局的帧指针。 这不是现代操作系统中的调用约定或 ABI 的一部分;异常的堆栈展开使用不同的元数据。 但是,如果对调用方禁用了优化,则会出现这种情况。

正如Michael Petch所指出的,MSVC不允许在x86-64 C/C++代码上使用asm内联结构。尽管编译器允许一整套内部函数来处理这个问题。

从上面给出的例子来看,这里使用的调用召集__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
}

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