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

dev*_*ium 215 c c++ x86 assembly

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

替代文字

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

任何人都可以解释我什么ebp,并esp在这方面?

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


编辑:我的意思是在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
Run Code Online (Sandbox Code Playgroud)

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


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

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

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

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

Mic*_*ael 221

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.
Run Code Online (Sandbox Code Playgroud)

然后在函数中你可能有代码(假设两个局部变量都是4个字节)

mov [ebp-4], eax    ; Store eax in first local
mov ebx, [ebp - 8]  ; Load ebx from second local
Run Code Online (Sandbox Code Playgroud)

您可以启用的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
Run Code Online (Sandbox Code Playgroud)

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

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

  • EBP并没有神奇地改变,所以在你为你的功能建立一个新的EBP之前,你仍然会有呼叫者的价值.除了参数,堆栈还将保留旧的EIP(返回地址) (3认同)
  • 很好的答案.虽然如果没有提到epilog中的内容就不能完整:"离开"和"退出"指令. (2认同)
  • 我认为这张图片将有助于澄清流程的一些内容.还要记住堆栈向下增长.http://ocw.cs.pub.ro/courses/_media/so/laboratoare/call_stack.png (2认同)

Dav*_*ble 89

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

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

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

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

  • 对于 asm 新手可能会产生误导的一件事是,“EBP”中的“BP”代表“基指针”,但事实并非如此,因为“EBP”指向堆栈帧的中心,而不是底部。“帧指针”感觉是这个寄存器的一个更好的名字。 (2认同)
  • @EpicSpeedy不,ESP指向你压入堆栈的最后一个项目(或者它上面的可用空间,不记得是哪个)。无论哪种方式,当您压入/弹出寄存器或调用函数/从寄存器返回时,它都会发生变化。 (2认同)

Rob*_*ino 15

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

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

这样的内存分配这种方式是非常,非常快速,高效.

  • @Robert:当你在调用函数之前说堆栈的"previous"顶部时,你忽略了两个参数,这些参数在调用函数和调用者EIP之前被压入堆栈.这可能会使读者感到困惑.我们只是说在标准堆栈框架中,EBP指向ESP指向_after_进入函数的同一个地方. (13认同)

wig*_*igy 7

编辑:有关更好的描述,请参阅WikiBook中有关x86程序集的x86反汇编/函数和堆栈帧.我尝试使用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的情况下进行堆栈遍历.


jmu*_*llo 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
Run Code Online (Sandbox Code Playgroud)

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

  • 不,ESP没有指向堆栈的_bottom_.内存寻址方案与它无关.堆栈是否增长到更低或更高的地址无关紧要.该堆栈的"顶部"永远是下一个值将被推(放于堆栈的顶部),或在其他架构,其中最后被推值已经投入和目前它奠定.因此,ESP总是指向堆栈的_top_. (2认同)
  • 另一方面,堆栈的_bottom_或_base_是放置_first_(或_oldest_)值的位置,然后被更新的值覆盖。这就是 EBP 名称“基指针”的由来:它应该指向子例程当前本地堆栈的基址(或底部)。 (2认同)