在 Windows 中,您可以使用RtlCaptureStackBackTrace 或RtlWalkFrameChain 安全地执行此操作,而无需依赖调试模式代码生成。见RbMn's answer in comments
在 GNU C / C++ (docs) 中,等价于
void * __builtin_return_address (unsigned int level)。所以__builtin_return_address(0) 得到你自己的,__builtin_return_address(1) 得到你父母的。该手册警告说,使用 0 的 arg 只能 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 compiler explorer) 的帧指针,因为 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 inline 构造。尽管编译器允许一整套 intrinsic functions 来处理它。