Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek popisuje standardní procesy a konvence, které jedna funkce (volající) používá k volání do jiné funkce (volaný) v kódu x64.
Další informace o __vectorcall konvenci volání najdete v tématu __vectorcall.
Další informace o konvenci volání __preserve_none najdete v tématu __preserve_none.
Výchozí nastavení konvence volání
x64 Application Binary Interface (ABI) ve výchozím nastavení používá čtyřregistrovou rychlovolací konvenci volání. V zásobníku volání je přidělen prostor jako stínové úložiště pro volané, aby se tyto registry uložily.
Mezi argumenty volání funkce a registry použitými pro tyto argumenty existuje striktní korespondence 1:1. Všechny argumenty, které se nevejdou do 8 bajtů nebo nejsou 1, 2, 4 nebo 8 bajtů, musí být předány odkazem. Jeden argument se nikdy nerozprostírá mezi více registry.
Zásobník registru x87 se nepoužívá. Může ho používat volaný, ale mezi voláními funkcí ho zvažte jako proměnlivé. Všechny operace s plovoucí desetinnou čárkou se provádějí pomocí 16 registrů XMM.
Celočíselné argumenty jsou předány v registrech RCX, RDX, R8, a R9. Argumenty s pohyblivou řádovou čárkou se předávají v registrech XMM0L, XMM1L, XMM2L a XMM3L. 16-bajtové argumenty se předávají odkazem. Předávání parametrů je podrobně popsáno při předávání parametrů. Tyto registry a RAX, R10, R11, XMM4 a XMM5 jsou považovány za volatilní, tedy takové, které mohou být při návratu změněny volanou funkcí. Použití registrů je podrobně popsáno v používání registrů x64 a uložených registrů volajícího a volaného.
U prototypovaných funkcí se všechny argumenty před předáním převedou na očekávané volané typy. Volající zodpovídá za přidělování prostoru pro parametry volané osoby. Volající musí vždy přidělit dostatek místa pro uložení čtyř parametrů registru, i když volaný nepřebere tento počet parametrů. Tato konvence zjednodušuje podporu neprotypovaných funkcí jazyka C a funkcí jazyka Vararg C/C++. Pro funkce vararg nebo funkce bez prototypu musí být všechny hodnoty pohyblivé řádové čárky duplikovány v odpovídajícím registru pro obecné účely. Všechny parametry nad rámec prvních čtyř musí být uloženy v zásobníku za stínovým úložištěm před voláním. Podrobnosti funkce Vararg najdete v nástroji Varargs. Informace o neprotypované funkci jsou podrobně popsány v neprotypovaných funkcích.
Zarovnání
Většina struktur je zarovnaná podle svého přirozeného zarovnání. Primárními výjimkami jsou ukazatel zásobníku a paměť malloc nebo alloca, které jsou 16-bajtově zarovnané tak, aby pomohly výkonu. Zarovnání nad 16 bajty musí být provedeno ručně. Vzhledem k tomu, že 16 bajtů je běžnou velikost zarovnání pro operace XMM, měla by tato hodnota fungovat pro většinu kódu. Další informace o rozložení struktury a zarovnání najdete v tématu x64 – typ a rozložení úložiště. Informace o rozložení zásobníku najdete v tématu o využití zásobníku x64.
rozvinutelnost
Listové funkce jsou funkce, které nemění žádné nezměnitelné registry. Funkce, která není listová, může například změnit nevolatilní RSP voláním funkce. Nebo může dojít ke změně RSP přidělením více prostoru zásobníku pro místní proměnné. Pokud se při zpracování výjimky obnovují nevolatilní registry, jsou ne-listové funkce opatřeny anotacemi se statickými daty. Data popisují, jak správně uvolnit funkci pomocí libovolné instrukce. Tato data jsou uložena jako pdata, nebo procedurní data, která následně odkazují na xdata, data pro zpracování výjimek. xdata obsahuje informace o rozbalování a může odkazovat na další pdata nebo funkci obslužné rutiny výjimky.
Prology a epilogy jsou vysoce omezené, aby je bylo možné správně popsat v xdata. Ukazatel zásobníku musí zůstat zarovnaný na 16 bajtů v jakékoli části kódu, která není součástí epilogu nebo prologu, s výjimkou koncových funkcí. Listové funkce lze jednoduše rozvinout simulací návratu, takže pdata a xdata nejsou požadovány. Podrobnosti o správné struktuře prologů a epilogů funkcí najdete v tématu x64 prolog a epilog. Další informace o zpracování výjimek, a o zpracování a rozbalování pdata a xdata, najdete v tématu zpracování výjimek x64.
Předávání parametrů
Ve výchozím nastavení konvence volání x64 předává první čtyři argumenty funkci v registrech. Registry použité pro tyto argumenty závisí na pozici a typu argumentu. Zbývající argumenty se předávají v zásobníku v pořadí zprava doleva. Volající si rezervuje požadovaný prostor zásobníku a zapíše tyto argumenty do paměti zásobníku pomocí instrukcí pro uložení nebo přesun, přičemž pro každý argument zachová zarovnání na 8 bajtů.
Celočíselné argumenty na čtyřech krajních levých pozicích se předávají zleva doprava v RCX, RDX, R8 a R9, v tomto pořadí. Páté a vyšší argumenty se předávají v zásobníku, jak jsme popsali dříve. Všechny celočíselné argumenty v registrech jsou správné, takže volaný může ignorovat horní části registru a přistupovat pouze k části registru potřebné.
Všechny argumenty s plovoucí řádovou čárkou a argumenty s dvojitou přesností mezi prvními čtyřmi parametry se předávají v XMM0 - XMM3 v závislosti na své pozici. Hodnoty s pohyblivou řádovou čárkou se ukládají pouze do celočíselných registrů RCX, RDX, R8 a R9, pokud jsou přítomny argumenty varargs. Podrobnosti najdete v tématu Varargs. Podobně jsou registry ignorovány, XMM0 - XMM3 pokud je odpovídající argument celé číslo nebo typ ukazatele.
__m128 typy, pole a řetězce se nikdy nepředávají jako přímá hodnota. Místo toho se ukazatel předá do paměti přidělené volajícím. Struktury a sjednocení velikosti 8, 16, 32 nebo 64 bitů a __m64 typů se předávají jako celá čísla stejné velikosti. Struktury nebo unie jiných velikostí jsou předány jako ukazatele na paměť přidělenou volajícím. U těchto agregačních typů předaných jako ukazatel, včetně __m128, musí být dočasná paměť přidělená volajícím zarovnaná na 16 bajtů.
Vnitřní funkce, které nepřidělují prostor zásobníku a nevolají jiné funkce, někdy používají jiné nestálé registry k předání dalších argumentů registru. Tato optimalizace je možná úzkou vazbou mezi kompilátorem a implementací vnitřní funkce.
Volaný je zodpovědný za uložení parametrů registru do svého stínového prostoru v případě potřeby.
Následující tabulka shrnuje předávání parametrů podle typu a pozice zleva:
| Typ parametru | pátý a výše | čtvrtý | třetí | sekunda | nejvíce vlevo |
|---|---|---|---|---|---|
| plovoucí řádová čárka | stack | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| integer | stack | R9 |
R8 |
RDX |
RCX |
Agregáty (8, 16, 32 nebo 64 bitů) a __m64 |
stack | R9 |
R8 |
RDX |
RCX |
| Další agregace jako ukazatele | stack | R9 |
R8 |
RDX |
RCX |
__m128, jako ukazatel |
stack | R9 |
R8 |
RDX |
RCX |
Příklad předání argumentu 1 – všechna celá čísla
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
Příklad předání argumentu 2 – všechny plovoucí hodnoty
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
Příklad předávání argumentů 3 – smíšená celá čísla a plovoucí hodnoty
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
Příklad předávání argumentů 4 - __m64, __m128, a agregáty
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
Vararg
Pokud jsou parametry předány prostřednictvím varargs (například argumenty se třemi tečkami), použije se normální konvence předávání parametrů registru. Tato konvence zahrnuje přesunutí pátého a pozdějšího argumentu do zásobníku. Je odpovědností volaného zajistit výpis argumentů, u kterých byla získána jejich adresa. Pouze pro hodnoty s plovoucí desetinnou čárkou musí jak celočíselný registr, tak registr s plovoucí desetinnou čárkou obsahovat hodnotu, pro případ, že volaný očekává hodnotu v celočíselném registru.
Neprotypované funkce
U funkcí, které nejsou plně prototypované, volající předává celočíselné hodnoty jako celá čísla a hodnoty s plovoucí desetinnou čárkou jako hodnoty s dvojitou přesností. Pouze pro hodnoty s plovoucí řádovou čárkou obsahují jak celočíselné, tak i plovoucí registry hodnotu, pokud volaný očekává hodnotu v celočíselných registrech.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Vrácené hodnoty
Skalární návratová hodnota, která se vejde do 64 bitů, včetně typu __m64, se vrací prostřednictvím RAX. Neskalární typy včetně typů float, double a vektorových typů, jako jsou __m128, __m128i a __m128d, se vracejí v XMM0. Stav nepoužívaných bitů v hodnotě vrácené v RAX nebo XMM0 není definován.
Uživatelem definované typy mohou být vráceny hodnotou z globálních funkcí a statických členských funkcí. Pokud chcete vrátit uživatelem definovaný typ podle hodnoty , RAXmusí mít délku 1, 2, 4, 8, 16, 32 nebo 64 bitů. Nesmí obsahovat ani uživatelem definovaný konstruktor, destruktor nebo přiřazovací operátor kopírování. Nemůže mít žádné soukromé nebo chráněné nestatické datové členy a žádné nestatické datové členy referenčního typu. Nemůže mít základní třídy ani virtuální funkce. A může mít pouze datové členy, které tyto požadavky splňují. Tato definice je v podstatě stejná jako typ POD C++03. Vzhledem k tomu, že se definice ve standardu C++11 změnila, nedoporučujeme pro tento test používat std::is_pod . Jinak volající musí přidělit paměť pro návratovou hodnotu a předat jí ukazatel jako první argument. Zbývající argumenty se pak posunou o jeden argument doprava. Stejný ukazatel musí volaná funkce vrátit v RAX.
Tyto příklady ukazují, jak se parametry a návratové hodnoty předávají pro funkce se zadanými deklaracemi:
Příklad návratové hodnoty 1 – 64bitový výsledek
__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.
Příklad návratové hodnoty 2 – 128bitový výsledek
__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.
Příklad návratové hodnoty 3 – výsledek typu uživatele podle ukazatele
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.
Příklad návratové hodnoty 4 – výsledek typu uživatele podle hodnoty
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.
Uložené registry volajícího nebo volaný
ABI x64 považuje rejstříky RAX, RCX, RDXR8, R9, , , R10, R11, a XMM0-XMM5 nestálé. Pokud jsou přítomny, horní části YMM0-YMM15 a ZMM0-ZMM15 jsou také volatilní. V AVX512VL jsou registry ZMM, YMM a XMM 16–31 také volatilní. Pokud je k dispozici podpora AMX, registry dlaždic TMM jsou volatilní. Zvažte volatilní registry jako zničené u volání funkcí, pokud není, že by se dalo prokázat bezpečí analýzou, jako je optimalizace celého programu.
ABI x64 považuje registry RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 a XMM6-XMM15 za nevolatilní. Musí být uloženy a obnoveny funkcí, která je používá.
Pokud je k dispozici podpora APX, registry R16-R29 jsou nestálé.
R30 a R31 jsou nevolatilní.
Ukazatelé funkcí
Ukazatele funkce jsou jednoduše ukazatele na popisek příslušné funkce. Pro ukazatele funkcí neexistuje žádný požadavek na tabulku obsahu (TOC).
Podpora pro čísla s plovoucí desetinnou čárkou pro starší kód
Registry zásobníku MMX a registry zásobníku s pohyblivou řádovou čárkou (MM0-MM7/ST0-ST7) jsou při přepínání kontextu zachovány. Pro tyto registry neexistuje žádná explicitní konvence volání. Použití těchto registrů je přísně zakázáno v kódu režimu jádra.
FPCSR
Stav registrů obsahuje také ovládací slovo FPU x87. Konvence volání určuje, aby tento registr byl nevolatilní.
Ovládací prvek X87 FPU word register se nastaví pomocí následujících standardních hodnot na začátku provádění programu:
| Zaregistrovat[bity] | Nastavení |
|---|---|
FPCSR\[0:6] |
Masky výjimek všech 1 (všechny výjimky maskované) |
FPCSR\[7] |
Rezervováno – 0 |
FPCSR\[8:9] |
Přesné ovládání – 10B (dvojitá přesnost) |
FPCSR\[10:11] |
Nastavení zaokrouhlování – 0 (zaokrouhlení na nejbližší) |
FPCSR\[12] |
Ovládání nekonečna – 0 (nepoužívá se) |
Volaná funkce, která změní kterékoli pole v FPCSR, je musí před návratem volajícímu obnovit. Volající, který změnil některá z těchto polí, navíc musí před vyvoláním volaného obnovit jejich standardní hodnoty, pokud volaný neočekává změněné hodnoty.
Existují dvě výjimky pravidel o nevolatilitě příznaků ovládacího prvku:
Ve funkcích, kde zdokumentovaný účel dané funkce spočívá v úpravě nevolatilních
FPCSRpříznaků.Pokud je prokazatelně správné, že porušení těchto pravidel vede k tomu, že program má stejné chování jako program, který tato pravidla neporušuje, například prostřednictvím analýzy celého programu.
Přestože se považuje za nevolatilní, neexistuje žádný statický popisovač unwind popisující, kde byl uložen a odkud by se měl obnovit. Kód odolný vůči výjimkám, který upravuje FPCSR, by měl při odvíjení zásobníku použít finalizační mechanismus pro výjimky (např. destruktor v jazyce C++ nebo klauzuli __finally), aby jej bylo možné explicitně obnovit.
MXCSR
Stav registru také zahrnuje MXCSR. Konvence volání rozdělí tento registr na nestálou část a nevolatilní část. Nestálá část se skládá ze šesti příznaků stavu, zatímco MXCSR\[0:5]zbytek registru , MXCSR\[6:15]je považován za nevolatilní.
Nevolatilní část je nastavena na následující standardní hodnoty na začátku provádění programu:
| Zaregistrovat[bity] | Nastavení |
|---|---|
MXCSR\[6] |
Denormální čísla jsou nuly |
MXCSR\[7:12] |
Masky výjimek všech 1 (všechny výjimky maskované) |
MXCSR\[13:14] |
Nastavení zaokrouhlování – 0 (zaokrouhlení na nejbližší) |
MXCSR\[15] |
Nastavení na nulu při maskovaném podtečení – 0 (vypnuto) |
Volaná funkce, která změní kterékoli z nevolatilních polí v MXCSR, je musí před návratem volajícímu obnovit. Volající, který změnil některá z těchto polí, navíc musí před vyvoláním volaného obnovit jejich standardní hodnoty, pokud volaný neočekává změněné hodnoty.
Existují dvě výjimky pravidel o nevolatilitě příznaků ovládacího prvku:
Ve funkcích, kde zdokumentovaný účel dané funkce spočívá v úpravě nevolatilních
MXCSRpříznaků.Pokud je prokazatelně správné, že porušení těchto pravidel vede k tomu, že program má stejné chování jako program, který tato pravidla neporušuje, například prostřednictvím analýzy celého programu.
Nepředpokládáme žádné předpoklady o MXCSR stavu nestálých částí registru v rámci hranice funkce, pokud ji dokumentace k funkci explicitně nepopisuje.
Navzdory tomu, že se MXCSR části považují za nevolatilní, neexistuje žádný statický popisovač unwind popisující, kde byl uložen a odkud by se měl obnovit. Kód bezpečný vůči výjimkám, který upravuje nevolatilní části MXCSR, by měl použít finalizační mechanismus pro výjimky (např. destruktor v C++ nebo klauzuli __finally), aby je během odvíjení zásobníku explicitně obnovil.
setjmp/longjmp
Když zahrnete setjmpex.h nebo setjmp.h, všechna volání setjmp nebo longjmp způsobí proces uvolnění, který vyvolá destruktory a volání __finally. Toto chování se liší od x86, kde zahrnutí setjmp.h způsobí, že __finally klauzule a destruktory nejsou vyvolány.
Volání setjmp zachovává aktuální ukazatel zásobníku, nevolatilní registry a registry MXCSR. Volání funkce longjmp se vrací do místa posledního volání setjmp a obnoví ukazatel zásobníku, nevolatilní registry a registry MXCSR do stavu uloženého při posledním volání setjmp.
Pokud je APX podporováno, R30 a R31 by neměly být ve funkci měněny od okamžiku, kdy je voláno setjmp, až do okamžiku, kdy je provedeno volání, jehož výsledkem je nakonec longjmp. Toto omezení je důsledkem toho, že R30 a R31 se neukládají jako součást jmp_buf – tuto definici struktury nelze změnit. Místo toho jsou obnoveny pomocí unwinderu. Následující příklad ukazuje, jak rozdíl při obnovování dat ovlivňuje toto omezení:
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);
...
}
V tomto příkladu se hodnota R30 mění od místa, kde je volána funkce setjmp, do místa, kde je volána funkce function_b. V function_b, longjmp uvolní zásobník, dokud nedosáhne funkce, která volala setjmp (function_a v tomto případě). Hodnota obnovená pro R30 bude 20 (hodnota v okamžiku, kdy bylo zavoláno function_b), nikoli 10 (hodnota v okamžiku, kdy bylo zavoláno setjmp). To znamená, že pokud setjmp se vrátí po druhé (v důsledku longjmp) hodnota R30 bude nastavena na 20 místo 10, což je nesprávné. To je důvod, proč kompilátory musí zajistit, aby R30 a R31 zůstaly konstantní od okamžiku, kdy je zavolána funkce setjmp, až po poslední místo ve funkci, které by nakonec mohlo vést k zavolání longjmp.
Vzhledem k tomu, že longjmp může být volána z filtru výjimek (nejen z podprogramu), to fakticky znamená, že R30 a R31 by měly zůstat konstantní od okamžiku, kdy je zavolána setjmp, až do konce funkce.