理解调用栈最重要的两点是:栈的结构,EBP寄存器的作用。
首先要认识到这样两个事实:
1、一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作。
2、几乎所有本地编译器都会在每个函数体之前插入类似如下指令:PUSH EBP; MOV EBP ESP;
即,在程序执行到一个函数的真正函数体时,已经有以下数据顺序入栈:参数,返回地址,EBP。
由此得到类似如下的栈结构(参数入栈顺序跟调用方式有关,这里以C语言默认的CDECL为例):
+| (栈底方向,高位地址) |
| .................... |
| .................... |
| 参数3 |
| 参数2 |
| 参数1 |
| 返回地址 |
-| 上一层[EBP] | <-------- [EBP]
“PUSH EBP”“MOV EBP ESP”这两条指令实在大有深意:首先将EBP入栈,然后将栈顶指针ESP赋值给EBP。“MOV EBP ESP”这条指令表面上看是用ESP把EBP原来的值覆盖了,其实不然——因为给EBP赋值之前,原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的EBP值!
一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层EBP值。
由于EBP中的地址处总是“上一层函数调用时的EBP值”,而在每一层函数调用中,都能通过当时的EBP值“向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值”。
如此形成递归,直至到达栈底。这就是函数调用栈。
编译器对EBP的使用实在太精妙了。
从当前EBP出发,逐层向上找到所有的EBP是非常容易的:
unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
{
//...
_ebp = *(unsigned int*)_ebp;
}
分享到:
相关推荐
C++ 获取函数调用堆栈的 高效实现代码
函数递归调用堆栈分析.doc
详细解释了函数调用过程中堆栈的处理过程,主要对新手来说可以通过这个提升一下自己对函数调用过程的了解
函数调用,参数堆栈等学习资料收集 关于函数调用时堆栈的变化分析(转自Jim's blog) - H_S_的学习总结与心得 - 博客园.mht ...函数调用堆栈分析.doc 转贴:关于函数调用的深入分析 百度空间_应用平台.mht
【应聘笔记系列】堆栈、栈帧与函数调用过程分析,C-C++堆栈指引
通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈
函数调用堆栈变化分析[参考].pdf
Win32环境下函数调用的堆栈之研究,一个好的学习资料
webpack loader,记录JavaScript函数调用堆栈到字符串window.dxj中,该堆栈可格式化进一步对比分析 也可保存不同时刻的对象进行对比分析
CSDN 博文,对栈帧的概念以及函数调用过程栈的变化做了详细的分析!
详细介绍了main函数如何调用子函数的过程,非常的经典
linux C用户态调试追踪函数调用堆栈以及定位段错误.docx
堆栈帧,函数调用,堆栈切换 函数调用过程种种细节分析
本文实例讲述了JavaScript实现显示函数调用堆栈的方法。分享给大家供大家参考,具体如下: 许多大型的JavaScript应用程序间的函数调用关系是非常复杂的,在开发或者调试过程中,经常需要跟踪某个函数是由哪些函数...
自动生存期:局部变量和函数形参一般都具有自动生存期,它们的内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时内存被收回。 动态生存期:具有动态生存期的变量的生存...
Visual C/C++的编译器提供了几种函数调用约定,了解这些函数调用约定的含义及它们之间的区别可以帮助我们更好地调试程序。在这篇文章里,我就和大家共同探讨一些关于函数调用约定的内容。 Visual C/C++的编译器支持...
gdb调试中,具有调用关系的函数在堆栈中的关系。
函数调用中堆栈的个人理解共4页.pdf.zip