- 参数从右向左压入堆栈
- 调用方清理堆栈
- 名称修饰:函数名加前缀_
- 参数从右向左压入堆栈
- 被调用方清理堆栈
- 名称修饰:函数名前缀加_,后缀@,之后接参数列表字节数
- 前两个小于等于DWORD的参数通过ECX, EDX寄存器传递,其他参数从右向左依次压入堆栈
- 被调用方清理堆栈
- 名称修饰:函数名前缀加@,后缀@,之后接参数列表字节数
prologue 和 epilogue 是函数调用开始时和结束时所作的准备工作,主要和创建和清理堆栈有关,这些代码对于每一个函数都差不多。
push ebp ; 备份调用方堆栈ebp
mov ebp, esp ; 为被调用函数堆栈设置ebp
sub esp, n ; 提升堆栈,用于存放局部变量
值得注意的是,上面最后一条指令 sub esp, n
如果n只有4个字节,通常会被编译器编译为 push ecx
,因为 push 指令长度比sub更小。
mov esp, ebp ; 恢复堆栈为提升前的样子
pop ebp ; 还原调用方ebp
ret
最后一条指令的具体形式和调用约定有关。
// cdecl
int __cdecl func_cde(int a)
{
int local = a;
return local;
}
// stdcall
int __stdcall func_std(int a, int b)
{
int local = a;
return local;
}
// fastcall
int __fastcall func_fast(int a, int b, int c, int d)
{
int local = a;
return local;
}
int main()
{
int local = 1;
int local1 = 1;
func_cde(1);
func_std(1,2);
func_fast(1,2,3,4);
return local;
}
以上C语言源代码通过MSVC编译后得到以下汇编指令:
_func_cde PROC
push ebp
mov ebp, esp
push ecx
mov eax, DWORD PTR _a$[ebp]
mov DWORD PTR _local$[ebp], eax
mov eax, DWORD PTR _local$[ebp]
mov esp, ebp
pop ebp
ret 0
_func_cde ENDP
_func_std@8 PROC
push ebp
mov ebp, esp
push ecx
mov eax, DWORD PTR _a$[ebp]
mov DWORD PTR _local$[ebp], eax
mov eax, DWORD PTR _local$[ebp]
mov esp, ebp
pop ebp
ret 8 ; 被调用方清理堆栈
_func_std@8 ENDP
@func_fast@16 PROC
push ebp
mov ebp, esp
sub esp, 12
mov DWORD PTR _b$[ebp], edx
mov DWORD PTR _a$[ebp], ecx
mov eax, DWORD PTR _a$[ebp]
mov DWORD PTR _local$[ebp], eax
mov eax, DWORD PTR _local$[ebp]
mov esp, ebp
pop ebp
ret 8 ; 被调用方清理堆栈
@func_fast@16 ENDP
_main PROC
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR _local$[ebp], 1
mov DWORD PTR _local1$[ebp], 1
push 1
call _func_cde
add esp, 4 ; cedcl需要调用方清理堆栈
push 2
push 1
call _func_std@8
push 4
push 3
mov edx, 2 ; fastcall的前两个dword参数通过寄存器ecx和edx传递
mov ecx, 1
call @func_fast@16
mov eax, DWORD PTR _local$[ebp]
mov esp, ebp
pop ebp
ret 0
_main ENDP
以上编译出的汇编代码中,_func_cde
,_func_std@8
, @func_fast@16
三个名称反映了MSVC编译器对三种调用约定的函数所采用的名称修饰规则。
调用约定中清理堆栈指的是在函数调用结束后,释放参数所占据堆栈空间,让ESP寄存器回到函数调用前的样子。
不难发现在cedcl中,主调函数main中需要指令 add esp, 4
来恢复一个int类型参数所占据的4个字节堆栈空间,有些编译器上该指令也可能被替换为 pop ecx
。
stdcall和fastcall中则都是通过被调函数的ret指令完成。
参数传递方面也有区别,fastcall中,前两个int类型的参数是通过寄存器ecx和edx来传递的,而其他参数则通过参数列表从右向左的顺序压入堆栈。
cdecl和stdcall中,参数全部通过堆栈传递。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)