"To my eye if you pushed anything onto the stack during the function it would still be there when the pop ebp happens[…]"
No, because immediately before the pop ebp
instruction, you have this:
mov esp, ebp ;Restoring the saved value of esp
Remember that esp
is essentially the address of the "top" of the stack. Pushing onto and popping from the stack changes this register. So if you change this register, you're changing the location where the next push
or pop
will happen.
So the above instruction, mov esp, ebp
essentially resets the stack pointer to the location it had immediately after the initial push ebp
. (That location was saved to the ebp
register right through the mov ebp, esp
instruction.)
That's why pop ebp
will pop the right thing.
This does however assume that your function did not change the ebp
register.
Update:
I am assuming a certain calling convention here, but let's make an example. Let's say we have a function that takes one 32-bit argument, which gets passed to the function via the call stack.
In order to call our function, we do this:
push eax ; push argument on stack
call fn ; call our function; this pushes `eip` onto the stack
The first thing fn
does is to set up its own stack frame (and making sure that the previous one can be restored at the end):
push ebp ; so we can later restore the previous stack frame
mov ebp, esp ; initialize our own function's stack frame
sub esp, 8 ; make room for 8 bytes (for local variables)
The sub esp, 8
is like pushing 8 bytes onto the stack, only that nothing will be written to the memory locations; so we essentially end up with 8 uninitialized bytes; this is the the memory area our function can use for local variables. It can refer to these local variables via e.g. [ebp-4]
or [ebp-8]
, and it can refer to its 32-bit argument via [ebp+8]
(skipping over the pushed ebp
and eip
).
During your function, the stack might then look like this:
+------------+ | "push" decreases "esp"
| <arg> | |
+------------+ <-- ebp+8 |
| <prev eip> | v
+------------+ <-- ebp+4
| <prev ebp> |
+------------+ <-- ebp
| <locals> |
+------------+ <-- ebp-4
| <locals> | ^
+------------+ <-- ebp-8 |
| ... | |
+------------+ <-- esp | "pop" increases "esp"
At the end of the function, this will happen:
mov esp, ebp ; "pop" over local variables and everything else that was pushed
pop ebp ; restore previous stack frame
And finally:
ret ; essentially this does a "pop eip" so program execution gets
; transferred back to instruction after the "call fn"
(PS: The calling code would have to pop the arguments passed to the function, e.g. by doing a add esp, 4
right after the call fn
.)
I haven't done assembly language in a long time so this is all from memory. I might be off on some of the fine points, but I hope you get the general picture.
call
andret
instructions for that.) – stakx - no longer contributing