调用约定:
1、在执行子函数之前,程序先压入子函数的所有参数,以逆序的方式压入(即先压入最后一个参数)。
2、然后程序执行call指令,指定哪一函数即将执行,call指令主要做了以下两件事情:
a)第一,将下一指令的地址压入栈(即函数的返回地址);
b)第二,更改%eip,使其指向函数的起始地址。
该操作后,函数栈如下所示:
Parameter #N
...
Parameter 2
Parameter 1
Return Address <--- (%esp)
3、接下来函数本身需要做以下事情:
a)首先,执行pushl %ebp保存函数帧指针;
b)其次,执行movl %esp, %ebp拷贝栈指针到函数帧指针寄存器中;此操作使得用户可以通过%ebp来访问到到函数的参数,%ebp通常是函数开始时的栈指针,每个参数可以用%ebp基址指针寻址的方式进行访问;
c)然后,函数需要预留一定的栈空间,来存储所有的局部参数,他们的声明周期与该函数相同
执行后,其函数栈如下所示:
Parameter #N <--- N*4+4(%ebp)
...
Parameter 2 <--- 12(%ebp)
Parameter 1 <--- 8(%ebp)
Return Address <--- 4(%ebp)
Old %ebp <--- (%ebp)
Local Variable 1 <--- -4(%ebp)
Local Variable 2 <--- -8(%ebp) and (%esp)
4、当函数结束时,将做以下3件事
a)将返回值存储至%eax;
b)恢复栈信息,
(movl %ebp, %esp) (popl %ebp)
c)执行ret指令。将栈顶值(函数返回地址)弹出,并设置%eip为该值
备注:(%ebp) has the old %ebp , 4(%ebp) has the return address, and 8(%ebp) is the location of the first parameter to the function.