Question

Despite looking at textbooks trying to grasp this, I'm having trouble.

    0x08048b29 <func+0>: push   %ebp
    0x08048b2a <func+1>: mov    %esp,%ebp
    0x08048b2c <func+3>: push   %ebx
    ...
    0x08048b30 <phase_2+7>: lea    -0x28(%ebp),%eax

In the lea instruction, I understand that %eax gets the value at 0x28 before %ebp, but where is that exactly? Is it 0x8048b29 - 0x28 (0x28 before the beginning of the function) or what?

Was it helpful?

Solution

The ebp register is typically used within a function to access any arguments passed to the function. Before this function is called, any arguments not passed by register are pushed onto the stack. At the start of this function, the calling function's base pointer is saved.

(1) 0x08048b29 <func+0>: push %ebp

The new top of the stack is copied into ebp to serve as the base pointer in this function.

(2) 0x08048b2a <func+1>: mov %esp,%ebp

Finally, ebx is saved, which was likely a variable passed to the function in a register.

(3) 0x08048b2c <func+3>: push %ebx

After all of this function entry code, the ebp register is pointing into the middle of the stack. Above it on the stack (toward the newest pushed items) is the ebx value that was pushed in #3 above. Below it on the stack are the old ebp from the calling function (saved in #1 above) and MOST importantly... any arguments passed to this function via the stack (done before the function was called).

(4) lea -0x28(%ebp),%eax

The final instruction is referring to one of those arguments passed by the stack. ebp ends up pointing into the stack, not at any of the functions code addresses. This is pretty normal for ebp, it is most often used as a pointer into the stack.

OTHER TIPS

Look at this from the context of calling that function. The code which does so looks something like:

caller+...: push argN
caller+...: ...
caller+...: push arg0
caller+...: call func

i.e. the arguments are put onto the stack in such order that, on entry to func(), the stack will have the following layout:

[esp+(N*4)] : argN
...         : arg(N-1)...arg2
[esp+4    ] : arg1
[esp      ] : <return address to caller()+...>

Then you perform the push %ebp; mov %esp, %ebp sequence, which changes %esp (by -4) so that your layout now is:

[ebp+4+(N*4)][esp+(N*4)] : argN
...                      : arg(N-1)...arg2
[ ebp+8     ][esp+8    ] : arg1
[ ebp+4     ][esp+4    ] : <return address to caller()+...>
[ ebp       ][esp      ] : <saved %ebp of caller>

The code then goes on to push a few more registers on the stack - since that grows downwards each time %esp is changed by -4. Ultimately (which you haven't shown in your disassembly but it'll be there) you'll have an instruction subl $..., %esp. Which is what allocates space for your local variables. The final stack layout is something like:

[ebp+4+(N*4)][         ] : argN
...                      : arg(N-1)...arg2
[ ebp+8     ][         ] : arg1
[ ebp+4     ][         ] : <return address to caller()+...>
[ ebp       ][         ] : <saved %ebp of caller>
[ ebp-4     ][         ] : <saved %ebx of caller>
[ ebp-8     ][         ] : ...
...                      : region for local variables
[ ebp-??    ][ esp     ] : end of stack for func()

Any address between [esp ... ebp-4] is within what's called the stack frame for your function, and it contains either a register saved on behalf of the caller (like ebx in the case of the disassembly shown by you) or local variables.

Hence, when in your code you see any access to %ebp - XX it's in the local variable space, if you see %ebp + YY it's within the space that contains the function arguments.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top