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:
- top of main's stack frame
- stack space for main - in this case, enough to align on 16 bytes
0x0804841b return address for call foo - pointer to
1. - stack space for foo
0x080483e7 return address for call bar - pointer to
4. - stack space for bar
0x080483f9 return address for call quux - pointer to
7. - 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.