前段时间我正在尝试编写程序集
例程并将其与C程序链接,我发现了
我可以跳过标准的C-call序言尾声
例程并将其与C程序链接,我发现了
我可以跳过标准的C-call序言尾声
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更快地解决什么?
解决方法
没有要求使用堆栈框架,但肯定有一些优点:
首先,如果每个函数都使用相同的过程,我们可以使用这些知识通过反转过程轻松确定一系列调用(调用堆栈).我们知道在调用指令之后,ESP指向返回地址,并且被调用函数要做的第一件事就是推送当前的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,当你推/弹时它会改变),通常更容易引用堆栈相对于EBP的数据,而不是相对于ESP,因为对于后者,你必须知道在函数的开始和引用之间可能已经调用过的任何push / pop指令.
正如其他人所提到的,你应该避免使用ESP下面的堆栈地址,因为你对其他函数的任何调用都可能会覆盖这些地址的数据.您应该通过以下方式在堆栈上保留空间以供您的函数使用:
sub esp,[number of bytes to reserve]
在此之后,初始ESP和ESP之间的堆栈区域 – [保留的字节数]是安全的.
在退出函数之前,必须使用匹配释放保留的堆栈空间:
add esp,[number of bytes reserved]