ebp 函数堆栈esp_函数堆栈调用过程

从内存的角度详细的分析C语言中的函数调用过程:

首先写一个测试用的代码:

#include 

int add(int x, int y)

{

int z = 0;

z = x + y;

return z;

}

int main()

{

int a = 1, b = 2;

int c = 0;

c = add(a, b);

return 0;

}

这是一个简单的的求和函数。

其次,让我们确定一下,程序是从哪里开始运行的:

调试程序,按一下F10(博主用的VS2013),

进入main函数:

然后进调试--->窗口--->调用堆栈(用来显示函数的调用关系)。

发现正在调用main这个函数,但现在我想知道是谁在调用main函数,F10一路走到return 0,接着换F11(逐语句调试),然后会发现,main函数返回后,我们来到了这里:

再看看此时的调用堆栈:

直接来看,现在运行的函数是__tmainCRTStartup(),这个函数又被mainCRTStratup()调用,而我们刚刚是从main()函数返回来的,所以,main()函数是由__tmainCRTStartup()这个函数调用的。

了解了main()函数是被谁调用后,我们可以进一步分析这其中的细节了!

现在重新F10进入调试,到这一步:

进入main()函数后还没有执行任何一条语句,我们 右击-->转到反汇编:

看到了汇编语言的代码,图中的ebp和esp是什么东西呢?我们知道,调用函数的时候操作系统要给这个函数分配一段内存空间,之前又说了main()函数是由—__tCRTStartup()函数调用的,所以请看:

mainCRTStratup()函数调用__tmainCRTStra()函数的时候就会从栈上为__tmainCRTStra()分配类似图中这么一块空间,把这块空间叫做栈帧。我们知道栈是由高地址向低地址扩展的。其中ebp叫做栈底指针,esp叫做栈顶指针(当然也有其它叫法)。ebp,esp本身是一个寄存器,其中存放了地址时,我们就称之为指针!

现在再来看汇编程序:

按一下F10执行第一条语句,箭头指向下一条语句,变成这样:

(和我们在外边的调试是一样的)这句 push ebp 就是将ebp中的值进行压栈,而此时ebp存放的是系统分给__tmainCRTStartup()函数的空间的起始地址。因为我们现在要调用main()函数了,所以当然要先把__tmainCRTStartup()函数的运行状态保存下来,这样main()函数才能返回的时候才能找得到!push是在栈顶进行的,所以,push之后,esp要向上移动:

刚刚说了,栈是由高地址向低地址扩展的,所以这个push操作应该是对esp进行一个减操作,具体见了多少,可以在内存里查一查:

先看一下push之前esp的的值:

esp的当前值为0x00ABFA30,代表它指向0x00ABFA30这个地址代表的内存。

再看一下push之后esp的值发生了什么变化:

变成了0x00ABFA2C,差了4个字节,就是放进去的地址的大小。

然后继续执行下一条语句: mov         ebp,esp

即把esp的值赋给ebp,这样,ebp也就指向了现在esp的位置,如下图:

接着又执行语句:sub         esp,0E4h

即将esp的值减去E4h,所以esp向上移动了E4h个位置(相当于申请了这么大的一块空间),新申请的这块空间就给main()用了。如下:

接下来紧接着三条push语句将后面要用到的寄存器中原来的值存储起来,等我们借用完寄存器后再给人家pop回去,不管它,这里esp再向上移动三次。

(ps:图片太大,所以只截了当前要用到的)

紧接着的四条语句共同完成一个任务,就是将图中最大长方形区域初始化为0CCCCCCCh(你经常看到的:烫烫烫烫......)

第一句:lea         edi,[ebp-0E4h]

就是将ebp减去E4h的值赋给edi,这个E4h是不是很眼熟呢?它就是我们上一步分配给main()的空间的大小,即edi指向了3次push之前的esp的位置;

第二句:mov         ecx,39h

把39h放在ecx中(充当了计数器)

第三句:mov         eax,0CCCCCCCCh

把要初始化的数据写入eax

最后一句:rep stos    dword ptr es:[edi]

循环的从低地址(ebp-0E4h)向高地址(ebp)写0CCCCCCCCh,循环了39h次!

