C与汇编程序的相互调用
函数调用操作包括从一块代码到另一块代码之间的双向数据传递和执行控制转移。数据传递通过函数参数和返回值来进行。另外,我们还需要在进入函数市为函数的局部变量分配存储空间,并且在推出函数时回收这部分空间。Intel 80x86CPU为控制传递提供了简单的指令,而数据的传递和局部变量存储空间的分配和回收则通过栈操作来实现。
1 栈帧结构和控制转移方式
大多数CPU上的程序实现使用栈来支持函数调用操作。栈被用来传递函数参数、存储返回信息、临时保存寄存器原有值以备恢复以及用来存储局部数据。单个函数调用操作所使用的栈部分被称为栈帧(stack frame)结构,期一般结构如下图所示。栈帧结构的两端由两个指针来指定。寄存器ebp通常用作帧指针(frame pointer),而esp则用作栈指针(stack pointer)。在函数执行过程中,栈指针esp会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针ebp进行。

对于函数A调用函数B的情况,传递给B的参数包含在A的栈帧中,当A调用B时,函数A的返回地址(调用返回后继续执行的指令地址)被压入栈中,栈中该位置也明确指明了A栈帧的结束处。而B的栈帧则从随后的的栈部分开始,即图中保存帧指针(ebp)的地方开始。再随后则用于存放任何保存的寄存器值以及函数的临时值。
B函数同样也是用栈来保存不能放在寄存器中的局部变量值。例如
指令CALL和RET用于处理函数调用和返回操作。调用指令CALL的作用是把返回地址压入栈中并且跳转到被调用函数开始处执行。返回地址是程序中紧随调用指令CALL后面一条指令的地址。因此当被调用函数返回时就会从该位置继续执行。返回指令RET用于弹出栈顶处的地址并跳转到该地址处。
IntelCPU采用了所有函数必须遵守的寄存器用法统一惯例。该惯例指明,寄存器eax,edx,和ecx的内容必须有调用者自己负责保存。当函数B被A调用时,函数B可以在不用保存这些寄存器内容的情况下任意使用他们而不会毁坏函数A所需要的任何数据。另外,寄存器ebx,esi和edi的内容则必须由被调用者B来保护。还有寄存器ebp和esp也必须遵守第二个惯例用法。
函数调用举例
void swap(int *a,int *b)
{
int c;
c=*a;*a=*b;*b=c;
}
int main()
{
int a,b;
a=16;b=32;
swap(&a,&b);
return(a-b);
}

在汇编程序效用一个C函数时,程序需要首先按照理想顺序吧函数参数压入栈中,即函数最后(最右边的)一个参数先入栈,而最左边的第一个参数在最后调用指令之前入栈,如下图所示。然后执行CALL指令去执行被调用的函数。在调用函数返回后,程序需要再把先前压入栈中的函数参数消除掉。

从C程序中调用汇编程序函数的方法与汇编程序中调用C函数的原理相同。

浙公网安备 33010602011771号