基指针和堆栈指针到底是什么?它们指向什么?

发布于 2024-08-04 08:56:00 字数 1100 浏览 5 评论 0原文

使用来自 Wikipedia 的 此示例,其中 DrawSquare() 调用 <代码>DrawLine():

(请注意,该图底部有高地址,顶部有低地址。)

任何人都可以向我解释一下 ebpesp 是什么在这种情况下?

据我所知,堆栈指针始终指向堆栈顶部,而基指针始终指向当前函数的开头?正确的?


编辑:我的意思是在 Windows 程序的上下文中。

edit2:eip 是如何工作的?

edit3: 我有来自 MSVC++ 的以下代码:

var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr  8
hPrevInstance= dword ptr  0Ch
lpCmdLine= dword ptr  10h
nShowCmd= dword ptr  14h

所有这些代码似乎都是双字,因此每个占用 4 个字节。所以我可以看到从 hInstancevar_4 有 4 个字节的间隙。这些是什么?我假设它是返回地址,正如维基百科的图表中所示。


(编者注:删除了Michael的回答中的长引用,该引用不属于问题,但编辑了后续问题):

这是因为函数调用的流程是:

  • 推送参数(hInstance等)
  • 调用函数,它推送返回地址
  • Push ebp
  • 为局部变量分配空间

我的问题(我希望是最后一个!)现在是,从我弹出我想要调用的函数的参数到结束的那一刻到底发生了什么序言?我想知道 ebp、esp 在这些时刻是如何演变的(我已经理解了 prolog 是如何工作的,我只想知道在将参数压入堆栈之后和 prolog 之前发生了什么)。

Using this example coming from Wikipedia, in which DrawSquare() calls DrawLine():

diagram of stack with annotations

(Note that this diagram has high addresses at the bottom and low addresses at the top.)

Could anyone explain to me what ebp and esp are in this context?

From what I see, I'd say the stack pointer points always to the top of the stack, and the base pointer to the beginning of the current function? Right?


edit: I mean this in the context of Windows programs.

edit2: And how does eip work, too?

edit3: I have the following code from MSVC++:

var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr  8
hPrevInstance= dword ptr  0Ch
lpCmdLine= dword ptr  10h
nShowCmd= dword ptr  14h

All of them seem to be dwords, thus taking 4 bytes each. So I can see there is a gap from hInstance to var_4 of 4 bytes. What are they? I assume it is the return address, as can be seen in the diagram from Wikipedia.


