为什么在function序言/尾声中使用ebp?
前段时间我正在尝试编写汇编程序并将其与C程序链接,我发现我可以跳过标准的C调用序言尾声
push ebp mov ebp, esp (sub esp, 4 ... mov esp, ebp) pop ebp
只是跳过这一切,只需通过esp
,就像
mov eax, [esp+4] ;; take argument mov [esp-4], eax ;; use some local variable storage
它似乎工作得很好。 为什么使用这个ebp – 可能更快地通过ebp
解决或者是什么?
没有要求使用堆栈框架,但肯定有一些优点:
首先,如果每个函数都使用相同的过程,我们可以使用这些知识通过反转过程轻松确定一系列调用(调用堆栈)。 我们知道在call
指令之后, ESP
指向返回地址,并且被调用函数要做的第一件事就是push
当前的EBP
,然后将ESP
复制到EBP
。 因此,在任何时候我们都可以查看EBP
指向的数据,它将是之前的EBP
,而EBP+4
将是最后一个函数调用的返回地址。 因此,我们可以使用类似的东西打印调用堆栈(假设为32位)(原谅生锈的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); }
然后,这将打印出整个调用序列(以及它们的返回地址)直到那一点。
此外,由于EBP
不会改变,除非你明确地更新它(不像ESP
,当你push
/ pop
时它会改变),通常更容易引用堆栈相对于EBP
,而不是相对于ESP
,因为对于后者,你必须知道在函数开始和引用之间可能已经调用过的任何push
/ pop
指令。
正如其他人所提到的,你应该避免使用ESP
下面的堆栈地址,因为你对其他函数的任何call
都可能会覆盖这些地址的数据。 您应该通过以下方式在堆栈上保留空间以供您的函数使用:
sub esp, [number of bytes to reserve]
在此之后,初始ESP
和ESP - [number of bytes reserved]
之间的堆栈区域ESP - [number of bytes reserved]
是安全的。 在退出函数之前,必须使用匹配释放保留的堆栈空间:
add esp, [number of bytes reserved]
在调试代码时,使用EBP
非常有用,因为它允许调试器遍历调用链中的堆栈帧。
它[创建]一个单独的链表,将每个调用者的帧指针链接到一个函数。 从例如EBP的例程中,您可以恢复函数的整个调用堆栈。
见http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames
特别是它链接到的页面涵盖了您的问题: http : //blogs.msdn.com/b/larryosterman/archive/2007/03/12/fpo.aspx
它可以工作,但是,一旦你得到一个中断,处理器会将它的所有寄存器和标志推入堆栈,覆盖你的值。 堆栈是有原因的,使用它…