前段时间我在尝试写汇编 例程并将其与 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
将是最后一次函数调用的返回地址。因此,我们可以使用类似(请原谅生锈的 C++)的方式打印调用堆栈(假设为 32 位):
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
的堆栈地址,因为您对其他函数所做的任何调用
都可能会被覆盖这些地址的数据。您应该改为在堆栈上保留空间供您的函数按常规使用:
sub esp, [number of bytes to reserve]
在此之后,初始 ESP
和 ESP - [保留字节数]
之间的堆栈区域可以安全使用。
在退出您的函数之前,您必须使用匹配释放保留的堆栈空间:
add esp, [number of bytes reserved]
关于c - 为什么要在函数序言/结语中使用 ebp?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15655553/