当前演示的函数调用过程是依赖于C语言默认的调用约定——_cdecl
其他的调用约定还有__stdcall和__fastcall,三种都是C、C++使用的调用规则,三种调用约定的差异并不大,只是负责的事情有所不同
一:参数入栈
1.4字节参数入栈,顺序:从右向左入栈(先入最后一个参数),方式:使用寄存器push带入
先将b入栈,同时esp往上走,再将a入栈(从右向左入栈)
2.8字节参数入栈,顺序:从右向左,方式:使用寄存器push带入
先将b._2入栈,然后esp往上移(向低地址移动),然后将b._1入栈,然后esp往上移(向低地址移动),然后将a._2入栈,然后esp往上移(向低地址移动),然后将a._1入栈,然后esp往上移(向低地址移动),方式仍然是从右向左
3.12字节参数入栈,顺序:从右向左,方式:现在栈顶开辟足够该参数的空间,之后将数据复制进去
先开辟12个字节的空间(第一行表示esp=esp-0c,高地址往低地址增长,所以用减),然后先后存入b._1,b._2,b._3,然后再开辟12个字节的空间然后先后存入a._1,a._2,a._3
4.C语言中,大于8字节以后,都是采用3的方式,小于8字节,采用1 2方式
5.在C++中,只要是自定义类型, 无论多大字节,都采用3的方式
6.寄存器:CPU中的变量
7.esp寄存器:栈顶寄存器(低地址)
8.ebp寄存去:栈底寄存器(高地址)
9.唯一标志一段空间就需要有一个头有一个尾
10.mov是取值(把后面的值赋值给前面),push是入栈,sub是减,call是调用函数
二:函数栈帧开辟
1.在参数入栈之后,调用函数的时候,esp往上(往低地址)移动了4个字节,保存的是下一行指令的地址(就是函数调用完成之后,main函数要从那里继续执行)
2.然后把main函数的ebp(栈底寄存器)入栈
3.然后让ebp=esp
4.然后开辟整个fun函数的栈帧(esp=esp-0cc)
5.然后将其他使用的寄存器入栈
6.然后循环初始化成0xcccc cccc
7.然后进行校验
三:返回值返回
4字节的返回值:
方式:将返回值放入寄存器带回
8字节返回值:
方式:将放回值放入两个寄存器带回
大于8字节返回值:
方式:首先在函数参数入栈之后,入栈一个调用方栈帧上的地址(靠近栈顶位置)在返回值返回的时候将返回数据写入到之前入栈的调用方地址上,返回之后将从该地址上将数据取出
注意:在C++中,自定义类型都按照入栈调用方地址的方式(即大于8字节返回值的方式)
四:函数栈退出
1.在之前现场保护入的三个寄存器吐出
2.进行当前函数栈帧的校验
3.将现场保护的寄存器吐出(出栈)
4.让esp=ebp
5.ebp=Pop(把先前入栈的main函数的ebp还原回来)
6.将下一行指令的地址还原
7.清除参数