获取呼叫者的 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中,您可以使用RtlCaptureStackBackTrace
或RtlWalkFrameChain
来安全地而不依赖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
}
同样适用于上面提供的其他示例。
我想弄清楚如何在 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中,您可以使用RtlCaptureStackBackTrace
或RtlWalkFrameChain
来安全地而不依赖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
}
同样适用于上面提供的其他示例。