Search code examples
functionassembly64-bitx86-64cpu-registers

%rbp vs Return Address in Assembly?


I'm confused a little with my book, take a look at this:

f:
    pushl %ebp        # store old ebp
    movl %esp, %ebp   # move ebp to top
    movl $73, %eax    # make f return 73
    popl %ebp         # restore old ebp from stack
    ret               # pop return address and jump

I have a few question to make sure I understand:

  1. Is return address actually the value of rbp the very first time we entered main's frame?

  2. If so, why it's not called old rbp as others? what's if f calls another function should we call it return address or rbp?

  3. return address is the return address for main and has nothing to do with f() right?


Solution

  • The return address is a pointer to code that is pushed onto the stack by the caller, typically by a call instruction.  Since that is pushed by the caller, that value (and any others pushed by the caller) are necessarily on the stack before the first machine code instruction of f runs.  The convention requires that the return address is the top thing on the stack upon (the control flow) transition from caller to callee.

    So, just before the start of f there's return address on the stack.  The return address is sometimes called linkage in older texts.  It is a parameter passed by the caller that the callee uses to know where to return.  Passing the return address as a parameter for the callee to use allows the function (here f) to be called from many different places (at many different depths of the call chain), and always return to its caller, no matter who that is.

    The return address and rbp are different.  We cannot say how rbp is used by the caller here b/c we don't see the caller.  However, the rbp register is required — by the calling convention — to retain its original value upon return from callee to caller.  Thus, since this callee chooses to modify the rbp value (for no apparent reason, though), it is necessary to also restore the original rbp value before returning, which it does.

    It is called an old rbp b/c at the time of transition from caller to callee, the rbp value belongs to someone higher up in the call stack, and is not the rbp value that pertains to f (but rather to fs callers).

    We can't say what is in rbp that belongs to the callers, however, f, chooses to use rbp as a pointer to data, specifically stack data (it doesn't use it though).

    1. Is return address actually the value of rbp the very first time we entered main's frame?

    No.  rbp is generally used as a pointer to the stack, called a frame pointer, it is separate from the return address.

    1. If so, why it's not called old rbp as others? what's if f calls another function should we call it return address or rbp?

    If f calls another function there will be at least 2 return addresses on the stack.  rbp is another matter, and if main and f use a frame pointer (and f calls another function) then there will be at least one frame pointer (mains as f put it there) on the stack as well.

    1. return address is the return address for main and has nothing to do with f() right?

    We can't say from this snippet that main called ff should be callable by virtually any caller.  If main did call f then the return address represents the dynamic linkage of main calling f.  It would be ok, for example, for main to call f from two different places in mains implementation.  At each different call site in main, a different code address would be passed to f as the return address, and f would use the passed return address parameter to dynamically return to the proper caller & call site.

    The return address should be thought of as an additional (but hidden from C) parameter, so it is passed by the caller but belongs to the callee just like the other (explicitly seen in C) parameters.  We cannot call f without passing that return address parameter — if we didn't f would get confused, in finding its other parameters, and also in returning upon completion.


    Suggest you try a small example and single step in the debugger.  Keep an eye on stack pushes (and the value pushed) & pops and watch the control flow transition from caller to callee and all the way back again.