7.过程调用
过程调用的机器级表示
函数调用时如何传递参数,如何把控制转移到被调用的过程,寄存器的使用约定,递归函数的实现。
7.1
7.1.1 过程调用概述
int add(int x, int y)
{
return x+y;
}
int main()
{
int t1 = 125;
int t2 = 80;
int sum = add(t1, t2);
return sum;
}
通过这个程序,关注的问题:
这个过程调用的机器级代码是什么
如何把参数传给形式参数xy
如何把结果返回给caller
main为调用函数,add为被调用函数,main中有一串指令序列是存放参数的调用add,在add里肯定是有指令序列取出参数的。
CALL指令和RET指令,在硬件层次上做的事情。
**参数通过栈(stack)来传递,**那么栈在哪里呢?
栈是个存储空间,接下来要看看存储空间。
可执行文件在磁盘上,装入RAM的时候是映射到一个存储空间里的,不同的东西反倒不同的段里。
栈在一个比较高的位置,栈由上向下生长。
调用过程:
1(调用者)入口参数保存
2(调用者)返回地址保存
3(被调用者)保存调用者的现场,并为自己的非静态局部变量分配空间
4(被调用者)执行函数体
5(被调用者)恢复调用者的现场,释放局部变量空间
6(被调用者)取出返回地址,将控制转移到IP
12前面两个过程是CALL指令做的事情;3为准备阶段,保存现场,分配自己的空间,生成栈帧,然后就可以做具体的处理了,返回值放到合适的地方,然后5恢复现场,6执行RET指令,就回去了。
现场指的是通用寄存器的内容,因此寄存器是同一套。
关于寄存器的约定,哪些在调用过程用,哪些在被调用过程用。IA32的寄存器使用约定。
调用者在调用前去保存内存的寄存器:EAX,EDX,ECX,被调用者可以直接使用不需要保护内容
被调用者需要去保存的寄存器:EBX,ESI,EDI,子程序要先压栈在使用,在过程调用返回前要弹出
EBP和ESP是帧指针寄存器和栈指针寄存器,用来指向当前栈帧的底部和顶部
为了减少准备阶段和结束阶段的开销,Q里面可以刻意的去选择EAX,EDX,ECX,不够用在去用其他的。
7.1.2 过程调用的参数传递
入口参数的位置。
最右边的参数先进去
call做了三个事情
R[esp] <- R[esp] - 4
M[R[esp]] <- 返回地址(call的下一条指令的地址)
R[eip] <- add函数地址
每个过程的第一条指令总是
所以,参数1的位置就是EBP+8,EBP+12
IA32里,char和short也是分配4个字节,因此在看反汇编程序的时候,要能看出这是在调用参数。
举个例子,交换两个数的程序,按地址传送和按值传送,观察
经历了准备阶段,分配局部变量,准备入口参数,调用子函数。
调用嵌套也是一样的。
返回值放在 EAX 寄存器里,约定好的。
leave指令是退栈,等价于
把当前的栈切换到调用caller的函数的栈,caller的内容就释放掉了。
总结一下一个C语言过程(函数)的结构
准备阶段
形成栈底,push和mov,处理BS和SP的指向
生成栈帧(如果需要),sub或者add
保存现场(如果有被调用者保存的寄存器),mov
函数体
分配局部变量空间,并赋值
具体逻辑处理,如果又有函数调用
准备参数,实参送栈帧入口参数处
call,保存返回地址并转被调用函数
在eax中准备返回参数
结束阶段
退栈,leava,pop
取返回地址,ret指令
7.1.3 过程调用的参数传递
举例子,在调用子函数int fun(int a1,int a2,int a3)前,准备了3个参数
参数入栈的顺序从右往左,在栈里面就是自上而下。
执行了call之后,需要4个字节存放返回地址,原来esp的值给ebp了,并且ebp压栈,这时候原来的参数地址可以用 (%ebp)+8 来找到。
IA-32 体系里,不管参数是什么类型,都是分配的4个字节。
参数传递举例子
编译后反汇编查看程序做了什么事情
按照地址传递,用的指令是leal
在看这类调用时,心里有栈,对程序和指令的行为会清晰很多。
这是c语言课里讲的参数传递的问题,现在从底层角度有了更深入的认识。
7.1.4 过程调用举例
caller的过程为P,
7.1.6 递归过程调用
一个最简单的递归调用程序
由分析可以看出,栈一直向下生长,每递归一次,都有个新栈帧,所以递归次数太多会使得空间开销过大。
7.1.6 过程调用举例
不同系统执行可能不太一样。编译器对局部变量分配方式可能不同。
有一些问题需要去考虑
为何每次返回不一样
为什么会存储保护错
栈帧中的状态如何
i = 4时,EBP就被改变了,后面的就乱了,会有存储保护错。
7.2 选择和循环
7.2.1 选择
if-else 语句的机器级表示
举例子
机器实现基本上和 C 差不多,还是很好理解的。
switch-case 语句也是一种选择分支
7.2.2 循环
最后更新于
这有帮助吗?