我们在执行之前转到内存中看一下:

先查找ebp:

(我往下拖了一点,左下角的光标处的地址就是ebp当前值0z00ABFA2C)

四条语句执行后:

相应的位置已经被初始化为0CCCCCCCh,其它部分是乱码(此时ebp值为0x00ABFA2C,它之上的一段空间是分配给main()的)

程序继续往下执行:

mov         dword ptr [ebp-8],1  在ebp-8h的位置放一个1,

mov         dword ptr [ebp-14h], 2 在ebp-14h的位置放一个2

即分别创建了a,b两个变量,如图:

接着创建c:

此时我们的内存分配变成了这样:

然后到了这里,调用add()准备工作:

mov         eax,dword ptr [ebp-14h]    是把ebp-14h位置的值放入eax(此时ebp-14h的值是我们的变量b的值),然后:push        eax , 即eax压栈;

同理,mov         ecx,dword ptr [ebp-8]   把ebp-8位置的值放入ecx,然后ecx压栈。如下(传递形参给x和y):

程序到这:

在汇编里我们用call调用一个函数(_add是一个标号,它代表了一个地址,是add()函数的首地址),而call在执行的同时,会把它下一条指令的地址(就是图中的00D1450)push到main()的栈桢中去,以便add()执行完后返回的时候还可以找到程序当初执行到了哪里,然后接着执行。

为了证明这一点,我们先查看一下esp所指向内存的值:

然后F11跟进去到这里:

再查看esp所指内存:

可以看到esp的位置发生了改变,此时内存中的值 50 14 0d 00 是不是很像刚刚的call语句下一条指令地址呢?对它就是00 0d 14 50 的小端字节序,这里不再解释小端字节序,只需理解它是内存中字节存储的一种方式,有兴趣的可以查看:http://blog.csdn.net/qq_33724710/article/details/51056542

栈桢分配图变成了这样:

接着F11执行刚刚的jmp语句:

历尽千辛万苦终于进入add()!现在贴出来的这几句代码就和我们刚刚进入main()函数的语句大同小异了。

push        ebp  //ebp压栈

mov         ebp,esp  //ebp指向esp所指

sub         esp,0CCh  //esp - 0CCh, 开辟了新的栈桢

push        ebx  //3个push,照旧不管它

push        esi

push        edi

lea         edi,[ebp-0CCh]  //初始化烫烫烫烫......

mov         ecx,33h

mov         eax,0CCCCCCCCh

rep stos    dword ptr es:[edi]

然后到这里:

给ebp-8处放了个0,就是创建z啦!

再接着到这里:

eax,dword ptr [ebp+8]  //注意是加了8,取出的是我们之前传递进来的形参值1,放到eax

add         eax,dword ptr [ebp+0Ch]  //取epb+0Ch,取出的是我们之前传递进来的形参值2,加到eax

dword ptr [ebp-8],eax  //再把求和后的值eax赋给epb - 8的位置,就是z喽!

程序执行到这,准备返回main()了:

因为z是个临时变量,出了add()就会销毁,要返回z的值,就要把它的值放进寄存器:

mov         eax,dword ptr [ebp-8]  //epb-8找到的就是z,赋给eax

pop         edi  //连续三个pop,之前连续三个push我们没管它,现在仍然不管它

pop         esi

pop         ebx

3次pop后,esp高地址处移动了3个单位:

虽然esp上边的空间还在,但是已经不属于当前的栈桢了,相当于释放掉了!

然后:

mov         esp,ebp  //esp指向当前ebp

pop         ebp  //main()起始地址赋给给ebp,esp往高地址处移动一次

所以变成这样:

最后执行ret,程序回到这里:

看见了没,ret指令自动取出了call的下一条语句地址(ret自动执行了pop,esp又往高地址处移动了一次)赋给了PC(PC总是指向下一条要执行的语句)。

接着的add         esp,8  使esp继续往高地址方向移动,并跳过1,2两个参数,如下:

mov         dword ptr [ebp-20h],eax  //还记得eax吗,当初我们把求和的结果,即 z 的值赋给了它,ebp-20h依然是当初的c

