C函数调用原理

函数调用处理过程

The C calling convention in 16-bit programs is as follows. In the following description, the words caller and callee are used to denote the function doing the calling and the function which gets called.

  • The caller pushes the function's parameters on the stack, one after another, in reverse order (right to left, so that the first argument specified to the function is pushed last).
  • The caller then executes a  CALL  instruction to pass control to the callee. This  CALL  is either near or far depending on the memory model.
  • The callee receives control, and typically (although this is not actually necessary, in functions which do not need to access their parameters) starts by saving the value of  SP  in  BP  so as to be able to use  BP  as a base pointer to find its parameters on the stack. However, the caller was probably doing this too, so part of the calling convention states that  BP  must be preserved by any C function. Hence the callee, if it is going to set up  BP  as a frame pointer, must push the previous value first.
  • The callee may then access its parameters relative to  BP . The word at  [BP]  holds the previous value of  BP  as it was pushed; the next word, at  [BP+2] , holds the offset part of the return address, pushed implicitly by CALL . In a small-model (near) function, the parameters start after that, at  [BP+4] ; in a large-model (far) function, the segment part of the return address lives at  [BP+4] , and the parameters begin at  [BP+6] . The leftmost parameter of the function, since it was pushed last, is accessible at this offset from  BP ; the others follow, at successively greater offsets. Thus, in a function such as  printf  which takes a variable number of parameters, the pushing of the parameters in reverse order means that the function knows where to find its first parameter, which tells it the number and type of the remaining ones.
  • The callee may also wish to decrease  SP  further, so as to allocate space on the stack for local variables, which will then be accessible at negative offsets from  BP .
  • The callee, if it wishes to return a value to the caller, should leave the value in  AL AX  or  DX:AX  depending on the size of the value. Floating-point results are sometimes (depending on the compiler) returned in  ST0 .
  • Once the callee has finished processing, it restores  SP  from  BP  if it had allocated local stack space, then pops the previous value of  BP , and returns via  RETN  or  RETF  depending on memory model.
  • When the caller regains control from the callee, the function parameters are still on the stack, so it typically adds an immediate constant to  SP  to remove them (instead of executing a number of slow  POP instructions). Thus, if a function is accidentally called with the wrong number of parameters due to a prototype mismatch, the stack will still be returned to a sensible state since the caller, which knows how many parameters it pushed, does the removing.

调用过程示例及测试用例

/*
	 
以三个参数为例子,AT&T32汇编,栈向低地址增长
fun(p0, p1, p2);
1.参数入栈,right->left
	gcc的做法:
       其中p0 p1 p2姑且认为是立即数
	movl $p2 8(%esp)
	movl $p1 4(%esp)
	movl $p0 (%esp)


 	 |_______|
ebp->|_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|<-esp
 	 |_______|
 	 |_______|
 	 |_______|


2.call fun


 	 |_______|
ebp->|_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|
 	 |__ret__|<-esp
 	 |_______|
 	 |_______|


3.跳转到fun代码
	push %ebp
	movl %esp, %ebp
 
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|
 	 |__ret__|
ebp->|__ebp__|<-esp
 	 |_______|
 	 |_______|
 
其中存储的ebp是第2步中的ebp,即之前的ebp,不是图左面的ebp
如果栈向低地址增长
那么
old ebp = [ebp]
ret = [ebp + 4]
p0 = [ebp + 8]
p1 = [ebp + 12]
p2 = [ebp + 16]
 
4.
decrease ESP further, so as to allocate space on the stack for local variables,
which will then be accessible at negative offsets from EBP.
 
	subl $24, %esp
	根据具体需要分配栈空间,这里示例24/4=6个4B局部变量


 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|
 	 |__ret__|
ebp->|__ebp__|
 	 |_______|
 	 |_______|
 	 |_______|<= esp + 12 = ebp - 12
 	 |_______|
 	 |_______|
 	 |_______|<-esp
 	 |_______|
 	 |_______|
 
 
5.返回
	5.1 调整栈指针
	方式1:addl	$24, %esp
	方式2:movl %ebp, %esp
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|
 	 |__ret__|
ebp->|__ebp__|<-esp
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|


	 5.2弹出旧frame指针=销毁当前栈帧
	popl	%ebp


 	 |_______|
ebp->|_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|
 	 |__ret__|<-esp
	 |__ebp__|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |_______|


	5.3 返回调主
	ret
 
 	 |_______|
ebp->|_______|
 	 |_______|
 	 |_______|
 	 |_______|
 	 |__p2___|
 	 |__p1___|
 	 |__p0___|<-esp
 	 |__ret__|
	 |__ebp__|
 	 |_______|
 	 |_______|
 	 |_______|


	上面说要调整esp,把参数移出掉。gcc没有做这一步。esp就保留在这里
当下面再次调用函数时,旧值就被覆盖掉。参看第一步!


5.1和5.2步,可以使用leave指令代替


 */


//验证
#include <stdio.h>
#include <stdlib.h> 
void fun(int a, int b)
{
	int local;
	unsigned int i, j;
	long ret;


	local = 12;
	a = 10;
//	printf("%d\n", a);
#if 1
	asm volatile (
			"movl %%ebp, %%eax\n\t"
			"movl %%esp, %%ebx\n\t"
			:"=a"(i), "=b"(j)
			:);


	printf("ebp = %p; esp = %p\n", i, j);
	printf("local auto var : %d\n", i - j); //局部变量占用空间


	asm volatile (
			"movl 12(%%ebp), %%eax\n\t"//参数b
			"movl 8(%%ebp), %%ebx\n\t" //参数a
			"movl 4(%%ebp), %%ecx\n\t" //ret
			:"=a"(i), "=b"(j), "=c"(ret)
			:);
	printf("p2 = %x; p1 = %x; ret = %p\n", i, j, ret);
#endif
}


int main(int argc, char *argv[])
{
	fun(1, 2);
	exit(0);
}

总结

1-如果自行编写汇编,那么就要自行处理这些工作,以及对某些寄存器的压栈出栈的保护措施

2-第5.2步,当ebp弹出后,一个栈帧就不存在了,即函数环境已经撤销,因此如果fun内return一个局部变量的引用,也就无意义了。这是最常见的问题!!!

3-局部变量空间的分配,换言之,第4步,对esp的调整,其大小要根据编译时符号表而定!


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值