The ebp
register (base pointer) is often used in handling stack frames (different "levels" of the stack). While the stack pointer can change based on what you push and pop, the base pointer stays the same while you're at the same stack level.
That way, you can get at all the local variables on one side of ebp
(e.g., mov ax,[ebp-8]
) and all the passed parameters on the other side (e.g., mov ax,[ebp+12]
), along with any other locations that are relative to ebp
, such as the return code in some cases.
The reason you have the previous contents of the base pointer pushed on the stack is so that it is easy to recover the value when you move up to the previous stack frame. You just pop that value into ebp
and it's restored, meaning you can access locals and passed parameters for the next level up.
This article provides a graphical overview of how it can work, something I usually find invaluable:
+------------------+
+-> | prev-prev EBP |
| +------------------+
| | function param 2 |
| +------------------+
| | function param 1 |
| +------------------+
| | return address |
| +------------------+
+---| previous EBP | <-- current EBP
+------------------+
| local var 1 |
+------------------+
The way it works is that you push the parameters for the function, then you simply call that function. The prolog code at the start of the function is something like:
push ebp ; save old base pointer.
mov ebp, esp ; set new copy.
sub esp, 16h ; allocate space for local variables.
which saves the old ebp
and sets up a new one, pointing to the variables pushed by the calling code. The subtraction from the stack pointer is to allocate space for locals.
As stated, this means passed parameters can be gotten at with [ebp+N]
and locals with [ebp-N]
.
The epilog code is the reverse operation:
add esp, 16h ; forget about locals.
pop ebp ; restore previous value
ret ; return to calling code.
after which ebp
is now set to its previous value.