Intel x86 Assembly Language & Microarchitecture
Samtalskonventioner
Sök…
Anmärkningar
Resurser
Översikter / jämförelser: Agner Fogs trevliga samtalshandbok . Dessutom x86 ABI: er (wikipedia) : samtalskonventioner för funktioner, inklusive x86-64 Windows och System V (Linux).
SystemV x86-64 ABI (officiell standard) . Används av alla operativsystem utom Windows. ( Denna github wikisida , uppdaterad av HJ Lu, har länkar till 32bit, 64bit och x32. Länkar också till det officiella forumet för ABI-underhållare / bidragsgivare.) Observera också att clang / gcc-tecken / noll förlänger smala args till 32bit , även om ABI som skrivet inte kräver det. Clang-genererad kod beror på den.
SystemV 32bit (i386) ABI (officiell standard) , används av Linux och Unix. ( gammal version ).
OS X 32bit x86 samtalskonvention, med länkar till de andra . 64bit-samtalskonventionen är System V. Apples webbplats länkar bara till en FreeBSD-pdf för det.
Windows
__vectorcall: dokumenterar versionerna 32bit och 64bitWindows 32bit
__stdcall: används för att ringa Win32 API-funktioner. Den sidan länkar till andra__cdeclsamtal (t.ex.__cdecl).Varför använder Windows64 ett annat samtalskonvention än alla andra operativsystem på x86-64? : lite intressant historia, särskilt för SysV ABI där adresslistans arkiv är offentliga och går tillbaka innan AMD släppte första kisel.
32-bitars cdecl
cdecl är en Windows 32-bitars funktionskonvention som är mycket lik den samtalskonvention som används på många POSIX-operativsystem (dokumenterad i i386 System V ABI ). En av skillnaderna är att returnera små strukturer.
parametrar
Parametrar överförs på bunten, med det första argumentet på den lägsta adressen på bunten vid samtalstidpunkten (tryckt senast, så det är precis ovanför returadressen vid posten till funktionen). Den som ringer är ansvarig för att poppa parametrar tillbaka från bunten efter samtalet.
Returvärde
För skalära returtyper placeras returvärdet i EAX eller EDX: EAX för 64 bitars heltal. Flytande punkttyper returneras i st0 (x87). Återvända större typer som strukturer görs genom referens, med en pekare som en implicit första parameter. (Denna pekare returneras i EAX, så den som ringer behöver inte komma ihåg vad den passerade).
Sparade och klädda register
EBX, EDI, ESI, EBP och ESP (och FP / SSE avrundningsläge) måste bevaras av callee, så att den som ringer kan lita på att de register som inte har ändrats av ett samtal.
Alla andra register (EAX, ECX, EDX, FLAGS (andra än DF), x87 och vektorregister) kan fritt modifieras av callee; Om en samtalare vill bevara ett värde före och efter funktionssamtalet måste det spara värdet någon annanstans (t.ex. i ett av de sparade register eller på stacken).
64-bitars system V
Detta är standardsamtalskonventionen för 64-bitars applikationer på många POSIX-operativsystem.
parametrar
De första åtta skalparametrarna skickas i (i ordning) RDI, RSI, RDX, RCX, R8, R9, R10, R11. Parametrar förbi de första åtta placeras på bunten, med tidigare parametrar närmare toppen av bunten. Den som ringer är ansvarig för att dessa värden släpps från bunten efter samtalet om det inte längre behövs.
Returvärde
För skala returtyper placeras returvärdet i RAX. Återvända större typer som strukturer görs genom att konceptuellt ändra signatur för funktionen för att lägga till en parameter i början av parameterlistan som är en pekare till en plats där returvärdet ska placeras.
Sparade och klädda register
RBP, RBX och R12 – R15 bevaras av callee. Alla andra register kan modifieras av callee, och den som ringer måste bevara ett registervärde själv (t.ex. på stacken) om den vill använda det värdet senare.
32-bitars stdcall
stdcall används för 32-bitars Windows API-samtal.
parametrar
Parametrar överförs på bunten, med den första parametern närmast toppen av bunten. Callee kommer att dyka upp dessa värden från stacken innan de återvänder.
Returvärde
Skala returvärden placeras i EAX.
Sparade och klädda register
EAX, ECX och EDX kan fritt modifieras av callee och måste sparas av den som ringer om så önskas. EBX, ESI, EDI och EBP måste sparas av callee om de modifieras och återställs till sina ursprungliga värden vid retur.
32-bitars cdecl - Hantering av heltal
Som parametrar (8, 16, 32 bitar)
8, 16, 32 bitars heltal passeras alltid, på bunten, som 32 bitars hela breddvärden 1 .
Ingen förlängning, signerad eller noll, behövs.
Callee använder bara den nedre delen av värdena för hela bredden.
//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
Som parametrar (64 bitar)
64 bitar värden överförs på bunten med hjälp av två tryck, respekterande littel endian konvention 2 , tryck först de högre 32 bitarna sedan de lägre.
//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
Som returvärde
Åtta bitar heltal återlämnas i AL , så småningom clobbering hela eax .
16 bitar heltal återlämnas i AX , så småningom clobbering hela eax .
32 bitars heltal returneras i EAX .
64 bitars heltal returneras i EDX:EAX , där EAX håller de lägre 32 bitarna och EDX de övre.
//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 Detta håller stapeln i linje med 4 byte, den naturliga ordstorleken. En x86-CPU kan också bara trycka på 2 eller 4 byte när den inte är i långt läge.
2 Sänk DWORD på lägre adress
32-bitars cdecl - Hantering av flytande punkt
Som parametrar (float, double)
Floats är 32 bitar i storlek, de förs naturligt på bunten.
Dubblarna är 64 bitar i storlek, de passeras på bunten, respekterar Little Endian-konvention 1 , först skjuter de övre 32 bitarna och än de nedre.
//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
Som parametrar (lång dubbel)
Långa dubblar är 80 bitar 2 breda, medan på stacken en TBYTE skulle kunna lagras med två 32 bitar tryck och en 16 bitars push (för 4 + 4 + 2 = 10), för att hålla stacken i linje med 4 byte, slutar den uppta 12 byte, så att man använder tre 32 bitar tryck.
Med respekt för Little Endian-konventionen skjuts bitarna 79-64 först 3 , sedan bitarna 63-32 följt av bitarna 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
Som returvärde
Ett flytande punktvärde, oavsett storlek, returneras i 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 Sänk DWORD på lägre adress.
2 Känd som TBYTE, från Ten Bytes.
3 Med hjälp av en full breddspush med någon förlängning används inte högre WORD.
4 Vilket är TBYE-brett, observera att i motsats till heltalen returneras FP alltid med mer precision som det krävs.
64-bitars Windows
parametrar
De första fyra parametrarna skickas i (i ordning) RCX, RDX, R8 och R9. XMM0 till XMM3 används för att skicka parametrar för flytande punkt.
Eventuella ytterligare parametrar överförs på stacken.
Parametrar större än 64 bit skickas via adress.
Spill utrymme
Även om funktionen använder mindre än 4 parametrar ger den som ringer alltid utrymme för 4 QWORD-storlekar på bunten. Det är gratis att använda dem för alla ändamål, det är vanligt att kopiera parametrarna där om de skulle spillas av ett annat samtal.
Returvärde
För skala returtyper placeras returvärdet i RAX. Om returtypen är större än 64 bitar (t.ex. för strukturer) är RAX en pekare till det.
Sparade och klädda register
Alla register som används vid parameterpassering (RCX, RDX, R8, R9 och XMM0 till XMM3), RAX, R10, R11, XMM4 och XMM5 kan spillas av callee. Alla andra register måste bevaras av den som ringer (t.ex. på stacken).
Stapla inriktningen
Bunten måste hållas i linje med 16 byte. Eftersom "samtal" -instruktionen trycker på en 8-byte-returadress, betyder detta att varje icke-bladfunktion kommer att justera stapeln med ett värde på formen 16n + 8 för att återställa 16-byte-justering.
Det är uppringarnas jobb att rengöra stacken efter ett samtal.
Källa: Historik om samtalskonventioner, del 5: amd64 Raymond Chen
32-bitars cdecl - Hantering av strukturer
Stoppning
Kom ihåg att medlemmar i en struktur vanligtvis är vadderade för att säkerställa att de är i linje med sin naturliga gräns:
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)
};
Som parametrar (pass by reference)
När det skickas som referens skickas en pekare till strukturen i minnet som det första argumentet på stacken. Detta motsvarar passering av ett heltäckande (32-bitars) heltalsvärde; se 32-bitars cdecl för detaljer.
Som parametrar (pass by value)
När de överförs av värdet kopieras strukturer helt på bunten med respekt för den ursprungliga minneslayouten ( dvs. den första medlemmen kommer att vara på den lägre adressen).
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
Som returvärde
Såvida de inte är triviala 1 , kopieras strukturer till en anropslevererad buffert innan de återvänder. Detta motsvarar att ha en dold första parameter struct S *retval (där struct S är typen av strukt).
Funktionen måste återgå med denna pekare till returvärdet i eax ; Den som ringer får bero på att eax håller pekaren till returvärdet, som den tryckte rätt före call .
struct S
{
unsigned char a, b, c;
};
struct S foo(); // compiled as struct S* foo(struct S* _out)
Den dolda parametern läggs inte till i parameterräkningen för staplingsrening, eftersom den måste hanteras av callee.
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
I exemplet ovan sparas strukturen högst upp i bunten.
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 En "trivial" struktur är en som endast innehåller en medlem av en icke-struktur, icke-array-typ (upp till 32 bitar i storlek). För sådana strukturer returneras värdet på denna medlem helt enkelt i eax registret. (Detta beteende har observerats med GCC-inriktning på Linux)
Windows-versionen av cdecl skiljer sig från System V ABI: s samtalskonvention: En "trivial" struktur tillåter att innehålla upp till två medlemmar av en icke-struktur, icke-array-typ (upp till 32 bitar i storlek). Dessa värden returneras i eax och edx , precis som ett 64-bitars heltal skulle vara. (Detta beteende har observerats för MSVC och Clang som riktar sig till Win32.)