关于c ++:什么是基本指针和堆栈指针?

What is exactly the base pointer and stack pointer? To what do they point?

使用来自维基百科的这个例子,其中drawsquare()调用drawline(),

alt text

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

有人能解释一下在这种情况下,ebpesp是什么吗?

从我所看到的来看,堆栈指针总是指向堆栈的顶部,而基指针则指向当前函数的开头?或者什么?

编辑:我指的是在Windows程序的上下文中

伊迪丝2:那么,eip是如何工作的呢?

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

1
2
3
4
5
6
7
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个字节。所以我可以看到从Hinstance到4字节的var_4之间有一个间隙。它们是什么?我想它是回信地址,就像在维基百科的图片上看到的那样?

(编者按:从迈克尔的回答中删除了一个长引述,这不属于问题,但在中编辑了一个后续问题):

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

1
2
3
4
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals

我的问题(最后,我希望!)现在是,从我弹出要调用的函数的参数到prolog的末尾,到底发生了什么?我想知道EBP,特别是在那些时刻是如何发展的(我已经了解了prolog是如何工作的,我只想知道在我把参数推到堆栈上和prolog之前发生了什么)。


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

ebp通常在函数开始时设置为esp。函数参数和局部变量分别通过从ebp减去常量偏移量来访问。所有x86调用约定都将ebp定义为跨函数调用保留。ebp本身实际上指向前一帧的基指针,这使得栈可以在调试器中遍历并查看其他帧局部变量。

大多数函数prolog看起来像:

1
2
3
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个字节)

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

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

编辑:

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

1
2
3
4
5
6
7
8
9
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
*savedFramePointer = dword ptr 0*
*return address = dword ptr 4*
hInstance = dword ptr  8h
PrevInstance = dword ptr  0C
hlpCmdLine = dword ptr  10h
nShowCmd = dword ptr  14h

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

  • 推送参数(hInstance等)
  • 调用函数,推送返回地址
  • 推动ebp
  • 为本地人分配空间


esp是当前的堆栈指针,每当一个字或地址被推到堆栈上或从堆栈中弹出时,它都会改变。与直接使用esp相比,ebp是一种更方便的编译器跟踪函数参数和局部变量的方法。

通常(这可能因编译器而异),被调用函数的所有参数都被推到堆栈上(通常与函数原型中声明的相反顺序,但这会有所不同)。然后调用函数,将返回地址(EIP)推送到堆栈上。

进入函数后,将旧的EBP值推到堆栈上,并将EBP设置为esp的值。然后将esp递减(因为堆栈在内存中向下增长),为函数的局部变量和临时变量分配空间。从那时起,在执行函数的过程中,函数的参数位于堆栈上与ebp的正偏移量处(因为它们是在函数调用之前推送的),而局部变量位于与ebp的负偏移量处(因为它们是在函数项后分配到堆栈上的)。这就是为什么EBP被称为帧指针,因为它指向函数调用帧的中心。

退出后,所有函数都要做的是将esp设置为ebp的值(它从堆栈中释放局部变量,并在堆栈顶部显示条目ebp),然后从堆栈中弹出旧的ebp值,然后函数返回(将返回地址弹出到eip中)。


你说得对。堆栈指针指向堆栈上的顶部项,基指针指向调用函数前堆栈的"上一个"顶部。

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

以这种方式进行内存分配是非常、非常快速和高效的。


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

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

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

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

为了避免编译单元中的FPO,需要避免使用/O2或需要显式地添加/OFE到项目中的C++编译标志。您可能链接到C或C++运行时,它在发布配置中使用FPO,所以在没有BGHLP.DLL的情况下,您将有很难完成堆栈行。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 // 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指向函数的第一个参数(或类方法的这个值)。EBP-4指向函数的第一个局部变量,通常是EBP的旧值,因此可以恢复先前的帧指针。


我已经做了很长时间的汇编编程,但是这个链接可能很有用…

处理器有一组用于存储数据的寄存器。其中一些是直接值,而另一些则指向RAM中的一个区域。寄存器通常用于某些特定的操作,程序集中的每个操作数都需要特定寄存器中的一定数量的数据。

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

基址寄存器或段寄存器只指向大量数据的地址空间。与第二个寄存器相结合,基指针将把内存分成大的块,而第二个寄存器将指向这个块中的一个项。它的基指针指向数据块的基。

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


编辑是的,这基本上是错误的。它描述了完全不同的东西,以防有人感兴趣:)

是的,堆栈指针指向堆栈的顶部(无论是第一个空堆栈位置还是最后一个完全的堆栈位置,我都不确定)。基指针指向正在执行的指令的内存位置。这是在操作码的层次上的,这是你在计算机上能得到的最基本的指令。每个操作码及其参数都存储在一个内存位置。一个C或C++或C行可以被翻译成一个操作码,或者两个或更多个序列,这取决于它是多么复杂。这些文件按顺序写入程序存储器并执行。在正常情况下,基指针递增一条指令。对于程序控制(goto、if等),可以多次递增,也可以用下一个内存地址替换。

在此上下文中,函数存储在程序内存中的某个地址。当调用函数时,在堆栈上推送某些信息,让程序找到它的原位置以及函数的参数,然后将函数在程序内存中的地址推送到基指针中。在下一个时钟周期,计算机开始执行来自该内存地址的指令。然后在某个时刻,它将在调用函数的指令之后返回到内存位置,并从那里继续。


esp代表"扩展堆栈指针"….ebp代表"某物基指针"…,eip代表"某物指令指针"……堆栈指针指向堆栈段的偏移地址。基指针指向额外段的偏移地址。指令指针指向代码段的偏移地址。现在,关于段……它们是处理器内存区域的小64KB分区……这个过程称为内存分段。我希望这篇文章能有所帮助。