When debugging with WinDbg you may notice that all functions are not called in the same way. The "traditional" way would be to push arguments on the stack, call the function and check the return value. However, in many places this may not happen. This is because the x86 architecture has several different calling conventions which I will describe below.
__stdcall
Function parameters are passed on the stack, pushed right to left, and the callee cleans the stack.
__fastcall
The first two DWORD-or-smaller arguments are passed in the ecx and edx registers. The remaining parameters are passed on the stack, pushed right to left. The callee cleans the stack.
__cdecl
Function parameters are passed on the stack, pushed right to left, and the caller cleans the stack. The __cdecl calling convention is used for all functions with variable-length parameters.
Native C++ method call (also known as thiscall)
Function parameters are passed on the stack, pushed right to left, the "this" pointer is passed in the ecx register, and the callee cleans the stack.
COM (__stdcall for C++ method calls)
Function parameters are passed on the stack, pushed right to left, then the "this" pointer is pushed on the stack, and then the function is called. The callee cleans the stack.
Notes:
- All arguments are widened to 32 bits when passed.
- Return values are passed in
eax (or edx:eax). Large structures are returned as pointers to a hidden return structure.
- All registers must be preserved except for the scratch registers:
eax, ecx, and edx (and esp for __cdecl).
The following code gives an example of each convention. The C++ code below includes all the above calling conventions:
callconv.cpp
int __stdcall f_std (int i, long long j, char k, long z)
{
return i + j + k + z;
}
int __fastcall f_fast (int i, long long j, char k, long z)
{
return i + j + k + z;
}
int __cdecl f_cdecl (int i, long long j, char k, long z)
{
return i + j + k + z;
}
class CallConv {
public:
int m_native (int i, long long j, char k, long z)
{
return i + j + k + z;
}
int __stdcall m_std (int i, long long j, char k, long z)
{
return i + j + k + z;
}
int __fastcall m_fast (int i, long long j, char k, long z)
{
return i + j + k + z;
}
int __cdecl m_cdecl (int i, long long j, char k, long z)
{
return i + j + k + z;
}
};
int main (void)
{
CallConv cc;
/* Functions */
f_std (1, 2, 3, 4); /* __stdcall */
f_fast (1, 2, 3, 4); /* __fastcall */
f_cdecl (1, 2, 3, 4); /* __cdecl */
/* Methods */
cc.m_native (1, 2, 3, 4); /* native method */
cc.m_std (1, 2, 3, 4); /* __stdcall method */
cc.m_fast (1, 2, 3, 4); /* __fast method */
cc.m_cdecl (1, 2, 3, 4); /* __cdecl method */
}
And it translates to this in assembly:
callconv.asm
40 00401100 55 push ebp ;
40 00401101 8bec mov ebp,esp ;- main prolog
40 00401103 51 push ecx ;
44 00401104 6a04 push 4 ;
44 00401106 6a03 push 3 ;
44 00401108 6a00 push 0 ;- stdcall
44 0040110a 6a02 push 2 ;
44 0040110c 6a01 push 1 ;
44 0040110e e8f7feffff call f_std(0040102d)
45 00401113 6a04 push 4 ;
45 00401115 6a00 push 0 ;- fastcall
45 00401117 6a02 push 2 ;<--note that large
45 00401119 b203 mov dl,3 ; param is skipped over
45 0040111b b901000000 mov ecx,1; and two smaller are
45 00401120 e803ffffff call f_fast(00401028); put in regs
46 00401125 6a04 push 4 ;
46 00401127 6a03 push 3 ;
46 00401129 6a00 push 0 ;- cdecl
46 0040112b 6a02 push 2 ;
46 0040112d 6a01 push 1 ;
46 0040112f e8e0feffff call f_cdecl(00401014)
46 00401134 83c414 add esp,14h ;<--note cleanup
49 00401137 6a04 push 4 ;
49 00401139 6a03 push 3 ;- native
49 0040113b 6a00 push 0 ;
49 0040113d 6a02 push 2 ;
49 0040113f 6a01 push 1 ;
49 00401141 8d4dff lea ecx,[ebp-1] ;<--note the "this"
49 00401144 e8d5feffff call m_nativeCallConv(0040101e);pointer
50 00401149 6a04 push 4 ;
50 0040114b 6a03 push 3 ;
50 0040114d 6a00 push 0 ;- COM
50 0040114f 6a02 push 2 ;
50 00401151 6a01 push 1 ;
50 00401153 8d45ff lea eax,[ebp-1] ;<--note the "this"
50 00401156 50 push eax ; pointer on stack
50 00401157 e8a9feffff call m_stdCallConv(00401005)
51 0040115c 6a04 push 4 ;
51 0040115e 6a03 push 3 ;- fastcall
51 00401160 6a00 push 0 ;
51 00401162 6a02 push 2 ;
51 00401164 ba01000000 mov edx,1 ;<--note that "this"
51 00401169 8d4dff lea ecx,[ebp-1] ; is treated as first
51 0040116c e8b2feffff call m_fastCallConv(00401023); param
52 00401171 6a04 push 4 ;
52 00401173 6a03 push 3 ;
52 00401175 6a00 push 0 ;- cdecl
52 00401177 6a02 push 2 ;
52 00401179 6a01 push 1 ;
52 0040117b 8d4dff lea ecx,[ebp-1] ;
52 0040117e 51 push ecx ;
52 0040117f e88bfeffff call m_cdeclCallConv(0040100f)
52 00401184 83c418 add esp,18h ;<--note cleanup
53 00401187 33c0 xor eax,eax ; return 0
53 00401189 8be5 mov esp,ebp ;
53 0040118b 5d pop ebp ;- main epilog
53 0040118c c3 ret ;<-- is __cdecl
As a side-note that we can easily see that
main itself is a
__cdecl function as it does not clean up the stack!
UPDATE: 2009-05-01
Wikipedia has list of calling conventions which includes other OS's here:
http://en.wikipedia.org/wiki/X86_calling_conventions