我在通过调试时,看反汇编上的步骤来分析函数的调用过程,栈帧的创建和销毁。
原码如下:
#include <stdio.h>
int Add(int x,int y)
{
int sum = 0;
sum = x+y;
return (sum);
}
int main()
{
int a = 2;
int b = 3;
int ret = 0;
ret = Add(a,b);
return 0;
}
按f10,并转到反汇编
转到反汇编时,看到各种没见过的标识符,一脸蒙蔽,所以我们先了解这些标识符都是干嘛的。
esp:esp寄存器里存储的是在调用函数之后,栈的栈顶。并且始终指向栈顶。
ebp:ebp寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行函数调用之前,由esp传递给ebp的。(在函数调用前你可以这么理解:esp存储的是栈顶地址,也是栈底地址。)
push:压入操作,把一个32位的操作数压入堆栈中,这个操作在32位机中会使得esp被减4(字节),esp通常是指向栈顶的,这里顶部是地址小的区域,那么,压入堆栈的数据越多,esp也就越来越小
mov:数据传送。第一个参数是目的操作数,第二个参数是源操作数,就是把源操作数拷贝到目的一份。
lea:取得第二个参数地址后放入到前面的寄存器(第一个参数)中。
rep stos:rep指令的目的是重复其上面的指令.ECX的值是重复的次数.
STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址
———————————————————————————————————————————————————
这些简单的指令了解了以后我们来用图片和文字解释
1.
2.开始调用main函数;执行第一条指令
push ebp //压栈,放入一个ebp在栈顶,而esp始终指向栈顶,所以如图
3.执行第二条语句
mov ebp,esp //将esp赋予ebp ,ebp和esp暂时指向同一位置。
4.执行第三条语句
sub esp,0E4h //将esp减去0E4h,0E4h是16进制数,由于开辟的栈空间是由高到低,所以esp往上走0E4h.
5.执行push ebx
push esi
push edi //这里的 ebx,esi ,edi,为三个寄存器,分别压栈。此时为main开辟的空间不够了,就再往上加三块。
6.接下来执行
leaedi,[ebp-0E4h] //看上图,将ebx的地址传到edi中
movecx,39h//将39h数值传给ecx寄存器中
moveax,0CCCCCCCCh//将0CCCCCCCCh传给eax寄存器中
rep stosdword ptr es:[edi]//从edi中的地址(相当于ebx)向下拷贝eax中内容,ecx次
到这里main 函数调用完成。
———————————————————————————————————————————————————
再看自己想写出的逻辑
1.当给a赋值2时,我们看一下内存变化
在ebp-4位置上赋值2。而此时a=2局部变量已经创建,局部变量在栈上创建,说明现在开辟的空间就在栈上,所以这块空间叫做栈帧。
依次给变量b,ret赋值3,0.
2.准备调用Add函数
将b变量的值赋给eax,并将eax压栈,将a变量值赋给ecx,并压栈,
call指令下一条指令的地址,并压栈(函数调用完用它返回)
此时按F11进入Add函数。
———————————————————————————————————————————————————
1.执行前10条指令(和开辟main函数空间类似)
push ebp//ebp压栈,此时ebp为main函数栈帧中的ebp
mov ebp,esp //将esp传给ebp
sub esp,0cch//给Add函数预开辟一块空间
//...//初始化空间0ccccccccch
2.给变量sum初始化并传值。
mov dword ptr [epb-8],0 //给ebp-8处给sum初始化0,为什么是ebp-8不是ebp-4呢,这是因为VS2010在Debug模式下,int变量占用12个字节。可以这样认为,Debug模式下,在int变量的附近增加了8个字节,用于存储调试信息。当我们把模式设为Release,就会发现栈上连续定义的int变量,地址相差4个字节。
moveax,dword ptr [epb+8]//把eax+8处的值(看上图ebp下两格处a),传给eax
addeax,dword ptr [epb+0ch]//把eax加上 ebp+0ch处的值(看上图ebp下三格处b),传给eax
movdword ptr [epb-8],eax //把eax的值传给sum
给sum传值完成
此时 return (sum),并且
moveax,dword ptr [epb-8]//将sum的值存在寄存器eax中
接下来出栈。。不容易。。。
———————————————————————————————————————————————————
前三行指令,分别将寄存器pop出栈,
movesp,ebp//将esp栈顶下移
popebp//将ebp出栈,此时下面还有一ebp,相当于下移。
ret//回到call指令的下一条指令。相当于将call指令pop出栈。
此时转跳到了esp指向处(call指令下条指令)
———————————————————————————————————————————————————
addesp,8//esp向下跳两格
movdword ptr [ebp-20h],eax//将eax,也就是之前sum的值5传给ebp-20h处,看之前的图,ret就是ebp-20处。所以就酱sum的值传给了ret
———————————————————————————————————————————————————
就算完成了吧。
小结:仔细思考,慢慢推,规律就出来了。最重要就是耐心。