企业网站建设 价格,南京广告公司地址,在线seo优化工具,互联网推广电话销售话术目录
引言
基础知识
内存模型
寄存器的种类与功能
常用的汇编指令
函数栈帧创建与销毁
main()函数栈帧的创建
NO1.
NO2.
NO3.
NO4.
NO5.
NO6.
main()函数栈帧变量的创建
调用Add()函数栈帧的预备工作——传参
NO1.
NO2.
NO3.
Add()函数栈帧的创建
…目录
引言
基础知识
内存模型
寄存器的种类与功能
常用的汇编指令
函数栈帧创建与销毁
main()函数栈帧的创建
NO1.
NO2.
NO3.
NO4.
NO5.
NO6.
main()函数栈帧变量的创建
调用Add()函数栈帧的预备工作——传参
NO1.
NO2.
NO3.
Add()函数栈帧的创建
Add()函数栈帧变量的创建并运算
NO1.
NO2.
NO3.
NO4.
Add()函数栈帧的销毁
NO1.
NO2.
NO3.
返回main()函数栈帧
问题
NO1.
NO2.
NO3. 引言 在前期学习当中我们可能会有很多困惑比如 局部变量是怎么创建的为什么局部变量的值是随机函数是怎么传参的传参的顺序是怎样的形参和实参是什么关系函数调用是怎么做的函数调用是结束后怎么返回的 建议大家在观察函数栈帧创建与销毁时使用的环境不需要太高级的编译器越高级的编译器越不容易学习和观察。同时在不同的编译器下函数调用过程中栈帧的创建是略有差异的具体细节取决于编译器的实现。
基础知识 电脑中的任何指令都在CPU上运行但CPU只负责运算不负责存储。 数据都存储在寄存器缓存和内存中。 想了解函数栈帧的创建和销毁我们就需要了解到内存模型寄存器常用汇编指令。 那关于寄存器和缓存等之间的关系会在后面的博文讲解到。
内存模型 这只是一个大致的介绍后面博文我们也会详细去介绍到内存模型。
寄存器的种类与功能 在我们的函数栈帧创建与销毁。我们重点使用到ESP和EBP。 ESP(esp):栈指针寄存器extended stack pointer。栈顶指针堆栈的顶部是地址小的区域压入堆栈的数据越多esp也就越来越小。在32位平台上esp每次减少4个字节。其内存放着一个指针该指针永远指向系统栈最上面一个栈帧的栈顶。是CPU机制决定的push,pop等指令会自动调整esp的值。EBP(ebp):基址指针指栈的栈底指针。基址指针寄存器extended base pointer)。一般与esp配合使用可以存取某时刻的esp这个时候就是进入一个函数内后CPU会将esp的值赋给ebp此时刻就可以通过ebp对栈进行操作。比如获取函数参数局部变量等。其内存放一个指针该指针永远指向系统栈最上面一个栈帧的底部。 esp和ebp这两个寄存器放置的是地址这两个地址是用来维护函数栈帧的。每个函数调用都需要在栈区创建一个空间。当正在调用某函数时esp和ebp就维护这个函数栈帧的空间。栈区使用从高地址向低地址消耗/使用。 常用的汇编指令
这里我们只讲解几个我们函数栈帧创建等的汇编指令
函数栈帧创建与销毁
了解了上面的基础知识我们先大致来看下函数栈帧怎样创建与销毁 。
首先我们有想过一个问题吗就是main()函数也是函数那也是被哪个函数调用的吗 当然。在VS2013中main()函数也是被其他函数调用的。 接下来我们进入正题函数栈帧的创建与销毁。示例代码
//函数栈帧创建与销毁
#includestdio.h
int Add(int x, int y)
{int z 0;z x y;return z;
}
int main()
{int a 20;int b 10;int c 0;c Add(a, b);printf(%d\n, c);return 0;
}
//为了细致全方面去观察函数栈帧的创建与销毁所以把代码拆分的很细
//F10调试-----→转到反汇编
//转到汇编语言去观察时记得把符号名去掉更易观察
main()函数栈帧的创建 NO1.
首先我们知道关于此时此时栈区_tmainCRTStartup()函数被调用了,接下来它需要调用main() PUSH 把字压入堆栈。 先将ebp的值这个空间大小放置到栈区新开辟的栈帧中。 再将esp向上移动到ebp的栈顶的位置。 (esp的值减少4个字节值减少了ebp这么多的空间大小 首先esp的值减少4个字节再将ebp的值压入栈中。 NO2. MOV 传送字或字节。 将esp的值赋给ebp。这里并不是将esp所指向内存空间的值赋给ebp。 NO3. SUB 减法. 将esp-0E4H228即将esp指针向低地址方向移动0E4H字节。 NO4.
此刻我们发现mian()函数有自己栈区所欲开辟的新空间那接下来 首先将esp的值减少4个字节再将ebx的值压入栈中。 首先将esp的值减少4个字节再将esi的值压入栈中。 首先将esp的值减少4个字节再将edi的值压入栈中。 不确定顺序先后 NO5. LEA(lea):load加载。 load effective address 把[ebp-0E4h]这么多的空间大小放到edi里面去。 NO6. 以上四段汇编代码的意思是 从edi这个位置向下的39h 这么多的空间大小的双字节dword4个字节全部放置为0CCCCCCCCh 这样的内容。 每一次初始化dword共初始化19h次。 main()函数栈帧变量的创建 MOV 传送字或字节。 把0AH10的值赋给 地址为[ebp-8] 的双字节空间 把14h20的值赋给 地址为[ebp-14h] 的双字节空间 把0的值赋给 地址为[ebp-20h] 的双字节空间 那如果没有初始化呢 那么abc的位置就会被初始化为CCCCCCCC随机值。打印abc的时候也就是随机值。 调用Add()函数栈帧的预备工作——传参 NO1. MOV PUSH 将地址为[ebp-14h] 双字节空间大小 的值赋给eax将esp的值减少4个字节将eax压栈到栈中 NO2. MOV PUSH 将地址为[ebp-8]双字节空间大小 的值赋给ecx将esp的值减少4个字节将ecx压栈到栈中 NO3. CALL 先esp的值减少4个字节再将下一条指令的IP00921A30压入栈中。F11之后移动到调用的Add()函数的子程序里。 Add()函数栈帧的创建
现在我们正式进入Add函数。首先和main()函数栈帧一样我们需要在栈区开辟一块新的空间。因为在前面我们详细的讲解了main()函数栈帧的创建这里大家可以先自己动小脑瓜子想想画一画过程图再看最后结果。 将ebp的值压入栈中esp减少4个字节。将esp的值赋给ebp这里并不是将esp所指向的内存空间的值赋给ebp。将esp-0CCh即esp向上移动0CCh的空间大小。将ebx压入栈中esp的值减少4个字节。将esi压入栈中esp的值减少4个字节。将edi压入栈中esp的值减少4个字节。从edi向下33h的双字节空间大小全部初始化为0CCCCCCCCh每一次初始化dword双字节大小共初始化33h次。 Add()函数栈帧变量的创建并运算 NO1. MOV 将0的值赋给内存地址为[ebp-8]的双字节空间。 NO2. MOV 将内存地址[ebp8] 的双字节空间数据内容 赋给eax。 ADD 将内存地址[ebp0Ch] 的双字节空间数据内容 加上eax的值 再赋给eax。 NO3. MOV 将eax寄存器道德数据内容 赋给内存地址为[ebp-8]的双字节空间。 NO4. MOV 将内存地址为[ebp-8]的双字节空间大小中的数据赋给eax。 Add()函数栈帧的销毁
NO1. POP 先将esp所指的地址处的值赋给ediesp值增加4个字节。先将esp所指的地址处的值赋给esiesp值增加4个字节。先将esp所指的地址处的值赋给ebxesp值增加4个字节。 NO2. MOV 将ebp的值赋给esp这里并不是将ebp所指向的内存空间的值赋给esp POP 将ebp弹回到原来main()函数栈帧的栈底位置esp增加4个字节esp来到ebp的栈顶位置 NO3. RET 执行完这条命令就自动返回刚才call指令的下一条。 返回main()函数栈帧 之后的main()函数栈帧的销毁和Add()函数栈帧的销毁同理所以我们就不再讲解了。
问题
NO1.
为什么将call指令的下一条指令的地址压入栈帧中 确保我们调用完Add函数后返回main()函数栈帧时能回到call函数的下一条指令执行。 NO2.
为什么将main()函数栈帧的栈底地址ebp压入栈顶 为了当函数调用返回时esp和ebp都回到原来维护main()函数栈帧的位置。 NO3.
为什么说形参是实参的一份临时拷贝 还没有调用Add函数的时候已经将参数ab传递过去了在函数栈帧中已经为ab创建了一块空间在使用xy的时候返回这里使用即可。所以我们并没有在Add函数中为xy创建空间。 ✔✔✔✔✔最后感谢大家的阅读若有错误和不足欢迎指正
接下来的博文会更新一些练习题到实践中去加深对知识的理解。
代码----------→【gitee:https://gitee.com/TSQXG】
联系----------→ 【邮箱2784139418qq.com】