(editor's note: removed a long quote from Michael's answer, which doesn't belong in the question, but a followup question was edited in):

This is because the flow of the function call is:

  • Push parameters (hInstance, etc.)
  • Call function, which pushes return address
  • Push ebp
  • Allocate space for locals

My question (last one, I hope!) now is, what exactly happens from the instant I pop the arguments of the function I want to call up to the end of the prolog? I want to know how the ebp, esp evolve during those moments (I already understood how the prolog works, I just want to know what is happening after I pushed the arguments on the stack and before the prolog).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

走野 2024-08-11 08:56:00

esp 正如你所说,是堆栈的顶部。

ebp 通常在函数开始时设置为 esp。函数参数和局部变量的访问方式分别是从 ebp 中添加和减去常量偏移量。所有 x86 调用约定都将 ebp 定义为在函数调用之间保留。 ebp 本身实际上指向前一帧的基指针,这使得调试器中的堆栈遍历和查看其他帧的局部变量能够工作。

大多数函数序言看起来像这样:

push ebp      ; Preserve current frame pointer
mov  ebp, esp ; Create new frame pointer pointing to current stack top
sub  esp, 20  ; allocate 20 bytes worth of locals on stack.

然后在函数的后面,您可能会有类似的代码(假设两个局部变量都是 4 字节)

mov  [ebp-4], eax   ; Store eax in first local
mov  ebx, [ebp - 8] ; Load ebx from second local

FPO 或帧指针省略优化,您可以启用它,实际上会消除这种情况并使用< code>ebp 作为另一个寄存器并直接从 esp 访问局部变量,但这使得调试变得更加困难,因为调试器无法再直接访问早期函数调用的堆栈帧。

编辑:

对于您更新的问题,堆栈中缺少的两个条目是:

nShowCmd          = dword ptr +14h
hlpCmdLine        = dword ptr +10h
PrevInstance      = dword ptr +0Ch
hInstance         = dword ptr +08h
return address    = dword ptr +04h     <==
savedFramePointer = dword ptr +00h     <==
var_4             = dword ptr -04h
var_8             = dword ptr -08h
var_C             = dword ptr -0Ch

这是因为函数调用的流程是:

  • 推送参数 (hInstance, PrevInstance, hlpCmdLine, nShowCmd)
  • 调用函数,推送返回地址
  • Push ebp
  • 为局部变量分配空间

esp is as you say it is, the top of the stack.

ebp is usually set to esp at the start of the function. Function parameters and local variables are accessed by adding and subtracting, respectively, a constant offset from ebp. All x86 calling conventions define ebp as being preserved across function calls. ebp itself actually points to the previous frame's base pointer, which enables stack walking in a debugger and viewing other frame's local variables to work.

Most function prologs look something like:

push ebp      ; Preserve current frame pointer
mov  ebp, esp ; Create new frame pointer pointing to current stack top
sub  esp, 20  ; allocate 20 bytes worth of locals on stack.

Then later in the function you may have code like (presuming both local variables are 4 bytes)

mov  [ebp-4], eax   ; Store eax in first local
mov  ebx, [ebp - 8] ; Load ebx from second local

FPO or frame pointer omission optimization which you can enable will actually eliminate this and use ebp as another register and access locals directly off of esp, but this makes debugging a bit more difficult since the debugger can no longer directly access the stack frames of earlier function calls.

EDIT:

For your updated question, the missing two entries in the stack are:

nShowCmd          = dword ptr +14h
hlpCmdLine        = dword ptr +10h
PrevInstance      = dword ptr +0Ch
hInstance         = dword ptr +08h
return address    = dword ptr +04h     <==
savedFramePointer = dword ptr +00h     <==
var_4             = dword ptr -04h
var_8             = dword ptr -08h
var_C             = dword ptr -0Ch

This is because the flow of the function call is:

  • Push parameters (hInstance, PrevInstance, hlpCmdLine, nShowCmd)
  • Call function, which pushes return address
  • Push ebp
  • Allocate space for locals
原野 2024-08-11 08:56:00

ESPS大头钉 P ointer)是当前堆栈指针,每当将单词或地址压入/弹出堆栈时,该指针都会发生变化。 EBP (Base P ointer)是编译器跟踪函数参数和局部变量的一种比直接使用ESP更方便的方法。

一般来说(这可能因编译器而异),被调用函数的所有参数都由调用函数压入堆栈(通常按照与函数原型中声明的顺序相反的顺序,但这有所不同) 。然后调用该函数,推送返回地址EIPInstruction > 将P指针)放入堆栈。

进入该函数后,旧的 EBP 值被压入堆栈,并将 EBP 设置为 ESP 的值。然后,ESP 递减(因为堆栈在内存中向下增长),为函数的局部变量和临时变量分配空间。从那时起,在函数执行期间,函数的参数位于堆栈上距 EBP 正偏移处(因为它们是在函数之前推送的) call),并且局部变量位于距 EBP偏移处(因为它们是在函数入口之后在堆栈上分配的)。这就是为什么 EBP 被称为帧指针,因为它指向 函数调用框架

退出时,函数所要做的就是将 ESP 设置为 EBP 的值(这会从堆栈中释放局部变量,并公开条目 EBP< /code> 位于栈顶),然后从栈中弹出旧的 EBP 值,然后函数返回(将返回地址弹出到 EIP )。

返回到调用函数后,它可以递增 ESP,以便删除在调用其他函数之前压入堆栈的函数参数。此时,堆栈返回到调用被调用函数之前的相同状态。

ESP (Stack Pointer) is the current stack pointer, which will change any time a word or address is pushed or popped on/off the stack. EBP (Base Pointer) is a more convenient way for the compiler to keep track of a function's parameters and local variables than using the ESP directly.

