过程调用约定
2009-06-05 20:47
211 查看
过程调用约定
当程序中的各个过程(函数)被分别编译时,关键是如何规定寄存器的使用规则。当编译某个过程时,编译器必须事先知道需要用到哪些寄存器、哪些寄存器的内容需要保留给其他过程使用等信息。我们称这些寄存器的使用规则为寄存器使用约定或过程调用约定。顾名思义,大多数情况下,这些规定主要是用于约束软件的,而不会受到硬件的限制。因此,大多数的编译器和程序员都必须遵守这些约定以免发生错误。
下面是GCC编译器在MIPS系统下的寄存器使用约定。
1、过程调用
下面说明一个程序(调用者)调用另一个程序(被调用者)的具体步骤。
大多数有关过程调用的记录集中在一个叫做过程调用帧(procedure call frame)的内存块中。使用这块内存区域有以下几个目的:
a. 存放传递给被调用过程的参数值
b. 保留当前过程可以修改但给过程的调用者不希望改变的寄存器值(返回前必须恢复,亦即使用前必须保存)
c. 为过程的局部变量提供足够的内存空间。
在大多数编程语言中,过程调用和返回严格遵从后进先出(LIFO)原则,因此,这些内存可以在堆栈段实现分配和释放,这也是为什么这些内存块有时被称作堆栈帧。
下图是一个典型的堆栈帧。
帧的内存区域包括帧指针$fp(指向帧的首字)寄存器和堆栈指针$sp(指向帧的尾字)寄存器值之间的范围。由于堆栈的存储遵从由高到低的存放顺序,所以帧指针总是高于堆栈指针。运行的程序利用帧指针能快速地存取堆栈帧中的内容。例如,堆栈帧中的参数能够用下面的指令送入寄存器$v0:
lw $v0, 0($fp)
堆栈帧能用多种途径实现;但无论如何,调用者和被调用者必须遵从一系列步骤。下列描述的步骤用于大多数MIPS系统上的调用协议。在过程调用中,该协议在三个时刻发挥作用:在调用者调用被调用者之前,在被调用者开始执行的时刻,以及在被调用者返回到调用者之前的时刻。首先,调用者将过程调用参数放到合适的位置,然后按下列步骤调用目的过程:
1)传递参数。根据协议,前四个参数将被送到寄存器$a0~$a3中,其他参数压到堆栈中,这些参数刚好放在被调用过程的堆栈帧的起始地址处(从而在被调用过程中通过堆栈帧寄存器$fp的值访问这些参数),达到参数传递的目的。
2)保存调用者的寄存器的值。这样,被调用过程就可以随意使用这些寄存器($a0~$a3,$t0~$t9),而无须先保存它们的值。如果调用者在调用之后还想使用这些寄存器,那么它必须在调用前保存寄存器的值)。
3)执行jal指令。该指令把控制转移到被调用者的第一条指令处,并且在寄存器$ra中保存返回地址(而不是在IA-32系统中,将返回地址压栈,遭受缓冲区溢出之攻击)。
在被调用子程序正式运行之前(指执行子程序所代表之功能之前),该程序必须执行以下步步骤(例行公事),以便设置其堆栈帧:
1)通过从当前栈指针(寄存器)中减去栈帧之大小为栈帧分配空间(即$sp = $sp - frameSize)。
2)把被调用者所用寄存器保存到帧中。被调用者在改变其值之前必须保存$s0~S7、$fp以及$ra的值,因为调用者希望调用之后寄存器没有发生改变。而每个过程中如果需要创建新的栈帧则必须保存$fp。而$ra则仅在被调用者再调用时才保存。此外,其他被调用者专用的寄存器也要保存。
3)设置栈帧指针,其值为栈帧大小减去4加上$sp,保存在$fp中(即$fp=$sp+frameSize - 4)。
最后,被调用者执行如下步骤,将控制返回给调用者:
1)如果被调用者为具有返回值的函数,则将返回值保存到寄存器$v0中。
2)恢复被调用者进入时保存的所有寄存器。
3)向$sp加上栈帧大小,将帧从栈中弹出($sp = $sp + frameSize),释放栈帧空间。
4)跳转到$ra中记录的制定地址处。
当程序中的各个过程(函数)被分别编译时,关键是如何规定寄存器的使用规则。当编译某个过程时,编译器必须事先知道需要用到哪些寄存器、哪些寄存器的内容需要保留给其他过程使用等信息。我们称这些寄存器的使用规则为寄存器使用约定或过程调用约定。顾名思义,大多数情况下,这些规定主要是用于约束软件的,而不会受到硬件的限制。因此,大多数的编译器和程序员都必须遵守这些约定以免发生错误。
下面是GCC编译器在MIPS系统下的寄存器使用约定。
1、过程调用
下面说明一个程序(调用者)调用另一个程序(被调用者)的具体步骤。
大多数有关过程调用的记录集中在一个叫做过程调用帧(procedure call frame)的内存块中。使用这块内存区域有以下几个目的:
a. 存放传递给被调用过程的参数值
b. 保留当前过程可以修改但给过程的调用者不希望改变的寄存器值(返回前必须恢复,亦即使用前必须保存)
c. 为过程的局部变量提供足够的内存空间。
在大多数编程语言中,过程调用和返回严格遵从后进先出(LIFO)原则,因此,这些内存可以在堆栈段实现分配和释放,这也是为什么这些内存块有时被称作堆栈帧。
下图是一个典型的堆栈帧。
帧的内存区域包括帧指针$fp(指向帧的首字)寄存器和堆栈指针$sp(指向帧的尾字)寄存器值之间的范围。由于堆栈的存储遵从由高到低的存放顺序,所以帧指针总是高于堆栈指针。运行的程序利用帧指针能快速地存取堆栈帧中的内容。例如,堆栈帧中的参数能够用下面的指令送入寄存器$v0:
lw $v0, 0($fp)
堆栈帧能用多种途径实现;但无论如何,调用者和被调用者必须遵从一系列步骤。下列描述的步骤用于大多数MIPS系统上的调用协议。在过程调用中,该协议在三个时刻发挥作用:在调用者调用被调用者之前,在被调用者开始执行的时刻,以及在被调用者返回到调用者之前的时刻。首先,调用者将过程调用参数放到合适的位置,然后按下列步骤调用目的过程:
1)传递参数。根据协议,前四个参数将被送到寄存器$a0~$a3中,其他参数压到堆栈中,这些参数刚好放在被调用过程的堆栈帧的起始地址处(从而在被调用过程中通过堆栈帧寄存器$fp的值访问这些参数),达到参数传递的目的。
2)保存调用者的寄存器的值。这样,被调用过程就可以随意使用这些寄存器($a0~$a3,$t0~$t9),而无须先保存它们的值。如果调用者在调用之后还想使用这些寄存器,那么它必须在调用前保存寄存器的值)。
3)执行jal指令。该指令把控制转移到被调用者的第一条指令处,并且在寄存器$ra中保存返回地址(而不是在IA-32系统中,将返回地址压栈,遭受缓冲区溢出之攻击)。
在被调用子程序正式运行之前(指执行子程序所代表之功能之前),该程序必须执行以下步步骤(例行公事),以便设置其堆栈帧:
1)通过从当前栈指针(寄存器)中减去栈帧之大小为栈帧分配空间(即$sp = $sp - frameSize)。
2)把被调用者所用寄存器保存到帧中。被调用者在改变其值之前必须保存$s0~S7、$fp以及$ra的值,因为调用者希望调用之后寄存器没有发生改变。而每个过程中如果需要创建新的栈帧则必须保存$fp。而$ra则仅在被调用者再调用时才保存。此外,其他被调用者专用的寄存器也要保存。
3)设置栈帧指针,其值为栈帧大小减去4加上$sp,保存在$fp中(即$fp=$sp+frameSize - 4)。
最后,被调用者执行如下步骤,将控制返回给调用者:
1)如果被调用者为具有返回值的函数,则将返回值保存到寄存器$v0中。
2)恢复被调用者进入时保存的所有寄存器。
3)向$sp加上栈帧大小,将帧从栈中弹出($sp = $sp + frameSize),释放栈帧空间。
4)跳转到$ra中记录的制定地址处。
相关文章推荐
- GetProcAddress 调用过程约定
- C程序使用不同函数调用约定调用汇编子过程
- 函数调用约定及其作用和执行过程
- JAVA中 CallableStatement调用存储过程
- 主函数在调用函数过程中栈空间的使用情况
- 一个OpenStack访问请求在各组件之间的调用过程
- mybatis调用存储过程并且有多个返回output值
- 存储过程中调用JAVA程序段
- javascript进阶学习过程中函数的调用问题
- 在asp中调用sql server的存储过程
- LINQ 中调用存储过程自动绑定列名
- Class.forName("xxx")调用过程
- DLL中调用约定和名称修饰(一)
- 函数调用过程
- Oracle创建存储过程及在Mybatis中的调用
- NFS系统write调用过程(二)
- VS2013调用caffe新建自己的工程详细过程
- C#调用存储过程简单完整例子
- MSSQL 中存储过程跨服务器调用存储过程的时候,编辑Link server 的RPC的时候报错
- Spring Data JPA调用存储过程实例代码