Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här artikeln beskriver de standardprocesser och konventioner som en funktion (anroparen) använder för att göra anrop till en annan funktion (anroparen) i x64-kod.
Mer information om anropskonventionen __vectorcall finns i __vectorcall.
Mer information om anropskonventionen __preserve_none finns i __preserve_none.
Standardvärden för anropskonvention
X64 Application Binary Interface (ABI) använder en anropskonvention med fyra register och snabba samtal som standard. Utrymme allokeras i anropsstacken som ett skugglager för anropare för att spara dessa register.
Det finns en strikt en-till-en-korrespondens mellan ett funktionsanrops argument och de register som används för dessa argument. Alla argument som inte får plats i 8 byte, eller som inte är 1, 2, 4 eller 8 byte, måste skickas med referens. Ett enda argument sprids aldrig över flera register.
X87-registerstacken används inte. Den kan användas av den som anropas, men tänk på att den är volatil mellan funktionsanrop. Alla flyttalsåtgärder utförs med hjälp av 16 XMM-register.
Heltalsargument överförs i registren RCX, RDX, R8 och R9. Flyttalsargument skickas i XMM0L, XMM1L, XMM2Loch XMM3L. 16-byte-argumenten passeras som referens. Parameteröverföring beskrivs i detalj i Parameteröverföring. Dessa register, och RAX, R10, R11XMM4, och XMM5, anses vara flyktiga eller kan ändras av en anropare vid retur. Registeranvändningen dokumenteras i detalj i x64-registeranvändning och sparade register för uppringare/samtalsmottagare.
För prototypfunktioner konverteras alla argument till de förväntade typerna av anropare innan de skickas. Anroparen ansvarar för att allokera utrymme för anropades parametrar. Anroparen måste alltid allokera tillräckligt med utrymme för att lagra fyra registerparametrar, även om anroparen inte tar så många parametrar. Den här konventionen förenklar stödet för oprototypade C-språkfunktioner och vararg C/C++-funktioner. För vararg- eller oprototypade funktioner måste alla flyttalsvärden dupliceras i motsvarande register för generell användning. Alla parametrar utöver de fyra första måste lagras i stacken efter skugglagringen innan anropet. Vararg-funktionsinformation finns i Varargs. Oprototypad funktionsinformation beskrivs i oprototypade funktioner.
Justering
De flesta strukturer är anpassade till sin naturliga inriktning. De primära undantagen är stackpekaren samt malloc eller alloca-minne, som är 16-byte justerade för att optimera prestanda. Justering över 16 byte måste göras manuellt. Eftersom 16 byte är en gemensam justeringsstorlek för XMM-åtgärder bör det här värdet fungera för de flesta kod. Mer information om strukturlayout och justering finns i x64-typ och lagringslayout. Information om stacklayouten finns i x64-stackanvändning.
Avvindbarhet
Lövfunktioner är funktioner som inte ändrar några icke-volatila register. En nonleaf-funktion kan ändra icke-volatil RSP, till exempel genom att anropa en funktion. Eller så kan det ändras RSP genom att allokera mer stackutrymme för lokala variabler. För att återställa icke-volatila register när ett undantag hanteras, annoteras icke-bladfunktioner med statiska data. Data beskriver hur man korrekt avvecklar funktionen vid en godtycklig instruktion. Dessa data lagras som pdata, eller procedurdata, som i sin tur refererar till xdata, undantagshanteringsdata. xdata innehåller information om avlindning och kan peka på ytterligare pdata eller en undantagshanterarfunktion.
Prologer och epiloger är mycket begränsade så att de kan beskrivas korrekt i xdata. Stackpekaren måste vara 16 byte justerad i alla kodområden som inte ingår i en epilog eller prolog, förutom i lövfunktioner. Lövfunktioner kan enkelt återställas genom att simulera en retur, så pdata och xdata krävs inte. Mer information om rätt struktur för funktionsprologer och epiloger finns i x64 prolog och epilog. Mer information om undantagshantering samt avveckling av pdata och xdata finns i x64-undantagshantering.
Parameteröverföring
Som standard skickar x64-anropskonventionen de första fyra argumenten till en funktion i register. De register som används för dessa argument beror på argumentets position och typ. Återstående argument skickas på stacken i höger-till-vänster-ordning. Anroparen reserverar det nödvändiga stackutrymmet och skriver dessa argument till stackminnet med hjälp av lagrings- eller flyttinstruktioner, vilket upprätthåller 8-bytejustering för varje argument.
Heltalsvärdesargument i de fyra vänstra positionerna skickas i vänster-till-höger-ordning i RCX, RDX, R8respektive R9. De femte och högre argumenten skickas på stacken enligt beskrivningen ovan. Alla heltalsargument i register är rätt motiverade, så anropare kan ignorera de övre bitarna i registret och endast komma åt den del av registret som krävs.
Alla flyttals- och dubbelprecisionsargument i de första fyra parametrarna skickas i XMM0 - XMM3, beroende på position. Flyttalsvärden placeras bara i heltalsregistren RCX, RDX, R8och R9 när det finns varargs-argument. Mer information finns i Varargs. På samma sätt ignoreras registren XMM0 - XMM3 när motsvarande argument är ett heltal eller en pekartyp.
__m128 typer, matriser och strängar skickas aldrig med omedelbart värde. I stället skickas en pekare till det minne som allokeras av anroparen. Strukturer och unioner av storlek 8, 16, 32 eller 64 bitar, och __m64-typer, behandlas som om de vore heltal av samma storlek. Structs eller unioner av andra storlekar skickas som en pekare till minne som allokeras av anroparen. För dessa aggregeringstyper som skickas som en pekare, inklusive __m128, måste det anroparallokerade tillfälliga minnet vara 16 byte justerat.
Inbyggda funktioner som inte allokerar stackutrymme och inte anropar andra funktioner, använder ibland andra flyktiga register för att skicka ytterligare registerargument. Den här optimeringen möjliggörs genom den snäva bindningen mellan kompilatorn och implementeringen av den inbyggda funktionen.
Anropare ansvarar för att dumpa registerparametrarna i deras skuggutrymme om det behövs.
I följande tabell sammanfattas hur parametrar skickas, efter typ och position från vänster:
| Parametertyp | femte eller högre | fjärde | tredje | andra | Längst till vänster |
|---|---|---|---|---|---|
| flyttal | stapel | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| integer | stapel | R9 |
R8 |
RDX |
RCX |
Aggregeringar (8, 16, 32 eller 64 bitar) och __m64 |
stapel | R9 |
R8 |
RDX |
RCX |
| Andra aggregeringar, som pekare | stapel | R9 |
R8 |
RDX |
RCX |
__m128som pekare |
stapel | R9 |
R8 |
RDX |
RCX |
Exempel på argumentöverföring 1 – alla heltal
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack
Exempel på argumentspassering 2 – alla flyttal
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack
Exempel på argumentöverföring 3 – blandade heltal och flyttal
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack
Exempel på överföring av argument 4 – __m64, __m128och aggregat
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack
Varargs
Om parametrar skickas via varargs (till exempel ellipsargument) gäller den normala registreringsparameterns passeringskonvention. Den konventionen omfattar att lägga de femte och senare argumenten på stacken. Det är samtalsmottagarens ansvar att skriva ut argument vars adress har tagits. För flyttalsvärden måste både heltalsregistret och flyttalsregistret innehålla värdet, ifall den anropade förväntar sig värdet i heltalsregistren.
Oprototypade funktioner
För funktioner som inte är helt prototyperade skickar anroparen heltalsvärden som heltal och flyttalsvärden som dubbel precision. För enbart flyttalsvärden innehåller både heltalsregistret och flyttalsregistret flyttalsvärdet om den anropade förväntar sig värdet i heltalsregistren.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Returnera värden
Ett skalärt returvärde som får plats i 64 bitar, inklusive __m64 typen, returneras via RAX. Icke-skalära typer, inklusive flyttal, dubbelgångare och vektortyper som __m128, __m128i, __m128d returneras i XMM0. Tillståndet för oanvända bitar i värdet som returneras i RAX eller XMM0 är odefinierat.
Användardefinierade typer kan returneras av värde från globala funktioner och statiska medlemsfunktioner. Om du vill returnera en användardefinierad typ efter värde i RAXmåste den ha en längd på 1, 2, 4, 8, 16, 32 eller 64 bitar. Den får inte heller ha någon användardefinierad konstruktor, destructor eller kopieringstilldelningsoperator. Den kan inte ha några privata eller skyddade icke-statiska datamedlemmar och inga icke-statiska datamedlemmar av referenstyp. Den kan inte ha basklasser eller virtuella funktioner. Och den kan bara ha datamedlemmar som också uppfyller dessa krav. Den här definitionen är i stort sett samma som en C++03 POD-typ. Eftersom definitionen har ändrats i C++11-standarden rekommenderar vi inte att du använder std::is_pod för det här testet. Annars måste anroparen allokera minne för returvärdet och skicka en pekare till det som det första argumentet. De återstående argumenten flyttas sedan ett argument till höger. Samma pekare måste returneras av den anropade funktionen i RAX.
De här exemplen visar hur parametrar och returvärden skickas för funktioner med de angivna deklarationerna:
Exempel på returvärde 1 – 64-bitars resultat
__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.
Exempel på returvärde 2 – 128 bitars resultat
__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.
Exempel på returvärde 3 – resultat av användartyp efter pekare
struct Struct1 {
int j, k, l; // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.
Exempel på returvärde 4 – resultat av användartyp efter värde
struct Struct2 {
int j, k; // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.
Sparade register för uppringare/samtalsmottagare
X64 ABI tar hänsyn till register RAX, , RCXRDX, R8, R9, R10, R11och XMM0-XMM5 flyktiga. När de är närvarande är de övre delarna av YMM0-YMM15 och ZMM0-ZMM15 också flyktiga. I AVX512VL är även registren 16–31, ZMM, YMM och XMM, volatila. När stöd för AMX finns är TMM tileregistren flyktiga. Överväg att antaga att flyktiga register förstörs vid funktionsanrop om de inte kan bevisas säkra, till exempel genom analys som omfattar hela programoptimering.
X64 ABI tar hänsyn till register RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15och XMM6-XMM15 icke-volatil. De måste sparas och återställas av en funktion som använder dem.
När APX-stöd finns är register R16-R29 flyktiga.
R30 och R31 är icke-volatila.
Funktionspekare
Funktionspekare är bara pekare till etiketten för respektive funktion. Det finns inget innehållsförteckningskrav (TOC) för funktionspekare.
Stöd för flyttal av äldre kod
MMX-registren och flyttalsstackregistren (MM0-MM7/ST0-ST7) bevaras vid kontextväxlingar. Det finns ingen explicit anropskonvention för dessa register. Användning av dessa register är strängt förbjudet i kernellägeskod.
FPCSR
Registertillståndet innehåller även x87 FPU-kontrollordet. Anropskonventionen kräver att det här registret inte ärvolatilt.
Kontrollordregistret för x87 FPU sätts med följande standardvärden vid starten av programkörningen.
| Registrera[bitar] | Inställning |
|---|---|
FPCSR\[0:6] |
Undantag döljer alla 1:er (alla undantag dolda) |
FPCSR\[7] |
Reserverad - 0 |
FPCSR\[8:9] |
Precisionskontroll – 10B (dubbel precision) |
FPCSR\[10:11] |
Avrundningskontroll - 0 (avrunda till närmaste) |
FPCSR\[12] |
Oändlighetskontroll – 0 (används inte) |
En anropare som ändrar något av fälten inom FPCSR måste återställa dem innan den återgår till anroparen. Dessutom måste en anropare som har ändrat något av dessa fält återställa dem till sina standardvärden innan en anropare anropas, såvida inte anroparen enligt överenskommelse förväntar sig de ändrade värdena.
Det finns två undantag till reglerna om icke-volatilitet för kontrollflaggor:
I funktioner där det dokumenterade syftet med den angivna funktionen är att ändra de icke-volatila
FPCSRflaggorna.När det är korrekt att överträdelsen av dessa regler resulterar i ett program som beter sig på samma sätt som ett program som inte bryter mot reglerna, till exempel genom analys av hela programmet.
Trots att det anses vara icke-flyktigt finns det ingen statisk upprullningsbeskrivning som anger var det sparades och varifrån det ska återställas. Undantagssäker kod som ändrar FPCSR bör använda en finaliserare för undantag (t.ex. en C++-destruktor eller en __finally-sats) för att uttryckligen återställa den vid stackavveckling.
MXCSR
Registertillståndet innehåller också MXCSR. Anropskonventionen delar upp det här registret i en flyktig del och en icke-volatil del. Den flyktiga delen består av de sex statusflaggorna, i MXCSR\[0:5], medan resten av registret, MXCSR\[6:15], anses vara icke-volatil.
Den icke-volatila delen anges till följande standardvärden i början av programkörningen:
| Registrera[bitar] | Inställning |
|---|---|
MXCSR\[6] |
Denormaler är nollor – 0 |
MXCSR\[7:12] |
Undantag döljer alla 1:er (alla undantag dolda) |
MXCSR\[13:14] |
Avrundningskontroll - 0 (avrunda till närmaste) |
MXCSR\[15] |
Spola till noll för maskerat underflöde – 0 (av) |
En anropare som ändrar något av de icke-volatila fälten inom MXCSR måste återställa dem innan den återgår till anroparen. Dessutom måste en anropare som har ändrat något av dessa fält återställa dem till sina standardvärden innan en anropare anropas, såvida inte anroparen enligt överenskommelse förväntar sig de ändrade värdena.
Det finns två undantag till reglerna om icke-volatilitet för kontrollflaggor:
I funktioner där det dokumenterade syftet med den angivna funktionen är att ändra de icke-volatila
MXCSRflaggorna.När det är korrekt att överträdelsen av dessa regler resulterar i ett program som beter sig på samma sätt som ett program som inte bryter mot reglerna, till exempel genom analys av hela programmet.
Gör inga antaganden om MXCSR registrets tillstånd för flyktiga delar över en funktionsgräns, såvida inte funktionsdokumentationen uttryckligen beskriver det.
Trots att delar av MXCSR betraktas som icke-flyktiga, finns det ingen statisk unwind-deskriptor som anger var den har sparats och varifrån den ska återställas. Undantagssäker kod som ändrar de icke-volatila delarna av MXCSR bör tillgripa en undantags finaliserare (t.ex. C++ destructor eller en __finally sats) för att återställa den explicit när stacken varvar ned.
setjmp/longjmp
När du inkluderar setjmpex.h eller setjmp.h, resulterar alla anrop till setjmp eller longjmp i en uppvind som anropar destruktorer och __finally-anrop. Det här beteendet skiljer sig från x86, där inkludering av setjmp.h resulterar i att __finally-satser och destruktorer inte anropas.
Ett anrop till setjmp bevarar den aktuella stackpekaren, icke-flyktiga register och MXCSR-register. Anrop till longjmp återgår till det senaste setjmp-anropsstället och återställer stackpekaren, icke-flyktiga register och MXCSR-register till det tillstånd som bevarades vid det senaste setjmp-anropet.
Om APX stöds bör R30 och R31 inte ändras i en funktion från det att setjmp anropas fram till den punkt där det anrop som slutligen resulterar i longjmp görs. Den här begränsningen är resultatet av R30 och R31 sparas inte som en del av jmp_buf – den här strukturdefinitionen kan inte ändras. I stället återställs de via avrullningsverktyget. I följande exempel visas hur skillnaden i hur data återställs påverkar den här begränsningen:
jmp_buf jmpbuffer;
void function_a() {
...
int val = setjmp(jmpbuffer); // At this time R30 is 10
...
if (val == 0) {
function_b(); // At this time R30 is 20
}
...
}
void function_b() {
...
longjmp(jmpbuffer, 1);
...
}
I det här exemplet ändras värdet för R30 från den punkt där setjmp anropas till den punkt där function_b anropas.
function_b I longjmpvarvar du av stacken tills den når funktionen som anropade setjmp (function_a i det här fallet). Värdet som återställs för R30 kommer att vara 20 (värdet vid punkten function_b anropades), inte 10 (värdet som setjmp anropades). Det innebär att när setjmp returneras för andra gången (som resultatet av longjmp) kommer värdet R30 för att anges till 20 i stället för 10, vilket är felaktigt. Det är därför kompilatorer måste säkerställa att R30 och R31 förblir konstanta från den punkt där setjmp anropas till det sista stället i funktionen där det i slutändan skulle kunna leda till att longjmp anropas.
Eftersom longjmp kan anropas från ett undantagsfilter (inte bara en underrutin) avgör detta effektivt att R30 och R31 bör förbli konstant från punkten setjmp anropas via resten av funktionen.