Ich habe heute etwas Recherche in Hinsicht für meine Bachelorarbeit betrieben und mich etwas umfangreicher mit calling conventions beschäftigt. Um mir das Ganze auch zu merken habe ich gleich ein kleines Kapitel darüber geschrieben.
Ich gebe zu beachten, dass es 1.) Bestimmt viele viele Rechtschreibfehler darin gibt weil ich es im Word ohne Rechtschreibprüfer geschrieben habe
und 2.) Womöglich nicht alle Informationen 100% richtig sind. Ist die Arbeit von einem Tag und ich bin selbst erst am Lernen.
Wenn sich jedoch jemand dafür interessiert der auch noch eher im Anfängerstadium ist, ist sicher was hilfreiches dabei.
Auf eventuell Falschinterpretationen/Informationen kann man mich gerne hinweisen
Achja und nachdem ich meine Bakkarbeit in Englisch schreib habe ich diesen Text in Englisch geschrieben als kleine "Vorbereitung"
Calling conventions Everytime a function is called within a programm the parameters and the local variables have to be stored somewhere. It is also very important to save to address from which the function was called so that the program knows where to continue after the function is done and mostly also the current state of some cpu register. These informations are therefore stored on the stack. Now there are a few differnt methods how compiler handle function calls an their data, called "Calling convetions". The four most important ones are:
• C calling Convetion __cdecl
Standard calling convetion by C compiler. The parameters are being pushed onto the stack from right to left and the caller is responsible for cleaning up the stack afterwards.
• Standard Calling Convention __stdcall
This calling convetion is als known as WIN32 calling convention since the Win32 API primarily uses this convention. The WINAPI declaration after the return type oft he function in Win32 API also is just a #define for __stdcall. In this calling covention the callee cleans up the stack.
• Fastcall Calling Convention __fastcall
In Fastcall Calling Convention the first two paramters passed to the function will not be pushed on the stack but the CPU will use register ECX and EDX to store them. The other parameters will be pushed ont the stack from right to left if there are more than two. If any parameter got pushed on the stack the callee will clean up the stack. The implementation of __fastcall may vary on several compiler.
• This calling convetion __thiscall
As the name probably implies this calling convention is exclusive used by memberfunction from any C++ class which can be accessed via this pointer. ECX register is used to store the this pointer, other parameter are pushed on the stack. The callee cleans up the stack. This calling convention has some commonalities with _fastcall but the big differnce is that is only used ECX register to store first parameter (this pointer) and not EDX for second parameter too.
Since it is not possible to see how these differnt calling convetions behave from a high level perspective I show a dissasembly of __cdecl function call.
C calling convetion:
int __cdecl cdecl_sum(int a, int b) {
return a+b;
}
The disassembly looks following:
011713E0 push ebp
011713E1 mov ebp,esp
011713E3 sub esp,0C0h
011713E9 push ebx
011713EA push esi
011713EB push edi
011713EC lea edi,[ebp-0C0h]
011713F2 mov ecx,30h
011713F7 mov eax,0CCCCCCCCh
011713FC rep stos dword ptr es:[edi]
011713FE mov eax,dword ptr [a]
01171401 add eax,dword ptr [b]
01171404 pop edi
01171405 pop esi
01171406 pop ebx
01171407 mov esp,ebp
01171409 pop ebp
0117140A ret
Before I discuss the actual function i will show what happens when the function is called:
C Syntax:
cdecl_sum(1,4);
Dissasemly:
01171491 push 4
01171493 push 1
01171495 call cdecl_sum (117114Fh)
0117149A add esp,8
First the two parameter are pushed on the stack (from right to left). The call instruction afterwards pushes the current instruction pointer EIP on the stack and give control to the function (by performing a jmp operation). This operation does not affect the EBP register.
ESP is the stack pointer and is implicitly manipulated by several CPU instruction such as PUSH, POP, CALL and so on. ESP always points the element last used on stack.
The instruction push ebp stores the current value of ebp on the stack which should contain the old basepointer from the last stackframe. Afterwards the value of ESP is copied into EBP (the stack basepointer) therefore EBP now points at the top of the stack.
The first paramter on the stack is located at [ebp+0x8] and the second at [ebp+0xC]. While EBP holds the base pointer of previous stack frame and [EBP+0x4] the old EIP so the return adress.
Currently the stack looks like following:
[ebp+0xC]
[ebp+0x8]
[ebp+0x4]
[ebp] <- ebp, esp point here
The next instruction sub esp,0C0h now subtracts 12 from ESP to make some room for local variables in the function. My function doest not have any anyway. The Stack would look like this now:
[ebp+0xC]
[ebp+0x8]
[ebp+0x4]
[ebp] <- ebp points here
[ebp-0x4]
[ebp-0x8]
[ebp-0xC] <- esp points here
Now there are three push instructions. These are used to store the current values of ebx, esi and edi before the function can do whatever it likes to them. Only the Stackpointer ESP must not be changed.
This whole sequence is also called the function epicloge.
Of course the function has to restore everything as it was before calling the function before returning to the caller so there is also a function prolog.
First there are three POP instructions. This is for releasing the local varibles. It it the counterpart of the sub esp 0xC in the epilouge. While each pop highers the value of esp by 4. It is crucial that the registeres are restored in reverse order as they where pushed on the stack otherwise stack corruption is guaranteed.
mov esp,ebp stores the basepointer oft he current fram into ESP.
Afterwards EBP is popped from the stack and the RET operation is exectured. RET pops the old EIP from the stack and jumps to the location, therefore the caller now has control back.
Since the caller is responsible for cleaning up the stack.
There is one last instruction after the function.
add esp, 8
This is for cleaning up the parameter pushed on the stack by adding the size of the parameter which is 8 in this case.
In __stdcall it would look like following:
The important thing happens in the function prologue:
011713C4 pop edi
011713C5 pop esi
011713C6 pop ebx
011713C7 mov esp,ebp
011713C9 pop ebp
011713CA ret 8
In this case the Ret instruction in the function already adds 8 to the stack pointer.
And there is no add after the function call like in __cdecl as is illustrated in following dissasembly:
01171485 push 4
01171487 push 1
01171489 call std_sum (11710D7h)