为什么要使用EBP函数序言/结尾? [英] why to use ebp in function prologue/epilogue?

查看:197
本文介绍了为什么要使用EBP函数序言/结尾?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

前段时间我在写作尝试组装
程序与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 ,因为后者,你必须要知道的任何 / 弹出可能已调用的函数的起点和基准之间的指令。

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 calls 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