Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ez a cikk ismerteti azokat a szabványos folyamatokat és konvenciókat, amelyeket az egyik függvény (a hívó) használ, amikor hívásokat kezdeményez egy másik függvénybe (a hívottba) az x64 kódban.
A hívási konvencióval kapcsolatos további információkért __vectorcall lásd: __vectorcall.
A hívási konvencióval kapcsolatos további információkért lásd a __preserve_none__preserve_none.
A hívási konvenció alapértelmezései
Az x64 Application Binary Interface (ABI) alapértelmezés szerint négyregisztrációs, gyorshívási konvenciót használ. A hívásveremen a rendszer árnyéktárolóként foglal le helyet a hívottaknak a regiszterek mentéséhez.
Szigorú egy-az-egybeni megfelelés van a függvényhívás argumentumai és az azokhoz használt regiszterek között. Minden olyan argumentumot, amely nem fér el 8 bájtban, vagy nem 1, 2, 4 vagy 8 bájt, hivatkozással kell átadni. Egyetlen argumentum soha nem oszlik el több regiszterben.
Az x87 regiszterverem nincs használatban. Előfordulhat, hogy a hívó használja, de a függvényhívások során változónak tekinti. Minden lebegőpontos művelet a 16 XMM-regiszter használatával történik.
Az egész argumentumok a(z) RCX, RDX, R8 és R9 regiszterekben kerülnek átadásra. A lebegőpontos argumentumok a XMM0L, XMM1L, XMM2L és XMM3L regiszterben kerülnek átadásra. A 16 bájtos argumentumokat hivatkozással adja át a rendszer. A paraméterátadás részletes leírását a paraméterátadás ismerteti. Ezek a regiszterek, valamint RAX, R10, R11, XMM4 és XMM5, volatilisnek tekintendők, vagyis a hívott függvény visszatéréskor esetleg módosíthatja őket. A regiszterhasználat részletesen dokumentálva van az x64 regiszterhasználat és a hívó/hívott által mentett regiszterek részekben.
Prototípusfüggvények esetén az összes argumentumot a rendszer a várt hívótípusokká alakítja át az átadás előtt. A hívó felelős a hívott fél paramétereinek helykiosztásáért. A hívónak mindig elegendő helyet kell lefoglalnia négy regisztrációs paraméter tárolásához, még akkor is, ha a hívó nem veszi fel a sok paramétert. Ez az egyezmény leegyszerűsíti a nemprototípusú C-language függvények és a vararg C/C++ függvények támogatását. Vararg vagy nemprototípusú függvények esetén a lebegőpontos értékeket duplikálni kell a megfelelő általános célú nyilvántartásban. Az első négyen túli paramétereket a hívás előtt a veremen, az árnyéktároló után kell tárolni. A Vararg függvény részletei a Varargsban találhatók. A nemprototípusos függvények adatait a nemprototípusos függvények részletezik.
Igazítás
A legtöbb szerkezet a természetes igazításukhoz igazodik. Az elsődleges kivételek a veremmutató és malloc vagy alloca memória, amelyek 16 bájtra vannak igazítva a teljesítmény növelésére. A 16 bájt feletti igazítást manuálisan kell elvégezni. Mivel a 16 bájt az XMM-műveletek általános igazítási mérete, ennek az értéknek a legtöbb kódnál működnie kell. A struktúraelrendezésről és az igazításról további információt az x64-es típus- és tárelrendezésben talál. A veremelrendezésről további információt az x64 veremhasználatban talál.
Visszatekerhetőség
A levélfüggvények olyan függvények, amelyek nem módosítják a nemvolatilis regisztereket. A nemleaf függvények például egy függvény meghívásával megváltoztathatják a nemvolatilis RSPfüggvényeket. Vagy úgy is változhat RSP , hogy több veremterületet helyez el a helyi változók számára. A nemvolatilis regiszterek helyreállításához a rendszer a kivétel kezelésekor a nemleaf függvényeket statikus adatokkal jegyzeteli. Az adatok azt írják le, hogyan lehet megfelelően kikapcsolni a függvényt egy tetszőleges utasítással. Ezek az adatok pdataként vagy eljárásadatokként lesznek tárolva, ami viszont xdata-ra, a kivételkezelési adatokra utal. Az xdata tartalmazza a visszatekerési információkat, és mutathat további pdata-ra vagy egy kivételkezelő függvényre.
A prologok és epilogok szigorúan korlátozottak, így megfelelően leírhatók xdata-ban. A veremmutatónak 16 bájtonként igazítva kell maradnia a kód bármely olyan régiójában, amely nem része epilognak vagy prolognak, kivéve a levél függvényeken belül. A levélfüggvények egyszerűen feloldhatók a visszatérés szimulálásával, így a pdata és az xdata nem szükséges. A függvény prologok és epilogok megfelelő szerkezetéről az x64 prolog és az epilog című témakörben olvashat bővebben. A kivételkezelésről, valamint a pdata és xdata kivételkezeléséről és visszatekeréséről további információt az x64 kivételkezelés című témakörben talál.
Paraméter átadása
Alapértelmezés szerint az x64-hívási konvenció átadja az első négy argumentumot egy függvénynek a regiszterekben. Az argumentumokhoz használt regiszterek az argumentum pozíciójától és típusától függenek. A fennmaradó argumentumokat a rendszer jobbról balra rendezi a veremen. A hívó lefoglalja a szükséges veremterületet, és ezeket az argumentumokat tárolási vagy áthelyezési utasításokkal írja a veremmemóriába, és minden argumentumhoz 8 bájtos igazítást biztosít.
A bal szélső négy pozícióban lévő egész értékű argumentumokat balról jobbra haladva rendre a RCX, RDX, R8 és R9 helyeken adja át a rendszer. Az ötödik és a magasabb argumentumot a rendszer a korábban ismertetett módon adja át a veremen. A regiszterek összes egész argumentuma helyesen van megadva, így a hívó figyelmen kívül hagyhatja a regiszter felső bitjét, és csak a regiszter szükséges részét érheti el.
Az első négy paraméter lebegőpontos és dupla pontosságú argumentumait a rendszer a pozíciótól függően adja át XMM0 - XMM3. A lebegőpontos értékek csak akkor kerülnek az egészregiszterekbe, RCX, RDX, R8 és R9, ha vannak varargs argumentumok. További részletekért lásd: Varargs. Hasonlóképpen, a XMM0 - XMM3 regiszterek figyelmen kívül lesznek hagyva, ha a megfelelő argumentum egész szám vagy mutatótípus.
__m128 típusok, tömbök és szövegek soha nem kerülnek átadásra érték szerint. Ehelyett a hívó által lefoglalt memóriára egy mutató kerül átadásra. A 8, 16, 32 vagy 64 bites és típusú szerkezetek és __m64 egyesítések ugyanúgy lesznek átadva, mintha azonos méretű egész számok lennének. A rendszer más méretű szerkezeteket vagy egyesítéseket ad át mutatóként a hívó által lefoglalt memóriához. A mutatóként átadott összesítési típusok esetében, beleértve __m128, a hívó által lefoglalt ideiglenes memóriának 16 bájtos igazítású kell legyen.
Olyan belső függvények, amelyek nem foglalnak le veremterületet, és nem hívnak meg más függvényeket, néha más változó regiszterek használatával adhatnak át további regiszterargumentumokat. Ezt az optimalizálást a fordító és a belső függvény implementációja közötti szoros kötés teszi lehetővé.
A hívó felelős azért, hogy szükség esetén a regisztrációs paramétereket az árnyéktérbe dobja.
Az alábbi táblázat a paraméterek átadásának módját foglalja össze típus és pozíció szerint balról:
| Paraméter típusa | ötödik és magasabb | negyedik | harmadik | második | legbaloldalibb |
|---|---|---|---|---|---|
| lebegőpontos | stack | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| egész szám | stack | R9 |
R8 |
RDX |
RCX |
Aggregátumok (8, 16, 32 vagy 64 bit) és __m64 |
stack | R9 |
R8 |
RDX |
RCX |
| Egyéb összesítések mint mutatók | stack | R9 |
R8 |
RDX |
RCX |
__m128, mutatóként |
stack | R9 |
R8 |
RDX |
RCX |
Példa az 1-et átadó argumentumra – az összes egész számra
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élda a 2-et átadó argumentumra – az összes lebegőpontos
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élda a 3-at átadó argumentumra – vegyes ints és floats
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élda a 4 – __m64, és __m128összesítéseket átadó argumentumra
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
Ha a paramétereket varargokkal (például három pont argumentummal) adják át, akkor a normál regisztráció paraméterátadási konvenciója érvényes. Ez az egyezmény magában foglalja az ötödik és újabb argumentumok verembe való kiömlését. A hívó felelőssége, hogy olyan argumentumokat dobjon ki, amelyek címe meg van adva. Csak lebegőpontos értékek esetén az egész számregiszternek és a lebegőpontos regiszternek is tartalmaznia kell az értéket arra az esetre, ha a hívott az egész számregiszterekben várja az értéket.
Nemprototípusú függvények
A nem teljesen prototípusos függvények esetében a hívó az egész számértékeket egész számként, a lebegőpontos értékeket pedig dupla pontosságúként adja át. Csak lebegőpontos értékek esetén az egész számregiszter és a lebegőpontos regiszter is tartalmazza a lebegőpontos értéket, ha a hívott az egész számregiszterekben várja az értéket.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Visszaadott értékek
A 64 bitbe beleférő skaláris visszatérési érték, beleértve a __m64 típust is, a(z) RAX-n keresztül kerül visszaadásra. A nem skalár típusok, beleértve a float, a double és az olyan vektortípusokat, mint a __m128, __m128i és __m128d, a(z) XMM0 értékben lesznek visszaadva. A nem használt bitek állapota a visszaadott értékben RAX , vagy XMM0 nincs meghatározva.
A felhasználó által definiált típusok globális függvényekből és statikus tagfüggvényekből származó érték alapján adható vissza. A felhasználó által megadott típus érték RAXszerinti visszaadásához 1, 2, 4, 8, 16, 32 vagy 64 bit hosszúságúnak kell lennie. Nem rendelkezhet felhasználó által definiált konstruktorral, destruktorsal vagy másolás-hozzárendelési operátorral. Nem rendelkezhet privát vagy védett nemsztatikus adattagokkal, és nem lehetnek referencia típusú nemsztatikus adattagok. Nem rendelkezhet alaposztályokkal vagy virtuális függvényekkel. És csak olyan adattagokkal rendelkezhet, amelyek szintén megfelelnek ezeknek a követelményeknek. Ez a definíció lényegében megegyezik a C++03 POD típussal. Mivel a definíció megváltozott a C++11 szabványban, nem javasoljuk, hogy ezt a tesztet használja std::is_pod . Ellenkező esetben a hívónak memóriát kell lefoglalnia a visszatérési értékhez, és első argumentumként egy mutatót kell átadnia. Ezután a fennmaradó argumentumokat egy pozícióval jobbra tolják. Ugyanazt a mutatót kell visszaadnia a hívónak a következőben RAX: .
Az alábbi példák azt mutatják be, hogyan lesznek átadva a paraméterek és a visszaadott értékek a megadott deklarációkkal rendelkező függvényekhez:
Példa 1– 64 bites eredmény visszaadott értékére
__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élda a 2– 128 bites eredmény visszaadott értékére
__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élda a 3. visszatérési értékre – felhasználótípus eredménye mutató révén
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élda a 4. visszatérési értékre – felhasználótípus eredménye érték szerint
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.
Hívó/hívott által mentett regiszterek
Az x64 ABI a RAX, RCX, RDX, R8, R9, R10, R11 és XMM0-XMM5 regisztereket illékonynak tekinti. Amennyiben jelen vannak, a(z) YMM0-YMM15 és a(z) ZMM0-ZMM15 felső részei szintén illékonyak. Az AVX512VL esetén a ZMM, YMM és XMM 16–31-es regiszterei is volatilisek. AmX-támogatás esetén a TMM csemperegisztrálások változékonyak. Tegye fel, hogy a függvényhívások során illékony regiszterek megsemmisülnek, kivéve, ha az elemzés, például a teljes programoptimalizálás másként nem bizonyítja, hogy biztonságosak.
Az x64 ABI a RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, valamint a XMM6-XMM15 regisztereket nem volatilisnak tekinti. Azokat egy őket használó függvénynek kell mentenie és visszaállítania.
Ha az APX-támogatás jelen van, a regiszterek R16-R29 változékonyak.
R30 és R31 nemvolatilisak.
Függvénymutatók
A függvénymutatók egyszerűen az adott függvény címkéjének mutatói. A függvénymutatókhoz nincs tartalomjegyzék-követelmény.
Lebegőpontos támogatás régebbi kódhoz
Az MMX és a lebegőpontos veremregiszterek (MM0-MM7/ST0-ST7) megőrződnek a kontextusváltások között. Ezekhez a regiszterekhez nincs explicit hívási konvenció. A rendszermag módú kódban szigorúan tilos ezeknek a regisztereknek a használata.
FPCSR
A regiszter állapota az x87 FPU vezérlőszót is tartalmazza. A hívási konvenció azt diktálja, hogy ez a nyilvántartás ne legyen konvolatilis.
Az x87 FPU vezérlőszó-regiszter a program végrehajtásakor a következő standard értékekkel lesz beállítva:
| Regisztráció[bitek] | Beállítás |
|---|---|
FPCSR\[0:6] |
Kivétel maszkolja mind az 1-et (az összes kivétel maszkolt) |
FPCSR\[7] |
Fenntartott – 0 |
FPCSR\[8:9] |
Precíziós vezérlés – 10B (dupla pontosság) |
FPCSR\[10:11] |
Kerekítő vezérlőelem – 0 (kerekítés a legközelebbire) |
FPCSR\[12] |
Végtelen szabályozás - 0 (nincs használatban) |
A hívott függvénynek, amely módosítja a(z) FPCSR mezői közül bármelyiket, vissza kell állítania azokat, mielőtt visszatérne a hívójához. Ezenkívül a hívónak, aki módosította ezen mezők bármelyikét, vissza kell állítania őket a normál értékükre, mielőtt meghívja a hívót, kivéve, ha a hívó megegyezés szerint a módosított értékeket várja.
A vezérlőjelölők nem konvolatitására vonatkozó szabályok alól két kivétel van:
Olyan függvényekben, amelyekben az adott függvény dokumentált célja a nemvolatilis
FPCSRjelzők módosítása.Ha bizonyíthatóan helyes, hogy a szabályok megsértése olyan programot eredményez, amely ugyanúgy viselkedik, mint egy olyan program, amely nem sérti a szabályokat, például teljes programelemzéssel.
Annak ellenére, hogy nem volatilisnak tekintik, nincs olyan statikus veremvisszagörgetési leíró, amely megadja, hol lett elmentve, és honnan kell visszaállítani. A FPCSR elemet módosító kivételbiztos kódnak kivétel esetén valamilyen véglegesítő mechanizmust (például C++-destruktort vagy __finally záradékot) kell használnia ahhoz, hogy a verem lebontásakor azt kifejezetten visszaállítsa.
MXCSR
A regiszter állapota a következőt is tartalmazza MXCSR: . A hívási konvenció ezt a nyilvántartást egy illékony és egy nemvolatilis részre osztja. Az illékony rész a hat állapotjelzőből áll, MXCSR\[0:5]míg a regiszter MXCSR\[6:15]többi része nemvolatilisnek minősül.
A nemvolatilis rész a következő standard értékekre van beállítva a program végrehajtásakor:
| Regisztráció[bitek] | Beállítás |
|---|---|
MXCSR\[6] |
A denormálisok nullák - 0 |
MXCSR\[7:12] |
Kivétel maszkolja mind az 1-et (az összes kivétel maszkolt) |
MXCSR\[13:14] |
Kerekítő vezérlőelem – 0 (kerekítés a legközelebbire) |
MXCSR\[15] |
Nullára történő áttérés maszkolt alulfolyás esetén – 0 (kikapcsolva) |
A hívónak, aki módosítja a nem konvolatilis mezők MXCSR bármelyikét, vissza kell állítania őket, mielőtt visszatér a hívóhoz. Ezenkívül a hívónak, aki módosította ezen mezők bármelyikét, vissza kell állítania őket a normál értékükre, mielőtt meghívja a hívót, kivéve, ha a hívó megegyezés szerint a módosított értékeket várja.
A vezérlőjelölők nem konvolatitására vonatkozó szabályok alól két kivétel van:
Olyan függvényekben, amelyekben az adott függvény dokumentált célja a nemvolatilis
MXCSRjelzők módosítása.Ha bizonyíthatóan helyes, hogy a szabályok megsértése olyan programot eredményez, amely ugyanúgy viselkedik, mint egy olyan program, amely nem sérti a szabályokat, például teljes programelemzéssel.
Ne feltételezz feltételezéseket a MXCSR regisztráció változó részállapotáról egy függvényhatáron keresztül, hacsak a függvénydokumentáció nem írja le kifejezetten.
Annak ellenére, hogy a MXCSR egyes részeit nem volatilisnek tekintik, nincs olyan statikus veremvisszagörgetési leíró, amely megadná, hová lettek mentve, és honnan kell őket visszaállítani. A MXCSR nem felejtő részeit módosító kivételbiztos kódnak kivétel esetén egy véglegesítőt (például C++-destruktort vagy egy __finally záradékot) kell használnia ahhoz, hogy a verem lebontásakor kifejezetten visszaállítsa azokat.
setjmp/longjmp
Amikor setjmpex.h vagy setjmp.h elemeket használ, az összes setjmp vagy longjmp hívás visszatekerést eredményez, amely destruktorokat és __finally hívásokat indít el. Ez a viselkedés különbözik az x86-tól, ahol a setjmp.h eredményeként a __finally záradékok és a destruktorok nincsenek meghívva.
A(z) setjmp hívása megőrzi az aktuális veremmutatót, a nem volatilis regisztereket és a(z) MXCSR regisztereket. A(z) longjmp hívások a legutóbbi setjmp hívási helyre térnek vissza, és a veremmutatót, a nem volatilis regisztereket és a(z) MXCSR regisztereket a legutóbbi setjmp hívás által megőrzött állapotba állítják vissza.
Ha az APX támogatott, a(z) R30 és R31 értékét nem szabad módosítani egy függvényen belül attól a ponttól kezdve, hogy a(z) setjmp meghívásra kerül, addig a pontig, ahol megtörténik az a hívás, amely végső soron a(z) longjmp-at eredményezi. Ez a korlátozás abból ered, hogy a(z) R30 és a(z) R31 nem a(z) jmp_buf részeként kerül mentésre – a struktúra ezen definíciója nem módosítható. Ehelyett az unwinder állítja vissza őket. Az alábbi példa bemutatja, hogy az adatok visszaállításának különbsége hogyan befolyásolja ezt a korlátozást:
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);
...
}
Ebben a példában a(z) R30 értéke attól a ponttól kezdve változik, ahol a(z) setjmp meghívásra kerül, addig a pontig, ahol a(z) function_b meghívásra kerül. A(z) function_b esetén a(z) longjmp visszagöngyölíti a vermet, amíg el nem éri azt a függvényt, amely meghívta a(z) setjmp-t (ebben az esetben: function_a). A visszaállított R30 érték a következő lesz 20 (az a pont function_b , ahol az értéket meghívták), nem 10 (az az érték, amelyre setjmp meghívták). Ez azt jelenti, hogy amikor a setjmp másodszor tér vissza (a longjmp eredményeként), a R30 értéke 20 lesz beállítva 10 helyett, ami helytelen. Ezért a fordítóprogramoknak biztosítaniuk kell, hogy R30 és R31 változatlanok maradjanak attól a ponttól kezdve, hogy a setjmp meghívásra kerül, egészen a függvény azon utolsó pontjáig, ahol végső soron a longjmp meghívására sor kerülhet.
Mivel a(z) longjmp egy kivételszűrőből is meghívható (nem csak egy szubrutinból), ez gyakorlatilag azt jelenti, hogy a(z) R30 és a(z) R31 értékének állandónak kell maradnia attól a ponttól kezdve, hogy a(z) setjmp meghívásra kerül, a függvény hátralévő részében.