Intel x86 Assembly Language & Microarchitecture
Conventies aanroepen
Zoeken…
Opmerkingen
Middelen
Overzichten / vergelijkingen: Agner Fog's leuke roeping conventie gids . Ook x86 ABI's (wikipedia) : aanroepconventies voor functies, inclusief x86-64 Windows en System V (Linux).
SystemV x86-64 ABI (officiële standaard) . Gebruikt door alle besturingssystemen behalve Windows. ( Deze github-wikipagina , up-to-date gehouden door HJ Lu, heeft links naar 32bit, 64bit en x32. Ook links naar het officiële forum voor ABI-beheerders / medewerkers.) Merk ook op dat clang / gcc-teken / zero nauwe args uitbreiden tot 32bit , hoewel de ABI zoals geschreven dit niet vereist. Door Clang gegenereerde code is hiervan afhankelijk.
SystemV 32bit (i386) ABI (officiële standaard) , gebruikt door Linux en Unix. ( oude versie ).
OS X 32bit x86 oproepconventie, met links naar de anderen . De 64-bit belconventie is Systeem V. De website van Apple linkt daarvoor alleen naar een FreeBSD pdf.
Windows
__vectorcall: documenteert de__vectorcallen 64bit-versiesWindows 32bit
__stdcall: wordt gebruikt om Win32 API-functies aan te roepen. Die pagina linkt naar de andere documenten van de conventie (bijvoorbeeld__cdecl).Waarom gebruikt Windows64 een andere oproepconventie dan alle andere besturingssystemen op x86-64? : een interessante geschiedenis, in het bijzonder. voor de SysV ABI waar de archieven van de mailinglijst openbaar zijn en teruggaan vóór AMD's eerste silicium vrijgeeft.
32-bit cdecl
cdecl is een Windows 32-bit functie-aanroepconventie die erg lijkt op de aanroepconventie die wordt gebruikt op veel POSIX-besturingssystemen (gedocumenteerd in de i386 System V ABI ). Een van de verschillen zit in het teruggeven van kleine structuren.
parameters
Parameters worden doorgegeven aan de stapel, met het eerste argument op het laagste adres op de stapel op het moment van de aanroep (laatst ingedrukt, dus het is net boven het retouradres bij invoer voor de functie). De beller is verantwoordelijk voor het terughalen van parameters uit de stapel na het gesprek.
Winstwaarde
Voor scalaire retourtypen wordt de retourwaarde in EAX of EDX: EAX voor 64-bits gehele getallen geplaatst. Zwevende punttypen worden geretourneerd in st0 (x87). Het retourneren van grotere typen zoals structuren wordt gedaan door middel van verwijzing, met een aanwijzer als impliciete eerste parameter. (Deze aanwijzer wordt geretourneerd in EAX, zodat de beller niet hoeft te onthouden wat hij is gepasseerd).
Opgeslagen en verkapte registers
EBX, EDI, ESI, EBP en ESP (en FP / SSE afrondingsmodusinstellingen) moeten door de gebruiker worden bewaard, zodat de beller erop kan vertrouwen dat die registers niet door een oproep zijn gewijzigd.
Alle andere registers (EAX, ECX, EDX, FLAGS (anders dan DF), x87 en vectorregisters) kunnen door de gebruiker vrij worden gewijzigd; als een beller een waarde voor en na de functieaanroep wil behouden, moet hij de waarde elders opslaan (zoals in een van de opgeslagen registers of op de stapel).
64-bit systeem V
Dit is de standaard aanroepconventie voor 64-bits applicaties op veel POSIX-besturingssystemen.
parameters
De eerste acht scalaire parameters worden doorgegeven in (in volgorde) RDI, RSI, RDX, RCX, R8, R9, R10, R11. Parameters voorbij de eerste acht worden op de stapel geplaatst, met eerdere parameters dichter bij de bovenkant van de stapel. De beller is verantwoordelijk voor het weghalen van deze waarden van de stapel na het gesprek als deze niet langer nodig is.
Winstwaarde
Voor scalaire retourtypen wordt de retourwaarde in RAX geplaatst. Het retourneren van grotere typen zoals structuren wordt gedaan door conceptueel de handtekening van de functie te wijzigen om een parameter aan het begin van de parameterlijst toe te voegen die een pointer is naar een locatie waar de retourwaarde moet worden geplaatst.
Opgeslagen en verkapte registers
RBP, RBX en R12 – R15 worden bewaard door de callee. Alle andere registers kunnen door de gebruiker worden gewijzigd en de beller moet zelf de waarde van een register behouden (bijvoorbeeld op de stapel) als hij die waarde later wil gebruiken.
32-bits standaardoproep
stdcall wordt gebruikt voor 32-bit Windows API-aanroepen.
parameters
Parameters worden doorgegeven aan de stapel, met de eerste parameter het dichtst bij de bovenkant van de stapel. De callee zal deze waarden van de stapel verwijderen voordat hij terugkeert.
Winstwaarde
Scalaire retourwaarden worden in EAX geplaatst.
Opgeslagen en verkapte registers
EAX, ECX en EDX kunnen door de gebruiker vrij worden gewijzigd en moeten desgewenst door de beller worden opgeslagen. EBX, ESI, EDI en EBP moeten door de gebruiker worden opgeslagen als deze bij terugkeer worden gewijzigd en hersteld naar hun oorspronkelijke waarden.
32-bit, cdecl - Omgaan met gehele getallen
Als parameters (8, 16, 32 bits)
8, 16, 32-bits gehele getallen worden altijd op de stapel doorgegeven als 32 bits volledige waarde 1 .
Er is geen extensie, ondertekend of op nul gesteld, nodig.
De callee gebruikt alleen het onderste gedeelte van de volledige breedtewaarden.
//C prototype of the callee
void __attribute__((cdecl)) foo(char a, short b, int c, long d);
foo(-1, 2, -3, 4);
;Call to foo in assembly
push DWORD 4 ;d, long is 32 bits, nothing special here
push DWORD 0fffffffdh ;c, int is 32 bits, nothing special here
push DWORD 0badb0002h ;b, short is 16 bits, higher WORD can be any value
push DWORD 0badbadffh ;a, char is 8 bits, higher three bytes can be any value
call foo
add esp, 10h ;Clean up the stack
Als parameters (64 bits)
64 bits waarden worden doorgegeven aan de stapel met behulp van twee pushes, met inachtneming van de littel endian conventie 2 , waarbij eerst de hogere 32 bits dan de lagere worden geduwd.
//C prototype of the callee
void __attribute__((cdecl)) foo(char a, short b, int c, long d);
foo(0x0123456789abcdefLL);
;Call to foo in assembly
push DWORD 89abcdefh ;Higher DWORD of 0123456789abcdef
push DWORD 01234567h ;Lower DWORD of 0123456789abcdef
call foo
add esp, 08h
Als retourwaarde
8 bits gehele getallen worden geretourneerd in AL , waarbij uiteindelijk de hele eax ingesloten.
16 bits gehele getallen worden in AX geretourneerd, waardoor uiteindelijk de hele eax omsloten.
32 bits gehele getallen worden geretourneerd in EAX .
64 bits gehele getallen worden geretourneerd in EDX:EAX , waarbij EAX de onderste 32 bits en EDX de bovenste bevat.
//C
char foo() { return -1; }
;Assembly
mov al, 0ffh
ret
//C
unsigned short foo() { return 2; }
;Assembly
mov ax, 2
ret
//C
int foo() { return -3; }
;Assembly
mov eax, 0fffffffdh
ret
//C
int foo() { return 4; }
;Assembly
xor edx, edx ;EDX = 0
mov eax, 4 ;EAX = 4
ret
1 Dit houdt de stapel uitgelijnd op 4 bytes, de natuurlijke woordgrootte. Ook kan een x86 CPU slechts 2 of 4 bytes pushen als deze niet in de lange modus staat.
2 Laat DWORD op lager adres zakken
32-bit, cdecl - Omgaan met drijvend punt
Als parameters (float, dubbel)
Drijvers zijn 32 bits groot en worden op natuurlijke wijze op de stapel doorgegeven.
Dubbels zijn 64 bits groot, ze worden op de stapel doorgegeven, met inachtneming van de Little Endian conventie 1 , waarbij eerst de bovenste 32 bits en dan de onderste worden ingedrukt.
//C prototype of callee
double foo(double a, float b);
foo(3.1457, 0.241);
;Assembly call
;3.1457 is 0x40092A64C2F837B5ULL
;0.241 is 0x3e76c8b4
push DWORD 3e76c8b4h ;b, is 32 bits, nothing special here
push DWORD 0c2f837b5h ;a, is 64 bits, Higher part of 3.1457
push DWORD 40092a64h ;a, is 64 bits, Lower part of 3.1457
call foo
add esp, 0ch
;Call, using the FPU
;ST(0) = a, ST(1) = b
sub esp, 0ch
fstp QWORD PTR [esp] ;Storing a as a QWORD on the stack
fstp DWORD PTR [esp+08h] ;Storing b as a DWORD on the stack
call foo
add esp, 0ch
Als parameters (lang dubbel)
Lange doubles zijn 80 bits 2 breed, terwijl op de stapel een TBYTE kan worden opgeslagen met twee 32-bits pushes en één 16-bit push (voor 4 + 4 + 2 = 10), om de stapel op 4 bytes uit te lijnen, eindigt deze bezet 12 bytes, dus met gebruik van drie 32-bits pushes.
Met inachtneming van de Little Endian-conventie worden bits 79-64 eerst 3 gepusht, vervolgens bits 63-32 gevolgd door bits 31-0.
//C prototype of the callee
void __attribute__((cdecl)) foo(long double a);
foo(3.1457);
;Call to foo in assembly
;3.1457 is 0x4000c9532617c1bda800
push DWORD 4000h ;Bits 79-64, as 32 bits push
push DWORD 0c9532617h ;Bits 63-32
push DWORD 0c1bda800h ;Bits 31-0
call foo
add esp, 0ch
;Call to foo, using the FPU
;ST(0) = a
sub esp, 0ch
fstp TBYTE PTR [esp] ;Store a as ten byte on the stack
call foo
add esp, 0ch
Als retourwaarde
Een drijvende-kommawaarde, ongeacht de grootte, wordt geretourneerd in ST(0) 4 .
//C
float one() { return 1; }
;Assembly
fld1 ;ST(0) = 1
ret
//C
double zero() { return 0; }
;Assembly
fldz ;ST(0) = 0
ret
//C
long double pi() { return PI; }
;Assembly
fldpi ;ST(0) = PI
ret
1 Lagere DWORD op lager adres.
2 Bekend als TBYTE, van Ten Bytes.
3 Bij gebruik van een push op volle breedte met een extensie wordt een hogere WORD niet gebruikt.
4 Die TBYE-breed is, houd er rekening mee dat in tegenstelling tot de gehele getallen FP altijd met meer precisie wordt geretourneerd dan vereist.
64-bit Windows
parameters
De eerste 4 parameters worden doorgegeven (in volgorde) RCX, RDX, R8 en R9. XMM0 tot XMM3 worden gebruikt om parameters voor drijvende komma door te geven.
Alle verdere parameters worden doorgegeven aan de stapel.
Parameters groter dan 64bit worden op adres doorgegeven.
Morsen ruimte
Zelfs als de functie minder dan 4 parameters gebruikt, biedt de beller altijd ruimte voor 4 QWORD-parameters op de stapel. De gebruiker is vrij om ze voor elk doel te gebruiken, het is gebruikelijk om de parameters daar te kopiëren als ze door een ander gesprek zouden worden gemorst.
Winstwaarde
Voor scalaire retourtypen wordt de retourwaarde in RAX geplaatst. Als het retourtype groter is dan 64 bits (bijvoorbeeld voor structuren), is RAX daar een verwijzing naar.
Opgeslagen en verkapte registers
Alle registers die worden gebruikt bij het doorgeven van parameters (RCX, RDX, R8, R9 en XMM0 tot XMM3), RAX, R10, R11, XMM4 en XMM5 kunnen worden gemorst door de gebruiker. Alle andere registers moeten door de beller worden bewaard (bijv. Op de stapel).
Stapel uitlijning
De stapel moet op 16 bytes worden uitgelijnd. Omdat de "aanroep" -instructie een 8-byte-retouradres duwt, betekent dit dat elke niet-bladfunctie de stapel gaat aanpassen met een waarde van de vorm 16n + 8 om de uitlijning van 16 bytes te herstellen.
Het is de taak van de beller om de stapel na een oproep schoon te maken.
Bron: De geschiedenis van roepconventies, deel 5: amd64 Raymond Chen
32-bit, cdecl - Omgaan met Structs
vulling
Vergeet niet dat leden van een struct meestal worden opgevuld om ervoor te zorgen dat ze zijn uitgelijnd op hun natuurlijke grens:
struct t
{
int a, b, c, d; // a is at offset 0, b at 4, c at 8, d at 0ch
char e; // e is at 10h
short f; // f is at 12h (naturally aligned)
long g; // g is at 14h
char h; // h is at 18h
long i; // i is at 1ch (naturally aligned)
};
Als parameters (doorgeven door referentie)
Bij verwijzing wordt een wijzer naar de struct in het geheugen doorgegeven als het eerste argument op de stapel. Dit komt overeen met het doorgeven van een geheel getal van 32 bits (natuurlijk); zie 32-bit cdecl voor bijzonderheden.
Als parameters (waarde passeren)
Wanneer ze door waarde worden doorgegeven, worden structs volledig naar de stapel gekopieerd, met inachtneming van de oorspronkelijke geheugenlay-out ( dwz het eerste lid bevindt zich op het lagere adres).
int __attribute__((cdecl)) foo(struct t a);
struct t s = {0, -1, 2, -3, -4, 5, -6, 7, -8};
foo(s);
; Assembly call
push DWORD 0fffffff8h ; i (-8)
push DWORD 0badbad07h ; h (7), pushed as DWORD to naturally align i, upper bytes can be garbage
push DWORD 0fffffffah ; g (-6)
push WORD 5 ; f (5)
push WORD 033fch ; e (-4), pushed as WORD to naturally align f, upper byte can be garbage
push DWORD 0fffffffdh ; d (-3)
push DWORD 2 ; c (2)
push DWORD 0ffffffffh ; b (-1)
push DWORD 0 ; a (0)
call foo
add esp, 20h
Als retourwaarde
Tenzij ze triviaal 1 zijn , worden structs gekopieerd naar een door de beller geleverde buffer voordat ze terugkeren. Dit komt overeen met het hebben van een verborgen eerste parameter struct S *retval (waarbij struct S het type van de struct is).
De functie moet met deze aanwijzer terugkeren naar de retourwaarde in eax ; De beller mag afhangen van eax die de aanwijzer op de retourwaarde houdt, die hij vlak voor de call heeft ingedrukt.
struct S
{
unsigned char a, b, c;
};
struct S foo(); // compiled as struct S* foo(struct S* _out)
De verborgen parameter wordt niet toegevoegd aan de parametertelling voor het opschonen van de stapel, omdat deze door de gebruiker moet worden afgehandeld.
sub esp, 04h ; allocate space for the struct
; call to foo
push esp ; pointer to the output buffer
call foo
add esp, 00h ; still as no parameters have been passed
In het bovenstaande voorbeeld wordt de structuur boven aan de stapel opgeslagen.
struct S foo()
{
struct S s;
s.a = 1; s.b = -2; s.c = 3;
return s;
}
; Assembly code
push ebx
mov eax, DWORD PTR [esp+08h] ; access hidden parameter, it is a pointer to a buffer
mov ebx, 03fe01h ; struct value, can be held in a register
mov DWORD [eax], ebx ; copy the structure into the output buffer
pop ebx
ret 04h ; remove the hidden parameter from the stack
; EAX = pointer to the output buffer
1 Een "triviale" struct is er een die slechts één lid van een niet-struct, niet-array-type bevat (tot 32 bits groot). Voor dergelijke structuren wordt de waarde van dat lid eenvoudig teruggegeven in het eax register. (Dit gedrag is waargenomen bij GCC gericht op Linux)
De Windows-versie van cdecl verschilt van de aanroepconventie van System V ABI: een "triviale" struct mag maximaal twee leden van een niet-struct, niet-array-type bevatten (maximaal 32 bits). Deze waarden worden geretourneerd in eax en edx , net zoals een 64-bits geheel getal zou zijn. (Dit gedrag is waargenomen voor MSVC en Clang die Win32 targeten.)