4
votes

I have two questions about the EBP register.

I understand ESP and EIP. However, I don't really understand why one would use EBP.

In the code below, I push the EBP register (which is actually 0000000) to the stack. I then move the memory address of the stack to EBP so that ESP and EBP have the same data. This is the prolog. The there is some code which finishes with the syscall. Then I do the reverse (the epilog) as 'leave' indicates that I move EBP to ESP (these values are the same thanks to the prolog) then pop the last value of the stack (which is EBP which is 00000000) to EBP. This gives EBP the same value as happened before the prolog.

Why would anyone do this? What is the point? Please answer in a simple way! Remember that I do not grasp what EBP (the frame pointer) actually does.

EDIT: or is it that this is a way to effectively backup the stack (ESP) when in a function? In other words: the program can do what it does with the stack and the 'original stack' will always be there in EBP. Then when the program finishes, EBP is put back to how it was before. Is this correct? If so, the epilog is just a tidying up routine?

Also, AIUI, I can use 'enter' to replace 'push ebp / mov ebp, esp'. Yet when I try to compile in nasm, I get 'error: invalid combination of opcode and operands' 'leave' works fine; 'enter' does not. What is the correct syntax?

Thanks!

Example:

    push ebp
    mov, ebp, esp 

    [some code here]
    int 0x80

leave
ret   
3
What you are missing is called stack frame.user1129665
The syntax for enter is enter 0, 0. The first parameter is the number of bytes to reserve for local variables (sub esp, ???). The second parameter is "lex level" - you don't wanna know, just make it zero.Frank Kotler
Never use enter. It's very slow compared to push/mov, and only saves a tiny bit of code bytes if you can also replace a sub esp, imm to reserve some stack space. It's larger (32b) or the same size (64b) as just push / mov. leave doesn't have potential crazy-CISC semantics, so it isn't slow and is worth using on some microarchitectures.Peter Cordes

3 Answers

1
votes

EBP forms a fixed point of reference to variables in stack: mainly all parameters to a function, all local parameters of the function and finally the return address. With this fixed point a function can grow/alter it's stack almost randomly, jump to the function epilogue from where ever and restore the stack pointer to the original position.

The concept was next to mandatory, since original 8086 code didn't allow the stack pointer to be used with displacement as in mov ax, [sp + 10], but only with push and pop. Reference to anything else but the top element needed to be done with mov xx, [bp + 10].

1
votes

The idea of EBP is indeed to form a fixed point of reference. Often you may fiddle about with the stack pointer (e.g. while pushing parameters onto the stack ready for a call) and find it a real pain to figure out where some piece of data is relative to the stack pointer. But relative to the base pointer it is always the same. Modern compilers have no difficulty working this out, but if you wanted to write a big piece of assembler code (by hand) that uses the stack for pushing and popping, you would find it easier to reference your local variables relative to a register (EBP) that does not change.

0
votes

enter also needs a number which is the amount of space to allocate, which is the key to your question: these instructions are made to set up space for your function's local variables.

Local variables are referred to through the EBP register. Let me show you an example:

import core.stdc.stdio;
void main() {
    int a = 8;
    a += 8;
    printf("%d\n", 8);
}

(This is D code but that's not really relevant)

Disassembly of section .text._Dmain:

00000000 <_Dmain>:
   0:       55                      push   ebp
   1:       8b ec                   mov    ebp,esp
   3:       83 ec 04                sub    esp,0x4
   6:       b8 08 00 00 00          mov    eax,0x8
   b:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
   e:       01 45 fc                add    DWORD PTR [ebp-0x4],eax
  11:       50                      push   eax
  12:       b9 00 00 00 00          mov    ecx,"%d\n"
  17:       51                      push   ecx
  18:       e8 fc ff ff ff          call   printf
  1d:       31 c0                   xor    eax,eax
  1f:       83 c4 08                add    esp,0x8
  22:       c9                      leave
  23:       c3                      ret

Let's break this up into each part:

   0:       55                      push   ebp
   1:       8b ec                   mov    ebp,esp
   3:       83 ec 04                sub    esp,0x4

This is the function prolog, setting up ebp. The sub esp, 0x4 pushed the stack 4 bytes away - this makes room for our local int a variable, which is 4 bytes long.

The enter instruction is rarely used, but I believe enter 4,0 does this same thing - enter a function with 4 bytes of local variable space. edit: The other 0 is the nesting level, I've never seen it used... enter is generally slower than just doing the steps yourself as the compiler does here. /edit

   6:       b8 08 00 00 00          mov    eax,0x8
   b:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax

This is the a=8 line - the second line stores the value in the local variable's memory.

   e:       01 45 fc                add    DWORD PTR [ebp-0x4],eax

Then, we add to it in a+=8 (the compiler reused eax here since it recognized that the number is the same...)

After that, it calls printf by pushing its arguments to the stack, then zeroes out the eax register (xor eax, eax), which is how D returns 0 from a function.

  11:       50                      push   eax
  12:       b9 00 00 00 00          mov    ecx,"%d\n"
  17:       51                      push   ecx
  18:       e8 fc ff ff ff          call   printf
  1d:       31 c0                   xor    eax,eax
  1f:       83 c4 08                add    esp,0x8

Note that the add esp, 0x8 here is part of the call to printf: the caller is responsible for cleaning up the arguments after calling the function. This is needed because only the caller knows how many args it actually sent - this is what enables printf's variable arguments.

Anyway, finally, we clean up our local variables and return from the function:

  22:       c9                      leave
  23:       c3                      ret

edit: leave btw expands to mov esp, ebp; pop ebp; - it is exactly the opposite of the setup instructions, and like Aki Suihkonen said in the other answer, a nice thing here is the stack is restored to how it was at function entrance, no matter what happened inside the function (well, unless the function totally destroyed the stack's contents, in which case your program will most likely soon crash). /edit

So, bottom line, the ebp stuff is all about your local variables. It uses esp to get started so it has a nice memory space that won't step on other functions (it is on the call stack), but moves it to ebp so your locals remain at a consistent offset throughout the function - the variable a is ALWAYS [EBP-4] in this function, even as the stack is manipulated.

It is easiest to see in action by disassembling a function you write in C or something, like we did here. The linux command objdump -d -M intel somefile.o is what I used (then I manually fixed up some minor things to make it more readable. If you disassemble a .o file not all library calls are resolved yet so it can look kinda weird, but that doesn't affect the local variable stuff!)