什么是基指针和堆栈指针? 他们指的是什么?

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

替代文字

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

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

从我看到的,我会说堆栈指针始终指向堆栈的顶部,基指针指向当前函数的开始? 或者是什么?


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

edit2: eip也是如何工作的?

编辑3:我从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 

他们似乎都是dword,因此每个字节占4个字节。 所以我可以看到从实例到4个字节的var_4有差距。 他们是什么? 我认为这是返回地址,可以在维基百科的图片中看到?


(编者按:从迈克尔的回答中删除了很长的一句话,这不属于问题的范围,但后续的问题被编辑了):

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

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

我的问题(最后,我希望!)现在是,什么是从即时popup的函数的参数,我想调用到序幕的最后发生什么? 我想知道在这些时刻ebp,esp是如何演变的(我已经理解了序言的作用,我只是想知道在将参数推入堆栈之前和序言之前发生了什么)。

esp就像你说的那样,是堆栈的顶部。

ebp通常设置为在函数的开始处。 函数参数和局部variables分别通过加减ebp的常量偏移来访问。 所有x86调用约定定义ebp在函数调用中被保留。 ebp本身实际上指向前一帧的基本指针,这使得堆栈在debugging器中行进,并查看其他框架局部variables的工作。

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

 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. 

然后在函数后面你可能有类似的代码(假设两个本地variables是4个字节)

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

您可以启用的FPO或帧指针省略优化实际上将消除这种情况,并使用ebp作为另一个寄存器,并直接访问esp本地,但这使得debugging有点困难,因为debugging器不能再直接访问早期函数的堆栈帧调用。

编辑:

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

 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 

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

  • 推参数( hInstance等)
  • 调用函数,它将返回地址
  • ebp
  • 为当地人分配空间

ESP是当前的堆栈指针,当任何时候一个字或地址被压入或popup堆栈时,它都会改变。 EBP是编译器比使用ESP直接跟踪函数参数和局部variables更方便的方法。

一般来说(这可能因编译器而异),被调用函数的所有参数都会被压入堆栈(通常是按照在函数原型中声明的顺序)。 然后调用该函数,将返回地址(EIP)压入堆栈。

进入该function后,将旧的EBP值压入堆栈,并将EBP设置为ESP的值。 然后ESP减less(因为堆栈在内存中向下增长)为函数的局部variables和临时对象分配空间。 从这一点开始,在函数执行过程中,函数的参数位于堆栈上,与EBP正向偏移(因为它们在函数调用之前被推送),并且局部variables位于EBP的负偏移处(因为它们在函数入口之后被分配在堆栈上)。 这就是为什么EBP被称为帧指针 ,因为它指向函数调用帧的中心。

退出时,所有函数所要做的就是将ESP设置为EBP的值(从堆栈中释放本地variables,并将堆栈顶部的条目EBP公开),然后从堆栈中popup旧的EBP值,然后函数返回(将返回地址popup到EIP中)。

你说得对。 堆栈指针指向堆栈中的顶层项目,在调用该函数之前,基址指针指向堆栈的“上一个”顶端

当你调用一个函数时,任何局部variables将被存储在堆栈上,并且堆栈指针将被增加。 从函数返回时,堆栈上的所有局部variables超出范围。 您可以通过将堆栈指针设置回基本指针(在函数调用之前是“上一个”顶部)来执行此操作。

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

首先,由于x86堆栈从高地址值构build为低地址值,因此堆栈指针指向堆栈的底部。 堆栈指针是下一个推送(或调用)调用将放置下一个值的点。 它的操作等同于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指向你的函数的第一个参数(或者类方法的这个值)。 ebp-4指向你的函数的第一个局部variables,通常是ebp的旧值,所以你可以恢复先前的帧指针。

编辑:为了更好的描述,请参阅有关x86程序集的WikiBook中的x86 Disassembly / Functions和Stack Frames 。 我尝试添加一些您可能会对使用Visual Studio感兴趣的信息。

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

说到Windows程序,你可能会使用Visual Studio编译你的C ++代码。 请注意,Microsoft使用了称为帧指针省略的优化,这使得在不使用可执行文件的dbghlp库和PDB文件的情况下执行堆栈几乎是不可能的。

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

要避免编译单元中的FPO,您需要避免使用/ O2或需要在项目中的C ++编译标志中明确添加/ Oy-。 您可能会链接到C或C ++运行库,该版本在发布configuration中使用FPO,因此您将很难在没有dbghlp.dll的情况下执行堆栈遍历。

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

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

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

基址寄存器或段寄存器只是指向大量数据的地址空间。 与第二个寄存器组合在一起,Base指针会将内存分成大块,而第二个寄存器将指向该块中的一个项目。 基指针指向数据块的基础。

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

esp代表“扩展堆栈指针”….. ebp代表“基本指针”….并且代表“Something指针指针”……堆栈指针指向堆栈段的偏移地址。 基址指针指向额外的段的偏移地址。 指令指针指向代码段的偏移地址。 现在,关于段…他们是处理器内存区域的小型64KB分区…..这个过程被称为内存分割。 我希望这个post是有帮助的。

编辑是的,这是大多数错误。 它描述了一些完全不同的情况,如果有人感兴趣:)

是的,堆栈指针指向堆栈的顶部(不pipe这是第一个空的堆栈位置还是我不确定的最后一个完整堆栈位置)。 基指针指向正在执行的指令的内存位置。 这是操作码级别 – 您可以在计算机上获得的最基本的指令。 每个操作码及其参数都存储在一个存储单元中。 一个C或C ++或C#行可以被翻译成一个操作码,或者一个两个或更多的序列,取决于它的复杂程度。 这些被顺序写入程序存储器并执行。 一般情况下,基指针增加一条指令。 对于程序控制(GOTO,IF等),可以多次递增或者用下一个内存地址replace。

在这种情况下,function存储在程序存储器中的某个地址。 当函数被调用时,某些信息被压入堆栈,使得程序find它返回到函数被调用的地方以及函数的参数,然后程序存储器中函数的地址被压入基指针。 在下一个时钟周期,计算机开始执行该存储器地址的指令。 然后在某一点它将返回到调用该函数的指令之后的内存位置并从那里继续。