函数每一次调用都会在栈区上开辟空间;在介绍这一部分知识时,我们需要了解一些汇编语言的基础知识,比如常用指令的作用,同时还要了解常用寄存器的作用。
一:常用汇编指令和寄存器
push指令:它首先减少esp的值,再将源操作数复制到栈地址32位平台上,esp每次减少4字节。
pop指令:它首先把esp指向的栈元素内容复制到一个操作数中,再增加esp的值。在32位平台上,esp每次增加4字节。
mov指令: 用于将一个数据从源地址传送到目标地址,源操作地址的内容不变。
sub指令:减操作指令,从寄存器中减去表示的数值,并将结果保存到目标寄存器中
add指令:用于将两个运算子相加,并将结果写入第一个运算子。
ret指令:用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。
call指令:将程序下一条指令的位置的IP压入堆栈中,并转移到调用的子程序。
二:main()函数栈帧的创建
1.main()函数的汇编代码
2.main()函数部分代码执行
main()函数在运行前也是由其他的函数来调用。而且栈区的地址是由高地址向低地址开辟。
(1)步骤1
push叫做压栈,在栈顶压入一个四字节的区域;mov:把esp的值给ebp并且移动ebp;
(2)步骤2
sub 的效果是开辟一个大小为0E4(十六进制)大小的空间,这也是为main()函数开辟的栈帧。后面的三个push是压入三个空间。
(3)步骤3
这四步是给main()函数的栈帧区域赋值,值为0CCCCCCC;所以为什么我们不进行初始的时候,直接打印会出现“烫烫烫烫....”;并且记录当前esp位置的地址;
(4)创建变量
分别给ebp-8,ebp-20;ebp-32赋值10,20,0;
三.add函数代码执行、
1.传参
(1)存值
分别向eax和ecx寄存器里面存入地址为ebp-20(b:20)和ebp-8(a:10)的值;
这也说明了函数传参的时候是从又到左传递参数;在add函数中先传递b的值,再传递a的值;
(2)压栈
2.add()函数栈帧空间开辟
前三步和开辟main()函数的方法类似。先压栈,再移动esp寄存器开辟函数的栈帧空间
3.压栈和初始化
和main()函数初始的方法类似:
4.赋值
把值0存在ebp-8地址里;
5.运算z=x+y;并返回z
eax是一个擅长运算的寄存器;这三步的作用是把ebp+8地址的值(10)存入eax,再加上ebp+0C地址的值(20),再把值放入ebp-8地址处;
函数在传递形参时,并没有给add函数的局部变量x,y在栈区上开辟空间,是利用eax,ecx进行传参计算。
6.销毁add()函数的空间
pop指令叫做出栈;弹出edi,esi,ebx的空间;esp加上0CC,那么esp在栈空间上向下移动,也是销毁空间;给esp赋值为ebp,最后弹出ebp;
这样的结果为ebp回到了main()函数内部;
四.main()函数撤销空间
过程和add函数的撤销空间的方式类似。