6

I am trying to figure out how to grab the return address of a caller in MSVC. I can use _ReturnAddress() to get the return address of my function, but I can't seem to find a way to get the caller's.

I've tried using CaptureStackBackTrace, but for some reason, it crashes after many, many calls. I would also prefer a solution via inline assembly.

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

Output: return address of caller_function: 0x15AFA70

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • are you using /clr? also you probably should declare function as noinline. And it seems that fucntion somewhat sensitive to call conventions. – Swift - Friday Pie Aug 23 '19 at 07:59
  • 5
    Why would you need that? What are you trying to solve? This has a bit a smell of an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)... – Aconcagua Aug 23 '19 at 08:03
  • I don't know if this is the case but it is a basic and vastly concept used when patching binaries. – andresantacruz Aug 23 '19 at 08:04
  • 2
    `CaptureStackBackTrace` crashes only if you incorrect call it. really use it is solution – RbMm Aug 23 '19 at 09:01

2 Answers2

9

In Windows, you can use RtlCaptureStackBackTrace or RtlWalkFrameChain to do this safely without relying on debug-mode code-gen. See RbMn's answer in comments

In GNU C / C++ (docs), the equivalent is
void * __builtin_return_address (unsigned int level). So __builtin_return_address(0) to get your own, __builtin_return_address(1) to get your parent's. The manual warns that it's only 100% safe with an arg of 0 and might crash with higher values, but many platforms do have stack-unwind metadata that it can use.


MSVC 32-bit debug/unoptimized builds only

If there is a preserved call stack (i.e. on debug builds or when optimizations are not present) and considering MSVC x86 as target PE you could do something like:

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
}

On debug builds, when optimization are disabled on the compiler (MSVC compiler argument: /Od) and when frame pointer is not omitted (MSVC compiler argument: /Oy-) function calls to cdecl functions will always save the return address at the offset +4 of the callee stack frame. The register EBP stores the head of the running function's stack frame. So in the code above foo will return the return address of its caller.

With optimization enabled, even this breaks: it can inline into the caller, and MSVC doesn't even set up EBP as a frame pointer for this function (Godbolt compiler explorer) because the asm doesn't reference any C local variables. A naked function that used mov eax, [esp] ; ret would work reliably.


By reading again your question I think you might want the return address of the caller of the caller. You can do this by accessing the immediate caller's stack frame and then getting its return address. Something like this:

// 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
   }
}

It is important to note that this requires the caller to have set up EBP as a frame pointer with the traditional stack-frame layout. This isn't part of the calling convention or ABI in modern OSes; stack unwinding for exceptions uses different metadata. But it will be the case if optimization is disabled for the caller.

As noted by Michael Petch, MSVC doesn't allow the asm inline construct on x86-64 C/C++ code. Despite that the compiler allows a whole set of intrinsic functions to deal with that.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
andresantacruz
  • 1,676
  • 10
  • 17
  • this depends on calling conventions – Swift - Friday Pie Aug 23 '19 at 07:57
  • @Swift-FridayPie that's why I annotated `foo` as `cdecl`. – andresantacruz Aug 23 '19 at 07:58
  • @Aconcagua as far as I know `stdcall` does use the same stack frame layout as `cdecl`. Can you elaborate a bit more about `stdcall` not using this stack frame layout? – andresantacruz Aug 23 '19 at 10:51
  • 1
    Arrgh – have been inattendent... Sorry. Layout *is* the same, just cleaning up is done at different locations... – Aconcagua Aug 23 '19 at 10:55
  • 1
    IA-64 is not x86-64. – ecm Aug 23 '19 at 11:11
  • Note: MSVC/C++ doesn't support x86-64 inline assembly. – Michael Petch Aug 23 '19 at 14:06
  • 2
    At the top you say this _(same applies for x64 changing the respective r32 registers for its r64 counterparts and changing stack frame's offset +4 to +8)_ . The implication is that all you need to do is adjust the offset and use r64 registers to make the inline assembly work in 64-bit. The problem is that inline assembly is [no longer supported when building x86-64 C/C++ code.](https://learn.microsoft.com/en-us/cpp/assembler/inline/inline-assembler?view=vs-2019) . From that linked documentation it says _Inline assembly is not supported on the ARM and x64 processors._ – Michael Petch Aug 23 '19 at 14:17
  • `cdecl` doesn't prevent optimizing away the frame pointer. This only works if your *caller* is un-optimized. (Using MSVC inline asm I think forces that function to set up EBP as a frame pointer, even with optimization enabled.) This answer doesn't have nearly enough caveats / warnings that the 2nd part is very fragile. – Peter Cordes Aug 23 '19 at 16:14
  • 1
    Optimization of your `foo` isn't the problem. The problem is that the saved EBP value (which you load from `[ebp+0]` might be totally meaningless because whatever function that calls `foo` didn't set it up as a stack pointer. e.g. https://godbolt.org/z/86GsAe shows that MSVC -O2 compiling for x86 and x64 doesn't set up a stack frame in a function that calls another function (given just a prototype for that function). Even if the definition was available, it wouldn't change things. So basically **the 2nd version, chasing the EBP chain, only works in debug builds.** – Peter Cordes Aug 23 '19 at 16:47
  • @PeterCordes you are definitely right on that. I guess I let a lot go unnoticed indeed. Can you think of other possible problematic scenarios besides the functions on a given call stack being optimized? I will wait for you before updating my answer. – andresantacruz Aug 23 '19 at 17:05
  • @dedecos: Only working in debug-mode is already a total showstopper for most use-cases. But yes, also that `foo` could *inline* into its caller, so the asm block runs in that context. And possibly that caller also inlines into a parent function. A `naked` function would prevent this (and would also let you just use the parent's EBP value directly because you wouldn't want to set up your own frame pointer.) You could just write it in a separate asm file, then you could make a 64-bit version as well. But again still useless outside of debug builds. – Peter Cordes Aug 23 '19 at 17:23
  • 3
    really exist windows api `RtlCaptureStackBackTrace` and `RtlWalkFrameChain` which do this job, support both x86/x64, so not need write custom code. and they of course not crash, if call it correct. if write ownd code need check that next possible frame *eax*: `mov eax,[ebp]` was higer than *ebp* and lower than stack top - `fs:[4]` (fs:[NT_TIB.StackBase]) – RbMm Aug 24 '19 at 23:03
  • how minimum this [code](https://pastebin.com/UVVUVCPs) not crash because checking are ebp in thread stack range – RbMm Aug 25 '19 at 09:27
0

From the example giving above, the calling convension used here __cdecl is not in there right order. This is how it should be on current MVSC++ compilers code specification.

// 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
}

Same applies to other examples provided above.

Peter
  • 1,124
  • 14
  • 17
  • 1
    This should have been a suggested-edit to the other answer to fix that syntax error. I've updated the other answer so it compiles now. – Peter Cordes Mar 15 '20 at 19:46