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 ?
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.
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.
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 call
s 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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With