为什么要使用EBP函数序言/结尾? [英] why to use ebp in function prologue/epilogue?
问题描述
前段时间我在写作尝试组装
程序与C程序链接它,我发现,
我只可以跳过标准的C-通话序幕尾声
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
跳过这一切和地址,只需尤其
,像
mov eax, [esp+4] ;; take argument
mov [esp-4], eax ;; use some local variable storage
这似乎工作相当不错。为什么用这个EBP - 也许是
通过 EBP
更快的处理还是什么?
推荐答案
有没有规定使用栈帧,但也有一定优点:
There's no requirement to use a stack frame, but there are certainly some advantages:
首先,如果每一个功能都有使用同一过程中,我们可以利用这些知识很容易被倒车过程中确定调用序列(调用栈)。我们知道,一个呼叫
指令后, ESP
指向返回地址,那第一件事被调用函数会做的是推
当前 EBP
,然后复制 ESP
进入 EBP
。因此,在任何时候,我们可以看一下数据指向 EBP
这将是previous EBP
而 EBP + 4
将是最后一个函数调用的返回地址。因此,我们可以使用类似(原谅生锈C ++)打印调用堆栈(假设32位):
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.
此外,由于 EBP
不会改变,除非你明确地更新(不同于 ESP
,当你而改变推
/ 弹出
),它通常更容易堆栈相对的参考数据 EBP
,而不是相对于 ESP
,因为后者,你必须要知道的任何推$ C $的C> /
弹出
可能已调用的函数的起点和基准之间的指令。
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.
正如其他人所提到的,你应该避免使用堆栈地址在的 ESP
任何呼叫
就是你做的其他功能有可能在这些地址覆盖数据。你应该平时,而不是保留在堆栈上使用的空间由你的函数:
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]
在此之后,之间的堆栈区域的初始 ESP
和 ESP - [字节数保留]
可以放心使用。
退出你的函数之前,你必须使用一个匹配的释放了保留的堆栈空间:
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]
这篇关于为什么要使用EBP函数序言/结尾?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!