11

When execution enters a new function by performing call I do often see this code template (asm list generated by Gnu Debugger when in debugging mode):

0x00401170  push   %ebp
0x00401171  mov    %esp,%ebp
0x00401173  pop    %ebp

So what's the purpose of moving esp to ebp?

  • Dunno if these sort of things are on topic here? Anyway see here: stackoverflow.com/questions/2515598/push-ebp-movlesp-ebp – cb88 May 22 '13 at 17:00
  • I think yes, albeit very basic, as disassembly questions are on topic. Furthermore, there are reasons to use frame pointers that are more related to reverse engineering (e.g. debugging, runtime code analysis) than others (e.g. easy to implement alloca(), ease of remembering offsets) – Robert Mason May 22 '13 at 18:01
18

Moving esp into ebp is done as a debugging aid and in some cases for exception handling. ebp is often called the frame pointer. With this in mind, think of what happens if you call several functions. ebp points to a block of memory where you pushed the old ebp, which itself points to another saved ebp, etc. Thus, you have a linked list of stack frames. From these, you can look at the return addresses (which are always 4 bytes above the frame pointer in the stack frame) to find out what line of code called a stack frame in question. The instruction pointer can tell you the location of current execution. This allows you to generate a stacktrace which is useful for debugging by showing the flow of execution throughout a program.

As a practical example consider the following code:

void foo();
void bar();
void baz();
void quux();

void foo() {
    bar();
}

void bar() {
    baz();
    quux();
}

void baz() {
    //do nothing
}

void quux() {
    *(int*)(0) = 1; //SEGFAULT!
}

int main() {
    foo();
    return 0;
}

This generates the following assembly (with Debian gcc 4.7.2-4 gcc -m32 -g test.c, snipped):

080483dc <foo>:
 80483dc:   55                      push   %ebp
 80483dd:   89 e5                   mov    %esp,%ebp
 80483df:   83 ec 08                sub    $0x8,%esp
 80483e2:   e8 02 00 00 00          call   80483e9 <bar>
 80483e7:   c9                      leave  
 80483e8:   c3                      ret    

080483e9 <bar>:
 80483e9:   55                      push   %ebp
 80483ea:   89 e5                   mov    %esp,%ebp
 80483ec:   83 ec 08                sub    $0x8,%esp
 80483ef:   e8 07 00 00 00          call   80483fb <baz>
 80483f4:   e8 07 00 00 00          call   8048400 <quux>
 80483f9:   c9                      leave  
 80483fa:   c3                      ret    

080483fb <baz>:
 80483fb:   55                      push   %ebp
 80483fc:   89 e5                   mov    %esp,%ebp
 80483fe:   5d                      pop    %ebp
 80483ff:   c3                      ret    

08048400 <quux>:
 8048400:   55                      push   %ebp
 8048401:   89 e5                   mov    %esp,%ebp
 8048403:   b8 00 00 00 00          mov    $0x0,%eax
 8048408:   c7 00 01 00 00 00       movl   $0x1,(%eax)
 804840e:   5d                      pop    %ebp
 804840f:   c3                      ret    

08048410 <main>:
 8048410:   55                      push   %ebp
 8048411:   89 e5                   mov    %esp,%ebp
 8048413:   83 e4 f0                and    $0xfffffff0,%esp
 8048416:   e8 c1 ff ff ff          call   80483dc <foo>
 804841b:   b8 00 00 00 00          mov    $0x0,%eax
 8048420:   c9                      leave  
 8048421:   c3                      ret    

Note that leave is the same as:

mov %ebp, %esp
pop %ebp

With this in mind, and the standard-ish C calling convention on x86, we know that the stack at the segfault is going to look like:

  1. top of main's stack frame
  2. stack space for main - in this case, enough to align on 16 bytes
  3. 0x0804841b return address for call foo
  4. pointer to 1.
  5. stack space for foo
  6. 0x080483e7 return address for call bar
  7. pointer to 4.
  8. stack space for bar
  9. 0x080483f9 return address for call quux
  10. pointer to 7.
  11. stack space for quux

The instruction pointer will be 0x08048408. ebp will point to 10..

At this point, the processor generates an exception, which the operating system processes. It then sends SIGSEGV to the process, which obligingly terminates and dumps core. You then bring up the core dump in gdb with gdb -c core, and you type in file a.out and bt, and it gives you in response:

#0  0x08048408 in quux () at test.c:20
#1  0x080483f9 in bar () at test.c:12
#2  0x080483e7 in foo () at test.c:7
#3  0x0804841b in main () at test.c:24

#0 is generated from the instruction pointer. Then, it goes to ebp (10), looks at the previous item on the stack (9), and generates #1. It follows ebp (i.e. mov %ebp, (%ebp)) to (7), and looks 4 bytes above that (6) to generate #2. It finally follows (7) to (4) and looks at (3) to generate #3.

Note: This is but one way of doing such stack tracing. GDB is very, very smart, and can perform the stack trace even when you use -fomit-frame-pointer. However, in a very basic implementation this is probably the simplest way to generate a stack trace.

  • why not use gcc -S in the first place and get annotated code? – 0xC0000022L May 22 '13 at 16:37
  • I wanted to have real memory addresses that would match up with a stack trace in a live program. I also think objdump/gdb examples fit the rev-eng theme better. – Robert Mason May 22 '13 at 17:36
16

I like Robert explanation, it has a very good example, but.. I think it misses the point of which is the real purpose of this instruction.

is done as a debugging aid and in some cases for exception handling

Well.. not really, not only. It is part of the standard function prologue for x86 (32 bit), and it is the (common) technique to set up a function stack frame, so that parameters and locals are accessible as fixed offsets of ebp, which is, after all, the *B*ase frame *P*ointer.

Making ebp equal to esp at function entry, you will have a fixed, relative pointer inside the stack, that will not change for the lifetime of your function, and you will able to access parameters and locals as (fixed) positive and (fixed) negative offsets, respectively, to ebp.

You can or cannot see this standard prologue in release, optimized code: optimizers can do (and often do) FPO (frame pointer optimization) to get rid of ebp and just use esp inside your function to access params and locals. This is much trickier (I would not do it by hand) as esp can vary during the function lifetime, and therefore a parameter, for example, can be accessed using 2 different offsets at two distinct points in the code.

  • That is true, however in a large amount of compiled code the stack pointer doesn't change over the duration of the function as the compiler just subs once and uses fixed offsets vice push/pop. For purposes of reverse engineering I'm much more interested in what ebp can tell me about the structure of the program than how it makes the programmer's life easier (especially in compiled code). – Robert Mason May 26 '13 at 20:59
  • That being said, thanks for filling the (rather gaping) hole I left in my explanation. – Robert Mason May 26 '13 at 21:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.