4

I was asked in an interview to find out the address of a function without using any pointer or &. I couldn't answer his question but when i checked with him for the answer, he has given the following example in which function f1() calls function f2(). So, when function f2() gets invoked, the stack stores the return address which is nothing but the address of function f1(). In function f2() we can read the stack and then find out the address stored in stack which is of the function f1(). Can any one explain it in detail how we can read the stack from function f2() and find out the address of f1().

int main()
{
    f1();
    return 0;
}
void f1()
{
    f2();
}
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
  • 7
    That's...utterly unportable. – T.C. Jun 23 '14 at 04:28
  • There is no portable, pure-C way to do that. See e.g. http://stackoverflow.com/questions/16088040/understanding-stack-frame-of-function-call-in-c-c – tripleee Jun 23 '14 at 04:33
  • @sirpsychosexy, That involves pointers, which the question title specifically prohibits... – merlin2011 Jun 23 '14 at 04:42
  • 2
    It is a very contrived problem used in an interview to test the OP's knowledge of stack frames. – merlin2011 Jun 23 '14 at 04:43
  • In gcc you can use `register int rsp asm("rsp"); register int rbp asm("rbp");` to get the values of rsp and rbp or any other registers, hence getting the return address from stack, or use [`__builtin_return_address`](https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html) directly. In MSVC there's someway to get the stackframe [here](http://stackoverflow.com/a/1847109/995714) – phuclv Jun 23 '14 at 04:55
  • You cannot get the address of a function without using a pointer, because addresses are pointer values. If you mean without using a pointer *object* or the `&` operator, the name of the function is implicitly converted to its address. What are you supposed to do with the address once you obtain it? – Keith Thompson Jun 23 '14 at 05:23
  • Using inline assembly is kind of bending the rules (but then the assignment is probably impossible to solve without bending the rules). – tripleee Jun 24 '14 at 05:13

1 Answers1

4

With the caveat that this method is utterly unportable as T.C. mentions, and the additional caveat that it almost certainly will not work if optimizations are turned on, you can read the return address from the stack by reading off the end of a buffer, as in the example below.

int main()
{
    f1();
    return 0;
}
void f1()
{
    f2();
}
void f2() {
    char buf[4];
    printf("%p\n", *(void**)(buf + 8));
}

Note that the number 8 above will vary based on operating system, architecture, and compiler padding, so you will most likely have to try a variety of different numbers to make it work. The choice of 8 for the example assumes padding up to a 4-byte boundary, and 4-byte pointers on a 32-bit system.

You also have to make sure optimizations are turned off.

The reason why this works at all is because the structure of the stack immediately after a function call looks kind of like this.

|Return Address|
|Saved Frame Pointer|
|Local Variables|

Observe that the return address is at a higher address than the Local Variables. That is the reason that reading past the end of a buffer will allow you to potentially read the return address.

The reason why this breaks with optimizations is that compiler might decide to inline one or both functions, or realize one of them is doing nothing at all and optimize the function call away entirely, which blows aside the assumptions about the stack.

merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • 1
    Assuming that will work, it should actually give you the address `f2()` *jumps to* when its concluded, not the address of `f1()`. – Havenard Jun 23 '14 at 04:33
  • Better thinking of it, it will actually give you the backup value of register `EBP` when `f2()` was called. The return address would be `buf+12`. – Havenard Jun 23 '14 at 04:35
  • @Havenard, It is very fragile, and is merely an example. I am assuming 32-bit architecture with 4-byte pointers. – merlin2011 Jun 23 '14 at 04:35
  • Not that fragile I think, C follows a strict calling convention and you can enforce it by declaring f2 as `void __cdecl f2()` to prevent optimization issues. The only problem that remains in this case is stack alignment. – Havenard Jun 23 '14 at 04:38
  • @Havenard, Optimization could still kill `f1` though, which I believe in this example we are not permitted to change the definition of. – merlin2011 Jun 23 '14 at 04:39
  • Yeah, I guess there is that. – Havenard Jun 23 '14 at 04:40
  • you can do something with the array to make sure it isn't optimized out – phuclv Jun 23 '14 at 04:49
  • @LưuVĩnhPhúc, True, but that does not avoid the problem of the function we are actually trying to discover the address of being optimized out. In particular, the compiler might realize that the only thing `f1` does is call `f2`, so it can just inline the call and have `main` call `f2` directly. – merlin2011 Jun 23 '14 at 04:52
  • Doesn't this print out the address at which the return address is stored rather than the return address? (Assuming that the compiler decides to humor the attempt and not emit code that would chomp up the hard drive, that is.) – T.C. Jun 23 '14 at 05:01
  • @TC, I think you're right but let me think about it for a minute and correct it. – merlin2011 Jun 23 '14 at 05:02
  • I'm trying to write a solution in Ideone but it doesn't seem to accept explicit calling conventions and the optimization is obviously messing everything up, you guys know some alternative? – Havenard Jun 23 '14 at 05:04
  • @Havenard, Yes, use `gcc` on your computer, as in `localhost`. :) – merlin2011 Jun 23 '14 at 05:05
  • @Havenard, Trying to compile and run extremely non-portable C code on an online compiler is generally going to be hard. However, [koding.com](https://koding.com/Home) offers free VM's which may do the trick. – merlin2011 Jun 23 '14 at 05:07
  • @merlin2011 I don't think that works either; the result of the dereference would be a single byte rather than the 4-byte address. – T.C. Jun 23 '14 at 05:15
  • @T.C., I guess I'll have to make it ugly and cast before dereferencing. – merlin2011 Jun 23 '14 at 05:18