什么是基本指针和堆栈指针?他们指出了什么?

问题描述 投票:205回答:8

使用来自维基百科的this example,其中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

所有这些似乎都是dwords,因此每个占用4个字节。所以我可以看到从hInstance到4个字节的var_4之间存在差距。这些是什么?我认为它是返回地址,可以在维基百科的图片中看到?


(编者注:从迈克尔的答案中删除了长篇引文,该答案不属于该问题,但编辑后续问题):

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

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

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

c++ c assembly x86
8个回答
216
投票

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或帧指针省略优化实际上将消除这一点并使用ebp作为另一个寄存器并直接从esp访问本地,但这使调试变得更加困难,因为调试器不能再直接访问早期函数的堆栈帧调用。

编辑:

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

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
  • 为当地人分配空间

78
投票

ESP是当前的堆栈指针,无论何时将字或地址推入或弹出堆栈,它都会发生变化。与直接使用ESP相比,EBP是编译器跟踪函数参数和局部变量的更方便的方法。

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

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

退出时,所有函数必须将ESP设置为EBP的值(从堆栈中释放局部变量,并在堆栈顶部公开条目EBP),然后从堆栈中弹出旧的EBP值,然后函数返回(将返回地址弹出到EIP中)。

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


15
投票

你没事。堆栈指针指向堆栈上的顶部项,并且在调用函数之前,基指针指向堆栈的“上一个”顶部。

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

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


7
投票

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

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

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

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

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


6
投票

首先,堆栈指针指向堆栈的底部,因为x86堆栈从高地址值构建到较低的地址值。堆栈指针是下一次推送(或调用)调用将放置下一个值的点。它的操作等同于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的旧值,因此可以恢复前一个帧指针。


1
投票

自从我完成汇编编程以来,很长一段时间,但this link可能会有用......

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

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

基址寄存器或段寄存器只指向大量数据的地址空间。结合第二个调节器,Base指针将把内存分成大块,而第二个寄存器将指向该块内的一个项目。因此,基指针指向数据块的基础。

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


-3
投票

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

是的,堆栈指针指向堆栈的顶部(无论是第一个空堆栈位置还是最后一个我不确定的堆栈位置)。基指针指向正在执行的指令的内存位置。这是在操作码级别上 - 您可以在计算机上获得的最基本的指令。每个操作码及其参数都存储在存储器位置。一个C或C ++或C#行可以转换为一个操作码,或两个或更多的序列,具体取决于它的复杂程度。它们按顺序写入程序存储器并执行。在正常情况下,基指针递增一条指令。对于程序控制(GOTO,IF等),它可以多次递增或仅用下一个存储器地址替换。

在这种情况下,函数存储在某个地址的程序存储器中。当调用该函数时,某些信息被推送到堆栈上,让程序发现它已经回到调用函数的位置以及函数的参数,然后程序存储器中函数的地址被推入到基指针。在下一个时钟周期,计算机开始执行该存储器地址的指令。然后在某些时候它会在调用该函数的指令之后返回到存储器位置并从那里继续。


-5
投票

esp代表“扩展堆栈指针”..... ebp代表“Something Base Pointer”....并且eip代表“Something Instruction Pointer”......堆栈指针指向堆栈段的偏移地址。 Base Pointer指向额外段的偏移地址。指令指针指向代码段的偏移地址。现在,关于段......它们是处理器存储区的64KB小部分......这个过程称为存储器分段。我希望这篇文章很有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.