6
votes

I know this topic has been covered ad nauseam here, and other places on the internet - but hopefully the question is a simple one as I try to get my head around assembly...

So if i understand correctly the ebp (base pointer) will point to the top of the stack, and the esp (stack pointer) will point to the bottom -- since the stack grows downward. esp therefore points to the 'current location'. So on a function call, once you've saved the ebp on the stack you insert a new stack frame - for the function. So in the case of the image below, if you started from N-3 you would go to N-2 with a function call. But when you are at N-2 - is your ebp == 25 and the esp == 24 (at least initially, before any data is placed on the stack)?

Is this correct or am I off on a tangent here?

Thanks!

http://upload.wikimedia.org/wikipedia/en/a/a7/ProgramCallStack2.png
(source: wikimedia.org)

3
I'm going to go ahead and tag this as C as it sounds like you're trying to understand what code a C compiler generates does.. Feel free to remove that tag if that is not right.Earlz

3 Answers

4
votes

This actually depends upon not only the hardware architecture and the compiler, but also the calling convention, which is simply an agreed-upon way in which functions work with the stack to call one another. In other words, there are different orders in which a function can push things onto the stack, depending on your compiler settings (and peculiar #pragma options, etc, etc).

It looks like you are talking about the cdecl calling convention on the x86 architecture. In that case, the caller's ebp is usually pushed onto the stack immediately after the return address. So, in your example's N-2, location 25 will contain a pointer back to the calling function N-3 (ie, it will contain the address of the instruction immediately after the call that got you into N-2) and location 24 will contain the old ebp, and your esp will = 23 immediately after the call, before any locals have been pushed onto stack. (Except some compilers will make space on the stack immediately after the call, and so ESP will be 20 instead of moving up and down inside function N-2.)

However be aware that on the x86 there is a particular optimization the compiler can sometimes do called frame pointer omission, which avoids pushing the old ebp onto the stack altogether under certain conditions.

3
votes
  1. After calling into N-3, ebp is 28, and esp is 25.
  2. The old ebp is pushed, and then the ebp is set to the current value of esp. Now both esp and ebp are 24.
  3. Finally, esp is adjusted to make room for local variables. esp is likely now 20, depending on how the function behaves when calling N-2.

The best way to get your head around this, is read about function prologues, and familiarize yourself with the x86 implementation. It also helps to accept that esp and ebp are used to localize use of the stack in each function, with some variation between compilers, architectures and platforms (and almost irrelevant to the user of any language higher level than, or equal to C).

1
votes

It depends on the platform, but this is generally how things work.

On the architectures with which I am most familiar, the "calling" (aka return) address is in the $ra register and the stack is wherever it was left by the caller. So what happens is the return address gets pushed onto the stack as does your (caller's) base pointer, and then the base pointer is updated to point where the stack is and the stack keeps crawling up. The exact order of where things get pushed and what's set when I don't recall, but typically it's up to the callee to save off the registers that are going to be clobbered. That way the calling function doesn't need to save everything if the function being called only uses one or two registers. (Actually, the return address register is the same- it won't be pushed onto the stack if the function doesn't call anything else.)

This is actually pretty easy to follow if you disassemble a program and take a peek at the function prologue and epilogue. They all follow pretty common patterns of "store everything" up top and "restore everything" at the bottom. (Note that there are sometimes "special" registers which are never stored or restored and the compiler knows to expect that it can only count on the values being coherent if there were no function calls. On MIPS I think they're the S registers, and PPC calls them t?)