pointersassemblyx86stack

What happens with the Frame Pointer in the Stack, if the Stack Pointer enters a second, nested function?


I am trying to learn x86. (IA-32 architecture) Today I learned about the Stack. This is what I think I understood:

The StackPointer (SP) points to the "top" of the stack (smallest address) and is stored in a the ESP register The Frame Pointer (FP) enables accessing arguments and local variables as it is the "pop address" and therefore static even if the SP moves further inside the Stack.

int addSome(int arg1, int arg2);

int main(void){
  int answer = addSome(1,2);
}


int addSome(int arg1, int arg2){
  return arg1 + arg2;
}

The stack should look like that:

Stack where Frame Pointer shows "pop address"

What happens with the FP if there are nested functions? It can't be moved up, as its location will be needed if the sub-function is poped.

For visualization; adding this function, which will be called inside addSome():

int subSome(int arg3, int arg4);
int subSome(int arg3, int arg4){
  return arg3 - arg4;
}

How is the FP handled here? How is the new "pop address" known and the location of the new arguments? Stack where Frame Pointer shows "pop address" of first function, second is unknown

My guess would be that the stack size of addSome will be saved somewhere the get the location of the new "pop address" relative to the FP:

Stack where Frame Pointer has relative difference to new "pop address"

But that stack size would have to be saved somewhere and if there are more nested functions, there would have to be more of those places to store those sizes, which I believe can't be true.


Solution

  • First of all, in x86, the register we use to address values in the stack frame is called the "base pointer", or ebp. I do not know why you call it fp.

    Secondly, we do not need to discuss subSome(), because we already have a second, nested function, since we have two functions: main() and addSome().

    The standard prologue and epilogue of a function in x86 looks like this:

    push ebp       ; save caller's ebp
    mov ebp, esp   ; make ebp point to the frame of this function
    ...            ; main function body goes here
    pop ebp        ; restore caller's ebp
    ret            ; return to caller
    

    A normal, real-world, physical stack (say, a stack of cards) grows from bottom to top. When you add one more card to the stack, it goes to the top of the stack. Not so in x86; in x86 the stack grows from top to bottom. The push <32-bit-operand> instruction decrements the esp register by 4, and then stores the 32-bit operand to the memory address pointed by the new value of the esp register. The pop <32-bit-operand> instruction does the reverse.

    Furthermore, the return value is usually not saved on the stack. The Details depend on the ABI (Application Binary Interface) in effect, but it is usually returned in the eax register. Perhaps you meant the return address, which is, in fact, pushed to the stack by the call instruction and popped from the stack by the ret instruction.

    So, within addSome(), your stack would look like this:

    top-of-stack        `argc` parameter to `main()` pushed by the standard library
    top-of-stack - 4    `argv` parameter to `main()`  pushed by the standard library
    top-of-stack - 8    return address of standard library code that invoked `main()`
    top-of-stack - 12   ebp that was saved by `main()`
    top-of-stack - 16   arg1 parameter to `addSome()` (value: 1)
    top-of-stack - 20   arg2 parameter to `addSome()` (value: 2)
    top-of-stack - 24   return address back to `main()`
    top-of-stack - 28   ebp that was saved by `addSome()` <-- esp points here
    

    (note that top-of-stack usually will not be the real top of the stack; there will be some more stuff above everything else, pushed by the standard library code which invoked your main(). But the part shown is the part that we care about.)