Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why to use ebp in function prologue/epilogue?

Some time ago I was experimenting with writing assembly routines and linking it with C programs and I found that I just can skip standard C-call prologue epilogue

    push ebp
    mov ebp, esp
    (sub esp, 4
    ...
    mov esp, ebp)
    pop ebp

just skip it all and adress just by esp, like

    mov eax, [esp+4]          ;; take argument
    mov [esp-4], eax          ;; use some local variable storage

It seem to work quite good. Why this ebp is used - is maybe addressing through ebp faster or what ?

like image 965
user2214913 Avatar asked Mar 27 '13 09:03

user2214913


People also ask

What is meant by subroutine prologue and epilogue?

The prolouge is what happens at the beginning of a function. Its responsibility is to set up the stack frame of the called function. The epilog is the exact opposite: it is what happens last in a function, and its purpose is to restore the stack frame of the calling (parent) function.

What does the Prolog do to the stack frame base pointer value?

The prologue is responsible for setting up the stack frame, initializing the frame pointer register, saving registers that must be saved, and allocating size additional bytes of storage for the local variables.


1 Answers

There's no requirement to use a stack frame, but there are certainly some advantages:

Firstly, if every function has uses this same process, we can use this knowledge to easily determine a sequence of calls (the call stack) by reversing the process. We know that after a call instruction, ESP points to the return address, and that the first thing the called function will do is push the current EBP and then copy ESP into EBP. So, at any point we can look at the data pointed to by EBP which will be the previous EBP and that EBP+4 will be the return address of the last function call. We can therefore print the call stack (assuming 32bit) using something like (excuse the rusty C++):

void LogStack(DWORD ebp)
{
    DWORD prevEBP = *((DWORD*)ebp);
    DWORD retAddr = *((DWORD*)(ebp+4));

    if (retAddr == 0) return;

    HMODULE module;
    GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const char*)retAddr, &module);
    char* fileName = new char[256];
    fileName[255] = 0;
    GetModuleFileNameA(module, fileName, 255);
    printf("0x%08x: %s\n", retAddr, fileName);
    delete [] fileName;
    if (prevEBP != 0) LogStack(prevEBP);
}

This will then print out the entire sequence of calls (well, their return addresses) up until that point.

Furthermore, since EBP doesn't change unless you explicitly update it (unlike ESP, which changes when you push/pop), it's usually easier to reference data on the stack relative to EBP, rather than relative to ESP, since with the latter, you have to be aware of any push/pop instructions that might have been called between the start of the function and the reference.

As others have mentioned, you should avoid using stack addresses below ESP as any calls you make to other functions are likely to overwrite the data at these addresses. You should instead reserve space on the stack for use by your function by the usual:

sub esp, [number of bytes to reserve]

After this, the region of the stack between the initial ESP and ESP - [number of bytes reserved] is safe to use. Before exiting your function you must release the reserved stack space using a matching:

add esp, [number of bytes reserved]
like image 180
Iridium Avatar answered Nov 08 '22 08:11

Iridium