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
Dav*_*ble 89
ESP是当前的堆栈指针,无论何时将字或地址推入或弹出堆栈,它都会发生变化.与直接使用ESP相比,EBP是编译器跟踪函数参数和局部变量的更方便的方法.
通常(并且这可能因编译器而异),被调用函数的所有参数都被压入堆栈(通常按照它们在函数原型中声明的相反顺序,但这会有所不同).然后调用该函数,将返回地址(EIP)压入堆栈.
进入函数后,旧的EBP值被压入堆栈,EBP被设置为ESP的值.然后ESP递减(因为堆栈在内存中向下增长)为函数的局部变量和临时值分配空间.从那时起,在执行函数期间,函数的参数位于堆栈上,与EBP正偏移(因为它们在函数调用之前被推送),局部变量位于EBP的负偏移处. (因为它们是在函数入口后分配在堆栈上的).这就是EBP被称为帧指针的原因,因为它指向函数调用帧的中心.
退出时,所有函数必须将ESP设置为EBP的值(从堆栈中释放局部变量,并在堆栈顶部公开条目EBP),然后从堆栈中弹出旧的EBP值,然后函数返回(将返回地址弹出到EIP中).
Rob*_*ino 15
你没事.堆栈指针指向堆栈上的顶部项,并且在调用函数之前,基指针指向堆栈的"上一个"顶部.
调用函数时,任何局部变量都将存储在堆栈中,堆栈指针将递增.从函数返回时,堆栈上的所有局部变量都超出范围.您可以通过将堆栈指针设置回基指针(函数调用之前的"上一个"顶部)来完成此操作.
这样的内存分配这种方式是非常,非常快速,高效.
编辑:有关更好的描述,请参阅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的情况下进行堆栈遍历.
首先,堆栈指针指向堆栈的底部,因为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的旧值,因此可以恢复前一个帧指针.