函数调用栈

不同语言之间的转换

  • 高级程序语言经过编译,变成汇编代码,汇编代码经过汇编器和连接器变成可执行的机器代码。汇编代码是人类可读的代码。机器代码是01序列。

  • 在程序执行前,数据和指令事先存放在存储器中,每条指令和每个数据都有地址。程序启动后,计算机取指令执行。执行过程中,指令和数据从存储器取到CPU,存放到CPU寄存器中,CPU内的寄存器与控制器与ALU进行数据传送与运算,将运算结果写入内存中(存储器)。

  • 寄存器
    IA-32体系中,有8个通用寄存器GPR,2个专用寄存器EIP和EFLAGs和六个段寄存器,间接给出段基址。

指令

  • 计算机的指令有微指令、机器指令和伪指令之分。汇编指令是机器指令的符号表示,以帮助记忆,可以称之为助记符。机器指令和汇编指令是一一对应的,都是机器级指令。

  • 机器指令对源操作数进行操作,操作结果储存到目的操作数中。操作码:操作性质;立即数:操作数在指令中给出,这个数叫做立即数。找到操作数或操作数的地址这个过程叫寻址,操作数在不同的位置,有不同的寻址方式

    将cl寄存器中的内容传送到该地址的存储单元中。
  • 指令可以分为传送指令,如mov,push,pop,leal,in,out;定点算术运算指令,如add,sub,int,dec,neg,cmp;浮点运算指令等等。

函数调用过程

一段c语言代码
1
2
3
4
5
6
7
8
9
10
int add(int x,int y){
return x+y;
}

int main(){
int t1 = 125;
int t2 = 80;
int sum = add(t1,t2);
return sum;
}
过程描述

存放参数t1,t2;调出add执行,取出参数t1,t2,执行运算,保存返回结果,返回到main中。过程调用执行步骤(p为调用者,Q为被调用者)

关于Q过程中的保存P的现场和恢复P的现场

为什么要压入返回地址啊?返回地址是指被调用函数的下一条语句。ret指令会将返回地址送到eip寄存器,即指令指针。

保存现场即保存寄存器,以便被调用者返回前恢复寄存器。
根据IA-32的寄存器使用约定:

  • 调用者保存寄存器:eax,edx,ecx
    如果调用函数P调用完Q返回后,还需用到eax寄存器,则需要P在调用Q前保存。*一般而言P调用Q之后不需要用到eax寄存器,所以一般会将操作数优先存在eax,edx,ecx这类寄存器,以减少准备阶段和结束阶段的开销。

  • 被调用者保存寄存器:ebx,esi,edi
    如果被调用者Q需要用到ebx,则Q需要保存到栈中再使用。

  • ebp:帧指针寄存器,指向当前栈帧底部,高地址 esp:栈指针寄存器,指向当前栈帧顶部,低地址。如果以向上为高地址的话,栈就是个倒置的桶,栈帧底部abp描述的就是杯底,底部为高地址,没毛病。

栈和栈帧的变化
相应的汇编指令
1
2
3
4
5
6
7
8
9
10
int add(int x,int y){
return x+y;
}

int caller(){
int t1 = 125;
int t2 = 80;
int sum = add(t1,t2);
return sum;
}
汇编代码 操作
pushl %ebp 将旧ebp地址压栈(以便回到原栈底),esp地址-4,指向下一个存储单元
movl %esp,%ebp 将esp的值赋给ebp,ebp指向了当前栈帧的底部
subl $24,%esp 把esp减去了24,开辟内存空间
movl $125,-12(%ebp) 125送到ebp-12的位置
movl $80,-8(%ebp) 80这个立即数送到ebp-8的位置
movl -8(%ebp),%eax
movl %eax,4(%esp) 把ebp-8的数送到esp+4的位置,在对t2参数进行赋值
movl -12(%ebp),%eax
movl %eax,(%esp) 同理,在进行t1参数的赋值
call add 转入add函数体执行,同理第一二条指令是push ebp;mov esp,ebp形成add栈帧的底。return返回参数总是在eax中。call指令总会把下一条指令(movl %eax,-4(%ebp))压入到栈中。add最后一条指令是ret指令,ret指令会将返回地址取出送到eip寄存器(指令指针)中。
movl %eax,-4(%ebp) 将eax的值(即return的和)传送至ebp-4的位置
movl -4(%ebp),%eax 把返回值sum再放入eax寄存器中
leave 退栈,将ebp的内容传送到esp,将esp又指向了ebp;再执行pop指令,把esp指向的内容弹出到ebp内,恢复ebp的内容(ebp回到了p过程的栈底)。esp指向上一个单元,该单元存放的是返回地址。
ret return指令又可以返回到p(调用函数)中执行

准备参数入口,将值再复制了一遍是为什么?
假设回调函数为swap(&t1,&t2),要改变局部变量的之前是不是得先存起来,不然令t1=t2,t1原来的值消失了怎么办,简单比喻一下。

0%