先来简单说一说栈指针和帧指针。
栈指针就是用来存储栈顶的地址,帧指针是用来存储函数刚被调用时候的地址。
举个例子:
int add(int a,int b){
int c=a+b;
return c;
}
这是被调函数。
int x=add( 5,7);
这是主调函数的一个语句。
接下来是主调函数的反汇编代码:
8048459: movl $0x7,0x4(%esp)
8048461: movl $0x5,(%esp)
8048468: call 8048434
804846d: mov %eax,-0x8(%ebp)
再看被调函数的反汇编代码:
8048434: push %ebp
8048435: mov %esp,%ebp
8048437: sub $0x4,%esp
804843a: mov 0xc(%ebp),%eax
804843d: add 0x8(%ebp),%eax
8048440: mov %eax,-0x4(%ebp)
8048443: mov -0x4(%ebp),%eax
8048446: leave
8048447: ret
前边的十六进制就是代码所在的内存地址,首先执行第一条指令,将7装入esp+4地址中,再将5装入esp地址中,接下来执行call指令,call指令后的地址是被调函数的入口地址,call的作用是将下一条指令的地址压入运行栈,这个地址就是函数返回时的地址,接下来调用被调函数的代码,第一条指令是将ebp的原值压入运行栈,然后将esp的值赋给ebp,同时将esp减4,这是为了给被调函数的局部变量c留空间,然后将ebp+12的值装入eax寄存器,再将ebp+8的值与eax中的值相加保存在eax寄存器中,然后将eax寄存器中的值放入ebp-4地址中,至此,函数中的int c=a+b;指令就执行完了,接下来执行将ebp-4中的值装入eax寄存器中,接下来的指令是将ebp的值赋给esp,同时将栈顶元素弹出并赋给ebp,这样ebp和esp就回到了函数调用之初的状态,接下来的指令是将返回地址弹出并跳转至该地址,这样就回到了主调函数中,然后将eax的值装入ebp-8地址内,就是将c的值赋给x,这样整个函数调用就完成了。
以上就是我对函数调用的一些理解。