现在,我们要的结果已经赋给 c 了!

xor         eax,eax  //eax没用了,异或eax,清零

pop         edi  //又是连续3个pop

pop         esi

pop         ebx

add         esp,0E4h  //oE4h,当出开辟的main()栈桢的大小,现在释放掉

cmp         ebp,esp  //不管它

call        000D113B  //不管它

mov         esp,ebp  //释放main()栈桢

pop         ebp  //ebp指向__tmainCRTStartup()起始地址,esp下移

ret  //返回到__tmainCRTStartup()

__tmainCRTStartup()和mainCRTStart()里边的过程就不在分析了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/335767.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

apache pdfbox_Apache PDFBox命令行工具:无需Java编码

apache pdfbox在博客文章Apache PDFBox 2中 ,我演示了将Apache PDFBox 2用作从Java代码中调用的库来操作PDF。 事实证明,Apache PDFBox 2还提供了可以直接从命令行直接使用的命令行工具 ,而无需其他Java编码。 有几种命令行工具可用&#xff…

Java数组的学习

文章目录数组的声明和定义数组的特点如何使用数组数组的声明和定义 int[] ary new int[4] //动态初始化,长度4 int[] ary new int[]{1,2,3} //直接使用元素创建数组,也是动态初始化 int[] ary {1,2,3} //静态初始化// 数组变量的声明 int[] ary; in…

C++ 11 深度学习(十七)condition_variable、wait

简介 条件变量std::condition_variable的作用是阻塞线程,然后等待通知将其唤醒。我们可以通过某个函数判断是否符合某种条件来决定是阻塞线程等待通知还是唤醒线程,由此实现线程间的同步。所以简单来说condition_variable的作用就两个——等待(wait)、通…

【WebRTC---源码篇】(六)NACK判断包位置的关键算法

首先保证a与b不相等 template <typename T, T M = 0> inline bool AheadOf(T a, T b) {static_assert(std::is_unsigned<T>::value,"Type must be an unsigned integer.");return a != b && AheadOrAt<T, M>(a, b); } template <type…

spring集成mq_使用Spring Integration Java DSL与Rabbit MQ集成

spring集成mq我最近参加了在拉斯维加斯举行的2016年Spring大会 &#xff0c;很幸运地看到了我在软件世界中长期敬佩的一些人。 我亲自遇到了其中的两个人&#xff0c;他们实际上合并了几年前我与Spring Integration相关的一些次要贡献– Gary Russel和Artem Bilan &#xff0c;…

blt功能_BitBlt实现TransparentBlt

文章用BitBlt实现了TransparentBlt的功能&#xff0c;主要的工作如下&#xff1a;void TransparentBlt2(HDC hdcDest, // 目标DCint nXOriginDest, // 目标X偏移int nYOriginDest, // 目标Y偏移int nWidthDest, // 目标宽度int nHeightDest, // 目标高度HDC hdcSrc, // 源DCint…

MyEclipse 如何使用断点调试

文章目录1、在需要的代码行处打断点右键添加断点双击添加断点快捷键添加断点2、以debug模式开启服务器通过debug执行列表&#xff0c;选择服务器启动当前程序按右键&#xff0c;选择debug as&#xff0c;再选择服务器启动开发视图底部server列表选择服务器开启debug视图&#x…

【WebRTC---源码篇】(八)音频数据采集

