"Christmas - the time to fix the computers of your loved ones" « Lord Wyrm

calling conventions

Paxi 26.10.2011 - 22:36 1352 4
Posts

Paxi

Overclocking Team Member
Avatar
Registered: Oct 2009
Location: Wien
Posts: 389
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 :D
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:
Code: C
int __cdecl  cdecl_sum(int a, int b) {
		return a+b;
}

The disassembly looks following:

Code:
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:
Code: C
cdecl_sum(1,4);
Dissasemly:
Code:
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:

Code:
[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:

Code:
[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:
Code:
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:

Code:
01171485  push        4  
01171487  push        1  
01171489  call        std_sum (11710D7h)
Bearbeitet von Paxi am 27.10.2011, 01:43

Hansmaulwurf

u wot m8?
Avatar
Registered: Apr 2005
Location: VBG
Posts: 5639
Falls das so dein Bacc-Text ist, würd ich schauen bezüglich groß und klein Schreibung zB den stack pointer schreibst manchmal klein, groß oder ganze capital. Ausserdem kenn (zumindest persönlich) ich den Stack nur als Stapel, da gibts kein seitlich reinschieben sondern drauf.
(Und in dem Fall wird dir ein Stack Diagramm mit Ablauf einiges helfen, natürlich nur wenn du motiviert genug bist für die Arbeit weil die Diagramme sind halt zeitintensiv)

Aufn Inhalt kann ich nicht eingehen ich habs jetzt nur überflogen, aber thx für die Infos :)
Bearbeitet von Hansmaulwurf am 26.10.2011, 23:03

Paxi

Overclocking Team Member
Avatar
Registered: Oct 2009
Location: Wien
Posts: 389
Das ist eh nicht so mein Bacc-Text keine Sorge^^
Mit rechts nach links ist nur gemeint das der zweite Paramter (rechts) zuerst auf den Stack gepushed wird und der erste (link) danach, aber stimmt das ist etwas verwirrend.
Und danke für den Tipp trotzdem, das werde ich dann in den richtigen Arbeit natürlich einheitlich halten.

that

Moderator
Hoffnungsloser Optimist
Avatar
Registered: Mar 2000
Location: MeidLing
Posts: 11338
- WINAPI ist natürlich kein typedef.
- von fastcall gibts eine ganze Reihe von Varianten auf verschiedenen Plattformen und Compilern

Vielleicht auch noch interessant ist, dass auf x86_64 alles ziemlich anders ist. :)

Empfehlenswerte Links:
http://en.wikipedia.org/wiki/Calling_conventions
http://en.wikipedia.org/wiki/X86_calling_conventions
Bearbeitet von that am 27.10.2011, 00:06

Paxi

Overclocking Team Member
Avatar
Registered: Oct 2009
Location: Wien
Posts: 389
Da geb ich dir natürlich Recht WINAPI ist kein typedef sondern ein #define.
Und ich gehe nur auf die x86 Architektur ein, dass habe ich vergessen zu erwähnen.
Kontakt | Unser Forum | Über overclockers.at | Impressum | Datenschutz