一.前言
1.几个问题
在C语言学习阶段,我们可能会遇到下面几个问题,
在学习完函数栈帧的创建和销毁之后,我们就能更加深刻地理解下面几个问题了
2.几个说明
其次,我们要说明的是:不同编译器下汇编指令的样子是有所差异的
下面给大家看一下同样的代码在VS2013中的样子
同样的代码在Linux中的样子
而且在观察汇编代码学习函数栈帧的创建和销毁的过程中.
不要使用太高级的编译器,越高级的编译器越不容易学习和观察
同时在不同的编译器下,函数的调用过程是略有差异的,具体细节取决于编译器的实现
我们这一篇博客以VS2013为例,学习函数栈帧的创建和销毁的过程
二.相关寄存器和汇编命令的简要说明
三.从汇编代码调试的角度逐步分析函数栈帧的创建于销毁
我们以这份代码为例:
#include <stdio.h> int MyAdd(int a, int b) { int c = a + b; return c; } int main() { int x = 0xA; int y = 0xB; int z = MyAdd(x, y); return 0; }
1.函数栈区的知识:
首先我们要说明两点:
1.函数是开辟在栈区的,栈区空间的使用习惯是
先使用高地址,后使用低地址
也就是说函数栈区是从高到低去开辟的
2.main函数是也是被调用的
具体调用流程如下
2.逐步调试分析
初始情况:
esp(栈顶指针)
ebp(栈底指针)
esp和ebp之间有一块空间
这块空间其实就是__tmainCRTStartup的栈帧
1.保存__tmainCRTStartup这个函数栈帧的栈底地址
执行第一条指令:
push ebp 把ebp的值入栈,并且esp减小
2.正式进入main函数
执行第二条指令:
mov ebp esp 就是把esp的值给ebp
3.开辟main函数栈帧
执行第3条指令
sub esp 0E4h esp = esp-0E4h 也就是esp减少0E4h大的空间
这就是为main函数开辟栈帧
也就是说在汇编中开辟栈帧的方式就是栈顶指针减少
其中
函数的栈帧大小是由编译器决定的
根据什么决定的呢?
编译器可以通过sizeof求出该函数栈帧中的所有变量的具体大小
并进行合理分配栈帧的大小
4.将main函数栈帧中的数据置为随机值
接下来是3条push指令
push ebx push esi push edi
请注意:esp每一次push之后的值都减4
为什么呢?
因为push是压栈操作.我们可以理解为push进去的数据在内存空间上是紧密相邻的
接下来是:
lea edi,[ebp+FFFFFF1Ch] 就是把ebp+FFFFFF1Ch这个地址加载到edi中 其实这个ebp+FFFFFF1Ch就是ebp-0E4h 而这个0E4h就是第三条指令中 sub esp 0E4h 这个esp减去的大小 也就是这个main函数的栈帧大小
下面两条指令:
mov ecx,39h mov eax,0CCCCCCCCh 把16进制数字:39h给ecx 把0CCCCCCCCh给eax
下面这个指令:
rep stos dword ptr es:[edi] • 1
因此我们就可以回答
为什么局部变量的值是随机值呢?
因为函数栈帧创建之后,会对栈帧中的数据进行初始化,
而局部变量是开辟在栈帧中的,
如果没有对该局部变量进行初始化
那么该局部变量的值就是随机值
而VS2013中的随机值就是0CCCCCCCCh
这也就解释了为什么我们经常会见到烫烫烫烫烫烫这样的字符
这就是我们初始化后的栈帧空间
执行完刚才那条指令之后ecx被清0
edi会指向ebp