ADM的创建 1.Audio Device Module是在逻辑层与硬件设备层之间进行了转换,需要使用什么功能,调用什么接口就可以,不要管底层平台实现。 2.AudioDeviceModule是一个接口类,定义了一系列接口。最终实现由子类AudioDeviceModuleForTest实现,其中包括一些测试函数(对设备的…

openlayers地图旋转_地图切换动画#openlayers入门笔记#

本博客合集是我的openlayers学习笔记&#xff0c;希望能帮助到刚开始接触openlayers的同学commnet 所用openlayers版本&#xff1a;v5.3.0commnet 阅读本文前需要对前端知识有一定的了解comment 本文内容只提供参考&#xff0c;建议结合openlayers官网的API和examples来学习com…

6种漂亮的线条

第一种&#xff1a; <hr style" height:2px;border:none;border-top:2px dotted #185598;" /> height:2px;是hr的高度 border:none;是没有边框 border-top:2px dotted #185598;是设置横线的样式 dotted 点线 #185598 颜色 第二种&#xff1a; <hr st…

jsf netbeans_NetBeans Java EE技巧9:从数据库创建JSF应用程序

jsf netbeans您需要非常快速地创建数据库前端吗&#xff1f; NetBeans IDE允许人们以很少的代码就为一组数据库表开发JSF应用程序前端。 现在&#xff0c;本教程是一个老歌&#xff0c;但是一个好东西……对于那些不了解它的人来说&#xff0c;值得再次提及。 首先&#xff0c…

python内置属性类_Python内置类属性,元类研究

Python内置类属性我觉得一切都是对象&#xff0c;对象和元类对象&#xff0c;类对象其实都是一样的&#xff0c;我在最后进行了证明&#xff0c;但是只能证明一半&#xff0c;最后由于元类的父类是type&#xff0c;他可以阻挡对object属性的访问&#xff0c;告终__dict__ : 类的…

java向后兼容吗_Java向后不兼容历史的观察

java向后兼容吗在大多数情况下&#xff0c;Java是一个非常向后兼容的编程语言。 这样做的好处是&#xff0c;与大规模破坏兼容性相比&#xff0c;大型系统通常可以相对轻松的方式升级为使用Java的较新版本。 这样做的主要缺点是Java坚持了一些设计决策&#xff0c;这些决策自那…

转义字符的整理

文章目录转义符号 \转义字符清单换行符横向制表符空格编码符转义符号 \ 反斜杠\在程序设计中称为转义符&#xff0c;用来表示那些不能直接显示的字符。例如&#xff1a;换行。 在不同的系统中换行的表示又不相同&#xff0c;在Unix中换行符是\n,而在Windows中换行符是\r\n&…

cloud foundry_介绍“又一个” Cloud Foundry Gradle插件

cloud foundry在与两个同事&#xff08;感谢Mark Alston和Dave Malone &#xff01;&#xff09;一起使用自动Jenkins管道部署Cloud Foundry应用程序的过程中&#xff0c;我决定尝试编写Gradle插件来执行一些通常需要完成的任务使用命令行Cloud Foundry Client完成 。 引入一个…

C++ 11 深度学习(十八)模板概念,函数模板定义、调用

模板作为一个框架&#xff0c;通过传入的参数&#xff0c;来具体实例化想要的东西。 1.模板定义是用template关键字开头的&#xff0c;后跟<>,<>里面叫模板参数列表(模板实参)&#xff0c;如果模板参数列表中有多个参数使用逗号隔开。 2.<>里面至少要有一个…

kotlin将对象转换为map_Kotlin 集合框架:常用集合工具函数(Map 部分)

kotlin.collections.Maps.kt 文件定义了许多针对 Map 接口的工具函数&#xff0c;其中大多数是涉及函数式编程的高阶函数&#xff0c;我们在这里先不讲&#xff0c;等到集合框架结束后进入高阶函数时再讲。今天只讲几个简单的函数。Pair 类和 to() 函数Map 接口表示一种“键 -&…

Oracle/MySQL/Java数据类型对应表

文章目录数据类型对照表MySQL 的 int、bigint、mediumint、smallint、tinyint 的对比关于 MySQL 整型显示位数的问题关于 MySQL 的 timestamp关于 JDBC 类型数据类型对照表 MysqlOracleJavaBIGINTNUMBER(19,0)java.lang.LongBITRAWbyte[]BLOBBLOB RAWbyte[]CHARCHARjava.lang.…

java10个基础错误_我们处理了10亿个Java记录的错误-这是导致97%的错误的原因

java10个基础错误97&#xff05;的记录错误是由10个唯一错误引起的 在2016年&#xff0c;一件事在30年内没有改变。 开发和运营团队仍依靠日志文件对应用程序问题进行故障排除。 由于某些未知原因&#xff0c;我们隐式信任日志文件&#xff0c;因为我们认为事实隐藏在其中。 如…