Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym artykule opisano standardowe procesy i konwencje używane przez jedną funkcję (obiekt wywołujący) do tworzenia wywołań do innej funkcji (wywoływanej) w kodzie x64.
Aby uzyskać więcej informacji na temat __vectorcall konwencji wywoływania, zobacz __vectorcall.
Aby uzyskać więcej informacji na temat __preserve_none konwencji wywoływania, zobacz __preserve_none.
Domyślne konwencje wywoływania
Interfejs binarny aplikacji x64 (ABI) domyślnie używa konwencji wywoływania z czterema rejestrami i szybką obsługą. Miejsce jest przydzielane na stosie wywołań jako magazyn cieniowy dla funkcji wywoływanych, aby zapisać te rejestry.
Istnieje ścisła korespondencja jeden do jednego między argumentami wywołania funkcji a rejestrami używanymi dla tych argumentów. Każdy argument, który nie mieści się w 8 bajtach lub nie jest 1, 2, 4 lub 8 bajtów, musi zostać przekazany przez referencję. Jeden argument nigdy nie jest rozłożony na wiele rejestrów.
Stos rejestru x87 jest nieużywany. Może być używany przez obiekt wywoływany, ale należy wziąć pod uwagę, że nietrwały między wywołaniami funkcji. Wszystkie operacje zmiennoprzecinkowe są wykonywane przy użyciu 16 rejestrów XMM.
Argumenty liczb całkowitych są przekazywane w rejestrach RCX, RDX, R8 i R9. Argumenty zmiennoprzecinkowe są przekazywane w XMM0L, XMM1L, XMM2L i XMM3L. Argumenty 16-bajtowe są przekazywane przez referencję. Przekazywanie parametrów zostało szczegółowo opisane w artykule Przekazywanie parametrów. Te rejestry i RAX, R10, R11, XMM4 i XMM5 są uważane za niestabilne lub potencjalnie zmienione przez wywoływanie po powrocie. Rejestrowanie użycia jest szczegółowo udokumentowane w zastosowaniu rejestrów x64 i rejestrach zachowywanych przez wywołującego/wywoływanego.
W przypadku funkcji prototypowych wszystkie argumenty są konwertowane na oczekiwane typy wywoływane przed przekazaniem. Obiekt wywołujący jest odpowiedzialny za przydzielanie miejsca dla parametrów obiektu wywoływanego. Wywołujący musi zawsze przydzielić wystarczającą ilość pamięci do przechowywania czterech parametrów rejestru, nawet jeśli wywoływany nie wymaga tylu parametrów. Ta konwencja upraszcza obsługę nietypowych funkcji języka C i funkcji vararg C/C++. W przypadku funkcji vararg lub nietypowych wszystkie wartości zmiennoprzecinkowe muszą być zduplikowane w odpowiednim rejestrze ogólnego przeznaczenia. Wszystkie parametry wykraczające poza pierwsze cztery muszą być przechowywane na stosie po pamięci cienia przed wywołaniem. Szczegóły funkcji Vararg można znaleźć w temacie Varargs. Nietypowe informacje o funkcji są szczegółowo opisane w temacie Funkcje nietypowe.
Wyrównanie
Większość struktur jest ustawiona zgodnie z ich naturalnym wyrównaniem. Główne wyjątki to wskaźnik stosu i pamięć malloc lub alloca, które są wyrównane do 16 bajtów, aby wspomóc wydajność. Wyrównanie powyżej 16 bajtów należy wykonać ręcznie. Ponieważ 16 bajtów jest typowym rozmiarem wyrównania dla operacji XMM, ta wartość powinna działać dla większości kodu. Aby uzyskać więcej informacji na temat układu struktury i wyrównania, zobacz Typ x64 i układ pamięci masowej. Aby uzyskać informacje o układzie stosu, zobacz użycie stosu x64.
Możliwość rozwijania
Funkcje liścia to funkcje, które nie zmieniają żadnych rejestrów niewolnych. Funkcja nielistkowa może na przykład zmienić nieulotny RSP, poprzez wywołanie funkcji. Można też zmienić dostawcę zasobów przez przydzielanie większej ilości miejsca na stos dla zmiennych lokalnych. Aby odzyskać rejestry nieulotne w przypadku obsługi wyjątku, funkcje nieliściaste są oznaczone danymi statycznymi. W danych opisano sposób prawidłowego odwijania funkcji przy dowolnej instrukcji. Te dane są przechowywane jako dane pdata lub dane procedury, które z kolei odnoszą się do xdata, danych obsługi wyjątków. Dane xdata zawierają informacje o odwijeniu i mogą wskazywać dodatkowe dane pdata lub funkcję obsługi wyjątków.
Prologi i epilogi są wysoce ograniczone, aby można je było właściwie opisać w xdata. Wskaźnik stosu musi pozostać wyrównany na 16 bajtów w dowolnym regionie kodu, który nie jest częścią epilogu ani prologu, z wyjątkiem funkcji liściowych. Funkcje liścia mogą być rozwiane po prostu przez symulowanie powrotu, więc pdata i xdata nie są wymagane. Aby uzyskać szczegółowe informacje na temat właściwej struktury prologów funkcji i epilogów, zobacz x64 prolog i epilog. Aby uzyskać więcej informacji na temat obsługi wyjątków oraz odwijania i interpretacji danych pdata i xdata, zobacz obsługę wyjątków x64.
Przekazywanie parametrów
Domyślnie konwencja wywoływania x64 przekazuje pierwsze cztery argumenty do funkcji w rejestrach. Rejestry używane dla tych argumentów zależą od pozycji i typu argumentu. Pozostałe argumenty są umieszczane na stosie w kolejności od prawej do lewej. Obiekt wywołujący rezerwuje wymaganą przestrzeń stosu i zapisuje te argumenty w pamięci stosu przy użyciu instrukcji zapisu lub przemieszczania, zachowując 8-bajtowe wyrównanie dla każdego argumentu.
Argumenty liczb całkowitych w najbardziej lewej czwórce pozycji są przekazywane w kolejności od lewej do prawej odpowiednio w RCX, RDX, R8 i R9. Piąte i wyższe argumenty są przekazywane na stosie zgodnie z wcześniejszym opisem. Wszystkie argumenty całkowite w rejestrach są uzasadnione prawem, więc obiekt wywoływany może zignorować górne bity rejestru i uzyskać dostęp tylko do części rejestru niezbędnej.
Wszystkie argumenty zmiennoprzecinkowe i podwójnej precyzji w pierwszych czterech parametrach są przekazywane w XMM0 - XMM3, w zależności od położenia. Wartości zmiennoprzecinkowe są umieszczane tylko w rejestrach liczb całkowitych RCX, RDX, R8 i R9, gdy istnieją argumenty varargs. Aby uzyskać szczegółowe informacje, zobacz Varargs. Podobnie rejestry XMM0 — XMM3 są ignorowane, gdy odpowiedni argument jest liczbą całkowitą lub typem wskaźnika.
__m128 typy, tablice i ciągi nigdy nie są przekazywane przez wartość bezpośrednią. Zamiast tego wskaźnik jest przekazywany do pamięci przydzielonej przez obiekt wywołujący. Struktury i związki o rozmiarze 8, 16, 32 lub 64 bitach i __m64 typach są przekazywane tak, jakby były liczbą całkowitą o tym samym rozmiarze. Struktury lub związki innych rozmiarów są przekazywane jako wskaźnik do pamięci przydzielonej przez obiekt wywołujący. W przypadku tych typów agregatowych przekazywanych jako wskaźnik, w tym __m128, pamięć tymczasowa przydzielona przez wywołującego musi być wyrównana do 16 bajtów.
Funkcje wewnętrzne, które nie przydzielają miejsca na stosie i nie wywołują innych funkcji, czasami używają innych rejestrów tymczasowych do przekazywania dodatkowych argumentów rejestrów. Ta optymalizacja jest możliwa przez ścisłe powiązanie między kompilatorem a implementacją funkcji wewnętrznej.
Obiekt wywoływany jest odpowiedzialny za zrzucenie parametrów rejestru do obszaru cienia w razie potrzeby.
Poniższa tabela zawiera podsumowanie sposobu przekazywania parametrów według typu i pozycji po lewej stronie:
| Typ parametru | piąty lub wyższy | czwarty | trzeci | drugi | Po lewej stronie |
|---|---|---|---|---|---|
| zmiennoprzecinkowa | stos | XMM3 | XMM2 | XMM1 | XMM0 |
| liczba całkowita | stos | R9 | R8 | RDX | RCX |
Agregaty (8, 16, 32 lub 64 bity) i __m64 |
stos | R9 | R8 | RDX | RCX |
| Inne agregacje jako wskaźniki | stos | R9 | R8 | RDX | RCX |
__m128, jako wskaźnik |
stos | R9 | R8 | RDX | RCX |
Przykład przekazywania argumentu 1 — wszystkie liczby całkowite
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
Przykład przekazywania argumentów 2 — wszystkie liczby zmiennoprzecinkowe
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
Przykład przekazywania argumentów 3 — zmieszane liczby całkowite i zmiennoprzecinkowe
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
Przykład przekazywania argumentów 4 — __m64, __m128 i agregacji
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
Jeśli parametry są przekazywane przez varargs (na przykład argumenty wielokropka), ma zastosowanie normalna konwencja przekazywania parametrów rejestru. Konwencja ta obejmuje umieszczenie piątego i kolejnych argumentów na stosie. To odpowiedzialność wywoływanego za zrzucanie argumentów, które mają pobrany adres. Tylko w przypadku wartości zmiennoprzecinkowych zarówno rejestry liczb całkowitych, jak i rejestry zmiennoprzecinkowe muszą zawierać daną wartość, jeśli obiekt wywoływany oczekuje jej w rejestrach liczb całkowitych.
Funkcje nietypowe
W przypadku funkcji, które nie są w pełni prototypowane, obiekt wywołujący przekazuje wartości całkowite jako liczby całkowite i wartości zmiennoprzecinkowe jako podwójną precyzję. Tylko w przypadku wartości zmiennoprzecinkowych zarówno rejestr liczb całkowitych, jak i rejestr zmiennoprzecinkowy zawierają wartość zmiennoprzecinkową, jeśli obiekt wywoływany oczekuje wartości w rejestrach liczb całkowitych.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Wartości zwracane
Wartość zwracana skalarna, która może mieścić się w 64 bitach, w tym __m64 typ, jest zwracana za pośrednictwem funkcji RAX. Typy niepodobne, w tym liczby zmiennoprzecinkowe, podwójne i typy wektorów, takie jak __m128, __m128i__m128d , są zwracane w programie XMM0. Stan nieużywanych bitów w wartości zwróconej w raX lub XMM0 jest niezdefiniowany.
Typy zdefiniowane przez użytkownika mogą być zwracane przez wartość z funkcji globalnych i statycznych funkcji składowych. Aby zwrócić typ zdefiniowany przez użytkownika przez wartość w RAX, jego długość musi wynosić 1, 2, 4, 8, 16, 32 lub 64 bitów. Nie musi również mieć zdefiniowanego przez użytkownika konstruktora, destruktora ani operatora przypisania kopiowania. Nie może mieć prywatnych ani chronionych niestatycznych składowych danych, ani niestatycznych składowych danych typu referencyjnego. Nie może mieć klas podstawowych ani funkcji wirtualnych. Ponadto może mieć tylko pola danych, które spełniają te wymagania. Ta definicja jest zasadniczo taka sama jak typ POD w C++03. Ponieważ definicja zmieniła się w standardzie C++11, nie zalecamy używania std::is_pod tego testu. W przeciwnym razie wywołujący musi przydzielić pamięć dla wartości zwracanej i przekazać wskaźnik do niej jako pierwszy argument. Pozostałe argumenty są następnie przesuwane o jeden argument w prawo. Ten sam wskaźnik musi być zwracany przez obiekt wywoływany w usłudze RAX.
W tych przykładach pokazano, jak parametry i zwracane wartości są przekazywane dla funkcji z określonymi deklaracjami:
Przykład zwracanej wartości 1 — wynik 64-bitowy
__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.
Przykład wartości zwracanej 2 — wynik 128-bitowy
__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.
Przykład wartości zwracanej 3 — wynik typu użytkownika według wskaźnika
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.
Przykład wartości zwracanej 4 — wynik typu użytkownika według wartości
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.
Rejestry zapisane przez funkcję wywołującą/wywoływaną
ABI dla x64 uwzględnia rejestry RAX, RCX, RDX, R8, R9, R10, R11 oraz XMM0-XMM5 jako zmienne. Jeśli są obecne, górne części YMM0-YMM15 i ZMM0-ZMM15 są również niestabilne. Na AVX512VL rejestry ZMM, YMM i XMM są również niestabilne. Gdy jest obecna obsługa protokołu AMX, rejestry kafelków TMM są nietrwałe. Rozważ, że ulotne rejestry są niszczone podczas wywołań funkcji, chyba że można to bezpiecznie udowodnić poprzez analizę, na przykład dzięki optymalizacji całego programu.
Plik ABI dla x64 uwzględnia rejestry RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 oraz XMM6-XMM15 jako nieulotne. Należy je zapisać i przywrócić za pomocą funkcji, która ich używa.
Wskaźniki funkcji
Wskaźniki funkcji to po prostu wskaźniki do etykiety odpowiedniej funkcji. Nie ma wymagania spisu treści (TOC) dla wskaźników funkcji.
Obsługa zmiennoprzecinkowa dla starszego kodu
Rejestry stosów MMX i zmiennoprzecinkowych (MM0-MM7/ST0-ST7) są zachowywane w przełącznikach kontekstowych. Nie ma jawnej konwencji wywoływania tych rejestrów. Korzystanie z tych rejestrów jest ściśle zabronione w kodzie trybu jądra.
FPCSR
Stan rejestru zawiera również słowo kontrolne x87 FPU. Konwencja wywołania wymaga, aby ten rejestr był nieulotny (nonvolatile).
Rejestr wyrazów sterujących x87 FPU jest ustawiany przy użyciu następujących standardowych wartości na początku wykonywania programu:
| Rejestrowanie[bity] | Ustawienie |
|---|---|
| FPCSR[0:6] | Wyjątki maskuje wszystkie 1s (wszystkie wyjątki maskowane) |
| FPCSR[7] | Zarezerwowane — 0 |
| FPCSR[8:9] | Kontrolka precyzji — 10B (podwójna precyzja) |
| FPCSR[10:11] | Kontrola zaokrąglania — 0 (zaokrąglanie do najbliższej wartości) |
| FPCSR[12] | Kontrolka Nieskończoność — 0 (nie jest używana) |
Obiekt wywoływany, który modyfikuje dowolne pola w FPCSR, musi je przywrócić przed powrotem do obiektu wywołującego. Ponadto obiekt wywołujący, który zmodyfikował dowolne z tych pól, musi przywrócić je do ich standardowych wartości przed wywołaniem, chyba że wywoływany zgadza się na zmodyfikowane wartości.
Istnieją dwa wyjątki od reguł dotyczących nieulotności flag kontroli:
W funkcjach, w których udokumentowanym celem danej funkcji jest modyfikacja nieulotnych flag FPCSR.
Gdy jest udowadnialnie poprawne, że naruszenie tych reguł powoduje, że program zachowuje się tak samo jak program, który nie narusza reguł, na przykład poprzez analizę całego programu.
MXCSR
Stan rejestru obejmuje również MXCSR. Konwencja wywołania dzieli ten rejestr na ulotną część oraz trwałą część. Część volatile składa się z sześciu flag stanu w MXCSR[0:5], podczas gdy reszta rejestru MXCSR[6:15] jest uważana za niewolną.
Część nonvolatile jest ustawiona na następujące wartości standardowe na początku wykonywania programu:
| Rejestrowanie[bity] | Ustawienie |
|---|---|
| MXCSR[6] | Denormalizacje to zera - 0 |
| MXCSR[7:12] | Wyjątki maskuje wszystkie 1s (wszystkie wyjątki maskowane) |
| MXCSR[13:14] | Sterowanie zaokrągleniem — 0 (zaokrąglenie do najbliższej wartości) |
| MXCSR[15] | Opróżnij do zera dla zamaskowanego podpełnienia — 0 (wyłączone) |
Funkcja wywoływana, która modyfikuje dowolne z pól niewymazywalnych w rejestrze MXCSR, musi je przywrócić przed powrotem do funkcji wywołującej. Ponadto wywołujący, który zmodyfikował któreś z tych pól, musi przywrócić je do wartości standardowych przed wywołaniem, chyba że zgodnie z umową wywoływany obiekt oczekuje zmodyfikowanych wartości.
Istnieją dwa wyjątki od reguł dotyczących nieulotności flag kontroli:
W funkcjach, w których udokumentowanym celem danej funkcji jest modyfikowanie nieulotnych rejestrów MXCSR.
Gdy można to dowieść, że naruszenie tych reguł skutkuje programem zachowującym się tak samo jak program, który nie narusza reguł, na przykład poprzez analizę całego programu.
Nie należy zakładać stanu nietrwałej części rejestru MXCSR w granicach funkcji, chyba że w dokumentacji funkcji jawnie go opisano.
setjmp/longjmp
Gdy dołączasz setjmpex.h lub setjmp.h, wszystkie wywołania do setjmp lub longjmp powodują proces odwijania, który wywołuje destruktory oraz __finally wywołania. To zachowanie różni się od x86, gdzie uwzględnienie setjmp.h powoduje, że klauzule __finally i destruktory nie są wywoływane.
Wywołanie setjmp zachowuje bieżący wskaźnik stosu, rejestry nieulotne oraz rejestry MXCSR. Wywołania longjmp powracają do najbliższego miejsca wywołania setjmp i resetują wskaźnik stosu, rejestry niezależne oraz rejestry MXCSR, przywracając stan zachowany przez najnowsze setjmp wywołanie.