x86汇编 函数调用

函数调用一般有个模板。

push ebp
mov ebp,esp
//这里提升堆栈
sub esp,0x40  
//这里是开辟缓冲区,不同编译器开辟的缓冲区大小不同。
push edi
push esi
push ebx
//保留现场
lea edi, dword ptr ss:[ebp-0x40]
mov ecx,0x10
mov eax,0xcccccccc
rep stos dowrd ptr es:[edi]
//填充缓冲区
----------------
这里是写函数的功能
----------------
    pop ebx
    pop esi
    pop edi
    //恢复现场
    mov esp,ebp
    pop ebp
    ret

执行完上面代码,堆栈图差不多就是这个样子,(该图,下面是高址,上面是低址)
中间的ccc….就是填充缓冲区。填充后可以用来写入局部变量。
EBP后面的高址,存有恢复用的EIP,和call函数前push的参数。
EBP前面的低址,用来存局部变量。


根据函数调用约定的不同,平衡堆栈的方式不同
如果是__cedcl约定,是在母函数中平衡堆栈,就是函数调用完返回后,在调用者的函数体内 add esp,xx 来平衡堆栈。
例如母函数体内:
push xx call 子函数 add esp,0x4

有几种函数调用约定,不同的调用约定,参数的传递,堆栈的平衡方式不同。下面是三种常见的调用约定:
__cedcl 约定
参数从右往左,依次入栈。堆栈平衡在母函数中完成。
在函数调用前,将参数压栈。
push xxx push xxx ... 然后call 函数。 //函数执行完毕 add esp ,xx //在母函数中平衡堆栈

stdcall 约定
参数从右往左依次入栈,在子函数中 平衡堆栈。
参数还是在调用前push 入栈。
平衡堆栈时,在 子函数 ret 后加个 xx 恢复堆栈
例如 ret 0x8,相当于 pop eip add esp,0x8

fastcall 约定
参数从右往左依次入栈,在子函数中 平衡堆栈
参数1个或者两个用 寄存器 传参,多于两个,多余两个的部分还是压栈传参。
例如 func(int a,int b,int c)
push c mov eax,b mov ecx,a
然后再call 函数,最后在子函数中平衡堆栈。