过程调用概述
可执行文件装入的时候是映射到存储空间中
用户栈在高地址当中向低地址增长
栈底位置确定,栈顶专门放在一个ESP寄存器栈指针寄存器。
- test.c
int add(int x,int y){
return x+y;
}
int caller(){
int ti = 125;
int t2 = 80;
int sum = add(t1.t2);//
return sum;
}
- P放参数(实参),放入栈中
- call指令把返回地址压栈,然后转移到被调用过程去执行
- 保存P的现场(即P用到的通用寄存器的内容),并且为自己的局部变量分配空间
- 执行Q函数体,此时返回值放到专门的地方
- Q恢复P的现场,释放局部变量空间
- 取返回地址返回,将控制转移到P
- 为什么要保存现场?
因为调用过程和被调用过程用的都是同一套寄存器 - IA-32的寄存器使用约定
– 调用者保存寄存器:
EAX、EDX、ECX
当过程P调用过程Q时,Q可以直接使用这三个寄存器,不用将它们的值保存到栈中。
如果P在从Q返回后还要用这三个寄存器的话,P应在转到Q之前先保存,并在从Q返回后先恢复它们的值再使用。
– 被调用者保存寄存器:
EBX、ESI、EDI
Q必须先将它们的值保存到栈中再使用它们,并在返回P之前恢复它们的值。–
EBP和ESP分别是帧指针寄存器和栈指针寄存器,分别用来指向当前栈帧的底部和顶部。
问题:为减少准备和结束阶段的开销,每个过程应先使用哪些寄存器?
EAX、ECX、EDX!
- 准备阶段:前三行
第一条指令总是把就的EBP的值压入栈中
此时ESP指向此处
第二条mov指令后,EBP也指向此处,栈底就此形成
第三条指令减法,ESP-24,栈顶移动,留足空间 - 分配局部变量:4、5条
125送到ebp-12的地址
80送到ebp-8的地址 - 准备入口参数:6-9条
80送到ESP+4(即对t2参数赋值)
125送到esp(即对t1赋值)
注意形参赋值有顺序,从右向左
- call:
执行被调用过程,转到add执行
add的第一二条指令同样是push和mov
每个函数都相同 - add准备:
push EBP在caller中的值
形成add栈帧底部
栈底放的是这个函数的调用过程如caller是调用add的过程
执行结束 - 返回值总是在EAX中
- call 返回后总是把下一条指令的地址压入栈中
返回地址实际为倒数第四行的mov的地址
当执行ret时候,ret把返回地址取出送到EIP寄存器(PC) - 把返回值放入sum
- return sum 即 把sum的值放到EAX寄存器
- 结束阶段:最后2条
恢复现场,leave即退栈
让ESP等于EBP,中间所有内容丢失
然后将ESP指向的内容弹出到EBP,即恢复上一个函数的栈底
然后ESP指向上一个单元即返回地址,返回上一个函数
C函数大致过程
–准备阶段
•形成帧底:push指令 和 mov指令
•生成栈帧(如果需要的话):sub指令 或 and指令
•保存现场(如果有被调用者保存寄存器) :mov指令
–过程(函数)体
•分配局部变量空间,并赋值
•具体处理逻辑,如果遇到函数调用时
–准备参数:
将实参送栈帧入口参数处
–CALL指令:
保存返回地址并转被调用函数
•在EAX中准备返回参数
–结束阶段
•退栈:leave指令 或 pop指令
•取返回地址返回:ret指令
参考:南大计算机系统基础(一)