为什么在功能序言/尾声中使用ebp?

前端之家收集整理的这篇文章主要介绍了为什么在功能序言/尾声中使用ebp?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
前段时间我正在尝试编写程序集
例程并将其与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更快地解决什么?

@H_301_14@解决方法
没有要求使用堆栈框架,但肯定有一些优点:

首先,如果每个函数都使用相同的过程,我们可以使用这些知识通过反转过程轻松确定一系列调用(调用堆栈).我们知道在调用指令之后,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]

猜你在找的C&C++相关文章