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.
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.)