0

The GNU return address documentation states that __builtin_return_address(1) yields the return address of the caller of the current function.

Could someone expand on what this description means? After doing some testing, I realized it doesn't seem to be doing what I expect it to do (so I may not be understanding it correctly).

For example, I made the following very simple test code to further understand how this function work (as well as the others):

#include <stdio.h> 
#include <stdint.h>
void foo(uint64_t x, uint64_t y){
  void *ptr  = __builtin_extract_return_addr(__builtin_return_address(0));
  void *ptr2 = __builtin_extract_return_addr(__builtin_return_address(1));

printf("ret_addr(0)=%p\nret_addr(1)=%p\n", ptr, ptr2);
  return;
}

int main(int argc, char **argv)
{
  foo(1,1);
  foo(1,1);
}

Disassembled code (for reference)

0000000000400536 <main>:
  400536:       55                      push   %rbp
             ------- skipped -------
  40054f:       e8 93 ff ff ff          callq  4004e7 <foo>
  400554:       be 01 00 00 00          mov    $0x1,%esi
             ------- skipped -------
  40055e:       e8 84 ff ff ff          callq  4004e7 <foo>
  400563:       b8 00 00 00 00          mov    $0x0,%eax
             ------- skipped -------

Upon executing this code, the following is outputted:

ret_addr(0)=0x400554
ret_addr(1)=0x7f609c67cb97

ret_addr(0)=0x400563
ret_addr(1)=0x7f609c67cb97

So from this, I was a bit confused and wanted to ask for clarification.

I can see that __builtin_return_address(0) [ret_addr(0)] works fine as it returns the correct return address value of 0x400554 and 0x400563.

However, for __bulitin_return_address(1) [ret_addr(1)], shouldn't the returned value be 0x40054f and 0x40055e? because those two addresses are the callq 4004e7 <foo> instruction as shown in the disassembled code (and this is what I am understanding from the description).

Instead, I get some garbage value of 0x7f609c67cb97, and this value is the same for both foo functions, which even for garbage value, I would expect both to be different.

So to summarize, what is the purpose of __builtin_return_address(1) function? Is it supposed to return the exact address of the caller of the current function? (rather than simply finding the return address). If not, is it possible to find such an address? (I am thinking this may be a bit too difficult)

I believe my question is sort of similar to this: Getting the caller's Return Address.

yugr
  • 19,769
  • 3
  • 51
  • 96
Jay
  • 373
  • 1
  • 10
  • 1
    This function only works correctly when all stack frames involved are set up with a frame pointer (i.e. if your code is compiled with `-fno-omit-frame-pointer`). If you cannot guarantee that, you need to use something like the libunwind to evaluate program meta data to find stack frames. Apart from this, you might also be able to use the `backtrace` function found in the glibc. – fuz Oct 01 '20 at 10:11
  • 3
    The argument is the nesting level. `(1)` gives you the caller of your caller, that is in the C library function that called your `main` hence it's the same for both invocations. The manual says: _"A value of 0 yields the return address of the current function, a value of 1 yields the return address of the caller of the current function, and so forth."_. – Jester Oct 01 '20 at 10:17
  • @fuz thank you for your suggestion on libunwind and backtrace, I will take a look at them. – Jay Oct 01 '20 at 10:42
  • 2
    @Jester Ah, I see where I misunderstood. Thank you very much for your clarification, I added another calling function of bar to my foo function (now calls bar inside of foo), and I was able to understand what it means. – Jay Oct 01 '20 at 10:44

1 Answers1

1

__builtin_return_address(N) return return address to N-th caller. In your case __builtin_return_address(1) would return the return the address of caller of caller of foo i.e. caller of main i.e. Glibc startup code.

The N = 0 case means the immediate caller and always works, as you see in your example. Other values (N > 0) will normally rely on frame pointers being present which are only available when you compile with -fno-omit-frame-pointer flag. When frame pointers are not available, the code generated for __builtin_return_address(N) will return garbage or even crash for non-zero N's.

yugr
  • 19,769
  • 3
  • 51
  • 96
  • 1
    *will normally rely on frame pointers* - are you sure about that? I expected (but haven't checked) it would use whatever the standard stack-unwinding mechanism is; e.g. `.eh_frame` metadata on modern GNU/Linux, same as GDB uses for backtrace, and that libc `backtrace` uses: [How does glibc backtrace() determine which stack memory are the return addresses?](https://stackoverflow.com/q/63146854). Note that GCC for several years now enables `-fomit-frame-pointer` as part of `-O2` even in 32-bit code, so it's not true that "x86" will normally have frame pointers. – Peter Cordes Oct 10 '20 at 09:24
  • @PeterCordes "I think you left a sentence unfinished" - thanks, that was an unfinished refactoring... – yugr Oct 11 '20 at 10:47
  • @PeterCordes "are you sure about that?" - yes! It's actually the reason why `__builtin_return_address` is so useless in practice. This is indeed different from how `gdb` or `backtrace(3)` work (they use unwind info in `.eh_frame` or, in case of gdb, `.debug_frame`). – yugr Oct 11 '20 at 10:52
  • @PeterCordes "GCC for several years now enables -fomit-frame-pointer" - hm, thanks for the update. Then `__builtin_return_address` depends more on presence of `-fno-omit-frame-pointer` in `CFLAGS`, rather than particular platform. I've updated the answer. – yugr Oct 11 '20 at 10:56
  • Oh you're right, that's hilariously bad. It enabled use of a frame pointer *for that function*, and then assumes the caller did the same. https://godbolt.org/z/vK6jbK shows `gcc -O1 -m32` enables `-fomit-frame-pointer`, and shows the result of `__builtin_return_address(1)` - happens to not crash in a 32-bit build, but prints bogus garbage pointed to by the saved-EBP. – Peter Cordes Oct 11 '20 at 11:20