汇编调用函数的过程
push arg2 func: push %ebp 保存caller的栈顶
push arg1 mov %esp,%ebp ebp保存callee的新栈顶
push arg0 保存用到的寄存器值
eip-->call func 递减esp创建局部变量
递增esp销毁局部变量
恢复用到的寄存器值
mov %ebp,%esp 恢复callee的栈顶
pop %ebp 从callee的栈顶弹出caller的栈顶
ret n 弹出函数的返回地址(n可以用来销毁传递参数所占用的字节)
函数调用后栈中的内容
高地址 arg2
arg1
arg0
函数的返回地址【call后面的指令地址】
低地址 ebp--->caller的栈基地地址
1) EBP是栈基址的指针,永远指向栈底(高地址),ESP是栈指针,永远指向栈顶(低地址)。
2) CALL指令用来调用一个函数或过程,此时,下一条指令地址会被压入堆栈,以备返回时能恢复执行下条指令。
3) RET指令用来从一个函数或过程返回,之前CALL保存的下条指令地址会从栈内弹出到EIP寄存器中,程序转到CALL之前下条指令处执行
4) ENTER是建立当前函数的栈框架,即相当于以下两条指令:
pushl %ebp
movl %esp,%ebp
5) LEAVE是释放当前函数或者过程的栈框架,即相当于以下两条指令:
movl %ebp,%esp
popl %ebp
关于WINDOWS的C函数调用
__cdecl 最大好处在于由于是调用者清理栈【清理压入栈中的函数参数】,它可以处理可变参数,缺点则在于它增加了程序的大小,因为在每个调用返回的时候,需要多执行一条清理栈的指令。
__stdcall 是在windows程序设计中出现的最多的调用规则,所有的不可变参数的API调用都使用这个规则【由callee清除压入栈中的函数参数】。
__fastcall 在windows内核设计中被广泛的使用,由于两个参数由寄存器直接传递,采用这种规则的函数效率要比以上两种规则高【由callee清除压入栈中的函数参数】。
Int __cdecl func(void* p);
Push p
Call func
Add esp,4 //注意这里,由于是cdecl调用,需要调用者清栈。
Int __stdcall func(void* p);
Push p
Call func //由被调用者清除栈
linux系统调用
Linux会有6个寄存器使用来传递这些参数:eax (存放系统调用号)、 ebx、ecx、edx、esi及edi来存放这些额外的参数