Generally (and this may vary from compiler to compiler), all of the arguments to a function being called are pushed onto the stack by the calling function (usually in the reverse order that they're declared in the function prototype, but this varies). Then the function is called, which pushes the return address (EIP, Instruction Pointer) onto the stack.

Upon entry to the function, the old EBP value is pushed onto the stack and EBP is set to the value of ESP. Then the ESP is decremented (because the stack grows downward in memory) to allocate space for the function's local variables and temporaries. From that point on, during the execution of the function, the arguments to the function are located on the stack at positive offsets from EBP (because they were pushed prior to the function call), and the local variables are located at negative offsets from EBP (because they were allocated on the stack after the function entry). That's why the EBP is called the Frame Pointer, because it points to the center of the function call frame.

Upon exit, all the function has to do is set ESP to the value of EBP (which deallocates the local variables from the stack, and exposes the entry EBP on the top of the stack), then pop the old EBP value from the stack, and then the function returns (popping the return address into EIP).

Upon returning back to the calling function, it can then increment ESP in order to remove the function arguments it pushed onto the stack just prior to calling the other function. At this point, the stack is back in the same state it was in prior to invoking the called function.

多情出卖 2024-08-11 08:56:00

你说得对。堆栈指针指向堆栈顶部的项目,基指针指向调用函数之前的“前一个”堆栈顶部。

当调用函数时,任何局部变量都将存储在堆栈中,并且堆栈指针将递增。当您从函数返回时,堆栈上的所有局部变量都会超出范围。您可以通过将堆栈指针设置回基指针(这是函数调用之前的“前一个”顶部)来完成此操作。

以这种方式进行内存分配非常非常快速且高效。

You have it right. The stack pointer points to the top item on the stack and the base pointer points to the "previous" top of the stack before the function was called.

When you call a function, any local variable will be stored on the stack and the stack pointer will be incremented. When you return from the function, all the local variables on the stack go out of scope. You do this by setting the stack pointer back to the base pointer (which was the "previous" top before the function call).

Doing memory allocation this way is very, very fast and efficient.

独留℉清风醉 2024-08-11 08:56:00

编辑:有关更好的描述,请参阅x86 反汇编/函数和堆栈帧< /a> 在关于 x86 汇编的 WikiBook 中。我尝试添加一些您可能对使用 Visual Studio 感兴趣的信息。

将调用者 EBP 存储为第一个局部变量称为标准堆栈帧,这可用于 Windows 上的几乎所有调用约定。无论调用者还是被调用者释放传递的参数,以及哪些参数在寄存器中传递,都存在差异,但这些与标准堆栈帧问题正交。

谈到 Windows 程序,您可能会使用 Visual Studio 来编译 C++ 代码。请注意,Microsoft 使用一种名为“帧指针省略”的优化,这使得在不使用 dbghlp 库和可执行文件的 PDB 文件的情况下几乎不可能遍历堆栈。

这种帧指针省略意味着编译器不会将旧的 EBP 存储在标准位置,而是将 EBP 寄存器用于其他用途,因此在不知道给定函数的局部变量需要多少空间的情况下,您很难找到调用者 EIP。当然,即使在这种情况下,Microsoft 也提供了一个 API,允许您执行堆栈遍历,但对于某些用例来说,在 PDB 文件中查找符号表数据库需要很长时间。

为了避免编译单元中出现 FPO,您需要避免使用 /O2 或需要将 /Oy- 显式添加到项目中的 C++ 编译标志中。您可能会链接到 C 或 C++ 运行时,它在发布配置中使用 FPO,因此如果没有 dbghlp.dll,您将很难进行堆栈遍历。

EDIT: For a better description, see x86 Disassembly/Functions and Stack Frames in a WikiBook about x86 assembly. I try to add some info you might be interested in using Visual Studio.

Storing the caller EBP as the first local variable is called a standard stack frame, and this may be used for nearly all calling conventions on Windows. Differences exist whether the caller or callee deallocates the passed parameters, and which parameters are passed in registers, but these are orthogonal to the standard stack frame problem.

Speaking about Windows programs, you might probably use Visual Studio to compile your C++ code. Be aware that Microsoft uses an optimization called Frame Pointer Omission, that makes it nearly impossible to do walk the stack without using the dbghlp library and the PDB file for the executable.

This Frame Pointer Omission means that the compiler does not store the old EBP on a standard place and uses the EBP register for something else, therefore you have hard time finding the caller EIP without knowing how much space the local variables need for a given function. Of course Microsoft provides an API that allows you to do stack-walks even in this case, but looking up the symbol table database in PDB files takes too long for some use cases.

To avoid FPO in your compilation units, you need to avoid using /O2 or need to explicitly add /Oy- to the C++ compilation flags in your projects. You probably link against the C or C++ runtime, which uses FPO in the Release configuration, so you will have hard time to do stack walks without the dbghlp.dll.

空心空情空意 2024-08-11 08:56:00

首先,堆栈指针指向堆栈底部,因为 x86 堆栈是从高地址值向低地址值构建的。堆栈指针是下一次调用push(或call)将放置下一个值的点。其操作相当于C/C++的语句:

 // push eax
 --*esp = eax
 // pop eax
 eax = *esp++;

 // a function call, in this case, the caller must clean up the function parameters
 move eax,some value
 push eax
 call some address  // this pushes the next value of the instruction pointer onto the
                    // stack and changes the instruction pointer to "some address"
 add esp,4 // remove eax from the stack

 // a function
 push ebp // save the old stack frame
 move ebp, esp
 ... // do stuff
 pop ebp  // restore the old stack frame
 ret

基指针位于当前帧的顶部。 ebp 通常指向您的退货地址。 ebp+4 指向函数的第一个参数(或类方法的 this 值)。 ebp-4 指向函数的第一个局部变量,通常是 ebp 的旧值,这样您就可以恢复先前的帧指针。

First of all, the stack pointer points to the bottom of the stack since x86 stacks build from high address values to lower address values. The stack pointer is the point where the next call to push (or call) will place the next value. It's operation is equivalent to the C/C++ statement:

 // push eax
 --*esp = eax
 // pop eax
 eax = *esp++;

 // a function call, in this case, the caller must clean up the function parameters
 move eax,some value
 push eax
 call some address  // this pushes the next value of the instruction pointer onto the
                    // stack and changes the instruction pointer to "some address"
 add esp,4 // remove eax from the stack

 // a function
 push ebp // save the old stack frame
 move ebp, esp
 ... // do stuff
 pop ebp  // restore the old stack frame
 ret

The base pointer is top of the current frame. ebp generally points to your return address. ebp+4 points to the first parameter of your function (or the this value of a class method). ebp-4 points to the first local variable of your function, usually the old value of ebp so you can restore the prior frame pointer.

孤凫 2024-08-11 08:56:00

自从我完成汇编编程以来已经很长时间了,但是此链接可能有用...

处理器有一组用于存储数据的寄存器。其中一些是直接值,而另一些则指向 RAM 中的某个区域。寄存器确实倾向于用于某些特定的操作,并且汇编中的每个操作数都需要特定寄存器中的一定量的数据。

堆栈指针主要在调用其他过程时使用。使用现代编译器,一堆数据将首先转储到堆栈上,然后是返回地址,以便系统在被告知返回后知道返回到哪里。堆栈指针将指向可以将新数据推送到堆栈的下一个位置,并将一直保留到该位置,直到再次弹出。

基址寄存器或段寄存器只是指向大量数据的地址空间。与第二个寄存器相结合,基址指针将内存划分为大块,而第二个寄存器将指向该块内的一个项目。因此基址指针指向数据块的基址。

请记住,Assembly 是非常特定于 CPU 的。我链接到的页面提供了有关不同类型 CPU 的信息。

Long time since I've done Assembly programming, but this link might be useful...

The processor has a collection of registers which are used to store data. Some of these are direct values while others are pointing to an area within RAM. Registers do tend to be used for certain specific actions and every operand in assembly will require a certain amount of data in specific registers.

The stack pointer is mostly used when you're calling other procedures. With modern compilers, a bunch of data will be dumped first on the stack, followed by the return address so the system will know where to return once it's told to return. The stack pointer will point at the next location where new data can be pushed to the stack, where it will stay until it's popped back again.

Base registers or segment registers just point to the address space of a large amount of data. Combined with a second regiser, the Base pointer will divide the memory in huge blocks while the second register will point at an item within this block. Base pointers therefor point to the base of blocks of data.

Do keep in mind that Assembly is very CPU specific. The page I've linked to provides information about different types of CPU's.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文