简单了解
想要了解函数栈帧得先知道两个寄存器(ebp,esp),这两个寄存器存放的是地址,这两个地址是用来维护函数栈帧的。每一个函数调用都要在栈区创建一个空间。
这是根据右侧代码画了一个简略的图
其实,main也是被其他函数调用的。
深度剖析
接下来我们根据之前的右侧代码转成汇编深度剖析它的行为。
这是调用main之前的样子。
我们来看一下代码转成反汇编的样子。
main栈帧的开辟
push ebp :将ebp的值压栈。
mov ebp,esp :将esp的值给ebp。
sub esp,0E4h :esp 减 0E4h。意味着esp往上走了。
push ebx push esi push edi :压栈
lea(load effective address) 加载有效地址。
把[ebp-0E4h]加载到edi, 把39h放在ecx, 把0CCCCCCCCh放在eax。
dword(double word), 一个word两字节,dword四字节。
从edi向下每次初始化dword(四字节),初始化ecx(39h)次,初始化内容为eax(0CCCCCCCCh)。
main局部变量创建
把0Ah放到[ebp-8], 把14h放到[ebp-14h], 把0放到[ebp-20h]。
函数局部变量创建的基本思路:先创建函数栈帧,在栈帧里面找一些空间存放。
给Add传参
把ebp-14h的值赋给eax然后再把eax的值压栈,同理把ebp-8的值赋给ecx然后再把ecx值压栈。
调用Add
接下来是call指令,执行call同时会把call的下一条指令的地址压入栈。
进入Add
这里开辟Add的函数栈帧,和之前开辟的思想是一样的。
把[ebp+FFFFFF34h]加载到edi, 把33h放到ecx, 把0CCCCCCCCh放到eax。从edi这个位置开始向下到ebp所有内容初始化成CCCCCCCC。
计算任务
先在ebp-8位置初始化为0,这是创建临时变量z, 然后找到之前传入的参数相加放入eax再给z, 返回时z把值给eax,函数返回z会销毁,所以要存入寄存器中。
返回 销毁Add
pop: 将栈顶元素弹出放到指定位置。
把edi, esi, ebx弹出,再把ebp赋值给esp。
然后pop ebp, pop ebp的意思:把栈顶上那个元素弹出,放进ebp里。此时栈顶的元素存放着main时期对应的ebp值,所以此时ebp又回到了main时期ebp的位置。
此时栈顶元素是回去的地址,ret负责弹出栈顶元素并跳转到该地址。
接下来是形参的销毁。esp+8是往下走两个。
再将eax值给c。