Udostępnij przez


Omówienie konwencji X64 ABI

W tym temacie opisano podstawowy interfejs binarny aplikacji (ABI) dla x64, rozszerzenie 64-bitowe do architektury x86. Obejmuje on tematy, takie jak konwencja wywoływania, układ typów, użycie stosu i rejestrów i inne.

Konwencje wywoływania x64

Istnieją dwie ważne różnice między x86 i x64:

  • Możliwość adresowania 64-bitowego
  • Szesnaście rejestrów 64-bitowych do użytku ogólnego.

Biorąc pod uwagę rozszerzony zestaw rejestrów, x64 używa __fastcall konwencji wywoływania i modelu obsługi wyjątków opartego na protokole RISC.

Konwencja __fastcall używa rejestrów dla pierwszych czterech argumentów, a ramka stosu przekazuje więcej argumentów. Aby uzyskać szczegółowe informacje na temat konwencji wywoływania x64, w tym użycie rejestrów, parametrów stosu, wartości zwracanych i odwijania stosu, zapoznaj się z konwencją wywoływania x64.

Aby uzyskać więcej informacji o __vectorcall konwencji wywoływania, zobacz __vectorcall.

Włączanie optymalizacji kompilatora x64

Poniższa opcja kompilatora pomaga zoptymalizować aplikację pod kątem architektury x64:

Typ x64 i układ magazynu

W tej sekcji opisano przechowywanie typów danych dla architektury x64.

Typy skalarne

Chociaż możliwy jest dostęp do danych z dowolnym wyrównaniem, wyrównaj dane na ich naturalnej granicy lub wielokrotności tej granicy, aby uniknąć utraty wydajności. Wyliczenia są stałymi wartościami i traktowane jako liczby całkowite o rozmiarze 32 bitów. W poniższej tabeli opisano definicję typu i zalecane przechowywanie danych, jak ma się to do wyrównania przy użyciu następujących wartości wyrównania.

  • Bajt — 8 bitów
  • Word — 16 bitów
  • Doubleword – 32 bity
  • Quadword — 64 bitów
  • Octaword — 128 bitów
Typ skalarny Typ danych języka C Rozmiar magazynu (w bajtach) Zalecane wyrównanie
INT8 char 1 Bajt
UINT8 unsigned char 1 Bajt
INT16 short 2 Słowo
UINT16 unsigned short 2 Słowo
INT32 int, long 4 Podwójne słowo
UINT32 unsigned int, unsigned long 4 Podwójne słowo
INT64 __int64 8 Podwójne słowo
UINT64 unsigned __int64 8 Podwójne słowo
FP32 (precyzja pojedyncza) float 4 Podwójne słowo
FP64 (podwójna precyzja) double 8 Podwójne słowo
POINTER * 8 Podwójne słowo
__m64 struct __m64 8 Podwójne słowo
__m128 struct __m128 16 Octaword

Układ agregacji i unii x64

Inne typy, takie jak tablice, struktury i związki, mają bardziej rygorystyczne wymagania dotyczące wyrównania, które zapewniają spójne agregowanie i przechowywanie w unii oraz pobieranie danych. Poniżej przedstawiono definicje tablicy, struktury i unii:

  • Tablica

    Zawiera uporządkowaną grupę sąsiednich obiektów danych. Każdy obiekt jest nazywany elementem. Wszystkie elementy w tablicy mają ten sam rozmiar i typ danych.

  • Struktura

    Zawiera uporządkowaną grupę obiektów danych. W przeciwieństwie do elementów tablicy elementy członkowskie struktury mogą mieć różne typy danych i rozmiary.

  • Unia

    Obiekt, który zawiera dowolny zestaw nazwanych elementów członkowskich. Elementy nazwanego zestawu mogą być dowolnego typu. Pamięć przydzielona dla unii (lub struktury) jest równa pamięci wymaganej dla największego elementu unii, oraz wszelkie wypełnienie potrzebne do wyrównania.

W poniższej tabeli przedstawiono zdecydowanie zalecane wyrównanie składowych skalarnych związków i struktur.

Typ skalarny Typ danych języka C Wymagane wyrównanie
INT8 char Bajt
UINT8 unsigned char Bajt
INT16 short Słowo
UINT16 unsigned short Słowo
INT32 int, long Podwójne słowo
UINT32 unsigned int, unsigned long Podwójne słowo
INT64 __int64 Podwójne słowo
UINT64 unsigned __int64 Podwójne słowo
FP32 (precyzja pojedyncza) float Podwójne słowo
FP64 (podwójna precyzja) double Podwójne słowo
POINTER * Podwójne słowo
__m64 struct __m64 Podwójne słowo
__m128 struct __m128 Octaword

Obowiązują następujące reguły wyrównania agregacji:

  • Wyrównanie tablicy jest takie samo jak wyrównanie jednego z elementów tablicy.

  • Wyrównanie początku struktury lub unii to maksymalne wyrównanie dowolnego z jej członków. Każdy członek w ramach struktury lub unii musi być umieszczony w odpowiednim wyrównaniu zgodnie z definicją z poprzedniej tabeli, co może wymagać ukrytego wypełnienia wewnętrznego, w zależności od poprzedniego członka.

  • Rozmiar struktury musi być całkowitą wielokrotnością jej wyrównania, co może wymagać wypełnienia po ostatnim elemencie. Ponieważ struktury i związki mogą być zgrupowane w tablicach, każdy element tablicy struktury lub unii musi zaczynać się i kończyć na odpowiednim wyrównaniu wcześniej określonym.

  • Istnieje możliwość dostosowania danych w taki sposób, aby przekraczać wymagania dotyczące wyrównania, pod warunkiem, że poprzednie zasady są zachowane.

  • Pojedynczy kompilator może dostosować pakowanie struktury ze względów rozmiaru. Na przykład /Zp (Wyrównanie członków struktury) umożliwia dostosowanie pakowania struktur.

Przykłady wyrównania struktury x64

Poniższe cztery przykłady deklarują wyrównaną strukturę lub unię, a odpowiadające im liczby ilustrują układ tej struktury lub unii w pamięci. Każda kolumna na rysunku reprezentuje bajt pamięci, a liczba w kolumnie wskazuje przemieszczenie tego bajtu. Nazwa w drugim wierszu każdego rysunku odpowiada nazwie zmiennej w deklaracji . Zacienione kolumny wskazują wypełnienie wymagane do osiągnięcia określonego wyrównania.

Przykład 1

// Total size = 2 bytes, alignment = 2 bytes (word).

_declspec(align(2)) struct {
    short a;      // +0; size = 2 bytes
}

Diagram przedstawiający układ struktury dla przykładu 1. Diagram pokazuje 2 bajty pamięci. Członek a, krótki typu short, zajmuje bajty od 0 do 1.

Przykład 2

// Total size = 24 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) struct {
    int a;       // +0; size = 4 bytes
    double b;    // +8; size = 8 bytes
    short c;     // +16; size = 2 bytes
}

Diagram przedstawiający układ struktury, na przykład 2.

Na diagramie przedstawiono 24 bajty pamięci. Element int zajmuje bajty od 0 do 3. Diagram przedstawia wypełnienie dla bajtów od 4 do 7. Składowa b, podwójna, zajmuje bajty od 8 do 15. Członek c, typu short, zajmuje bajty od 16 do 17. Bajty od 18 do 23 są nieużywane.

Przykład 3

// Total size = 12 bytes, alignment = 4 bytes (doubleword).

_declspec(align(4)) struct {
    char a;       // +0; size = 1 byte
    short b;      // +2; size = 2 bytes
    char c;       // +4; size = 1 byte
    int d;        // +8; size = 4 bytes
}

Diagram przedstawiający układ struktury, na przykład 3.

Na diagramie przedstawiono 12 bajtów pamięci. Element typu char zajmuje bajt 0. Wypełnianie bajtów 1. Element b, typu short, zajmuje bajty od 2 do 4. Element c, typu char, zajmuje bajt 4. Bajty od 5 do 7 to wypełnienie. Składowa d, typ danych int, zajmuje bajty od 8 do 11.

Przykład 4

// Total size = 8 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) union {
    char *p;      // +0; size = 8 bytes
    short s;      // +0; size = 2 bytes
    long l;       // +0; size = 4 bytes
}

Diagram przedstawiający układ unii, na przykład 4.

Na diagramie przedstawiono 8 bajtów pamięci. Element p, znak znakowy, zajmuje bajt 0. Pole typu short zajmuje bajty od 0 do 1 włącznie. Członek l, typu long, zajmuje bajty od 0 do 3. Bajty od 4 do 7 są wypełnieniem.

Pola bitów

Pola bitów struktury są ograniczone do 64 bitów i mogą być typu int ze znakiem, int bez znaku, int64 lub int64 bez znaku. Pola bitowe, które przekraczają granicę typu, pomijają bity, aby wyrównać pole bitowe do następnego wyrównania typu. Na przykład pola bitowe liczb całkowitych mogą nie przekraczać granicy 32-bitowej.

Konflikty z kompilatorem x86

Typy danych, które są większe niż 4 bajty, nie są automatycznie wyrównane na stosie, gdy kompilujesz aplikację przy użyciu kompilatora x86. Ponieważ architektura kompilatora x86 jest stosem wyrównanym do 4 bajtów, cokolwiek większego niż 4 bajty, na przykład 64-bitowa liczba całkowita, nie może być automatycznie wyrównane do adresu 8-bajtowego.

Praca z danymi niewyrównanymi ma dwie implikacje.

  • Uzyskanie dostępu do niezsynchronizowanych lokalizacji może potrwać dłużej niż uzyskanie dostępu do wyrównanych lokalizacji.

  • Nie można używać nieprzystawionych lokalizacji w operacjach połączonych.

Jeśli potrzebujesz bardziej ścisłego wyrównania, użyj __declspec(align(N)) w deklaracjach zmiennych. To powoduje, że kompilator dynamicznie wyrównuje stos zgodnie z określonymi wymaganiami. Jednak dynamiczne dostosowywanie stosu w czasie wykonywania może spowodować wolniejsze wykonywanie aplikacji.

Użycie rejestrów x64

Architektura x64 zapewnia 16 rejestrów ogólnego przeznaczenia (określanych tutaj jako rejestry całkowite), a także 16 rejestrów XMM/YMM dostępnych do użytku zmiennoprzecinkowego. Rejestry lotne to rejestry tymczasowe, które są zakładane jako zmienione przez obiekt wywołujący podczas wywołania. Rejestry nieulotne są wymagane do zachowania ich wartości podczas wywołania funkcji i muszą zostać zapisane przez wywoływaną funkcję, jeśli są używane.

Rejestrowanie zmienności i ochrona

W poniższej tabeli opisano sposób użycia każdego rejestru między wywołaniami funkcji:

Zarejestruj Stan Użycie
RAX Zmienny Rejestr wartości zwracanych
RCX Zmienny Pierwszy argument liczby całkowitej
RDX Zmienny Drugi argument liczby całkowitej
R8 Zmienny Trzeci argument liczby całkowitej
R9 Zmienny Czwarty argument liczby całkowitej
R10:R11 Zmienny Należy zachować zgodnie z potrzebami podmiotu wywołującego; używane w instrukcjach syscall/sysret
R12:R15 Nieulotna Musi być zachowane przez wykonawcę
RDI Nieulotna Musi być zachowane przez wykonawcę
RSI Nieulotna Musi być zachowane przez wykonawcę
RBX Nieulotna Musi być zachowane przez wykonawcę
RBP Nieulotna Może być używany jako wskaźnik ramki; muszą być zachowywane przez obiekt wywoływany
RSP Nieulotna Wskaźnik stosu
XMM0, YMM0 Zmienny Pierwszy argument FP; pierwszy argument typu wektorowego, gdy __vectorcall jest używany
XMM1, YMM1 Zmienny Drugi argument FP; drugi argument typu wektora, gdy __vectorcall jest używany
XMM2, YMM2 Zmienny Trzeci argument FP; trzeci argument typu wektorowego, gdy __vectorcall jest używany
XMM3, YMM3 Zmienny Czwarty argument FP; czwarty argument typu wektora, gdy __vectorcall jest używany
XMM4, YMM4 Zmienny Należy zachować według potrzeb przez wywołującego; piąty argument typu wektora, gdy __vectorcall jest używany
XMM5, YMM5 Zmienny Musi zostać zachowane zgodnie z potrzebami wywołującego; szósty argument typu wektorowego, gdy używany jest __vectorcall.
XMM6:XMM15, YMM6:YMM15 Nieulotny (XMM), Ulotny (górna połowa YMM) Należy zachować przez wywoływanie. Rejestry YMM muszą być zachowywane zgodnie z potrzebami wywołującego.

Po zakończeniu działania funkcji i wpisie funkcji do wywołań biblioteki środowiska uruchomieniowego języka C i wywołaniach systemu Windows należy wyczyścić flagę kierunku w rejestrze flag procesora CPU.

Użycie stosu

Aby uzyskać szczegółowe informacje na temat alokacji stosu, wyrównania, typów funkcji i ramek stosu w architekturze x64, zobacz zastosowanie stosu x64.

Prolog i epilog

Każda funkcja, która przydziela przestrzeń stosu, wywołuje inne funkcje, zapisuje rejestry nieulotne lub używa obsługi wyjątków, musi mieć prolog, którego limity adresowe są opisane w danych rozwijania stosu skojarzonych z odpowiednim wpisem tabeli funkcji, oraz epilog przy każdym wyjściu z funkcji. Aby uzyskać szczegółowe informacje na temat wymaganego kodu prologu i epilogu na x64, zobacz x64 prolog i epilog.

Obsługa wyjątku x64

Aby uzyskać informacje na temat konwencji i struktur danych używanych do implementowania obsługi wyjątków strukturalnych i obsługi wyjątków języka C++ w architekturze x64, zobacz obsługa wyjątków x64.

Funkcje wewnętrzne i wbudowany zestaw

Jednym z ograniczeń kompilatora x64 jest brak wbudowanej obsługi asemblera. Oznacza to, że funkcje, których nie można zapisać w języku C lub C++, muszą być zapisywane jako podroutines lub jako funkcje wewnętrzne obsługiwane przez kompilator. Niektóre funkcje są wrażliwe na wydajność, a inne nie. Funkcje wrażliwe na wydajność powinny być implementowane jako funkcje wewnętrzne.

Funkcje wewnętrzne obsługiwane przez kompilator są opisane w temacie Funkcje wewnętrzne kompilatora.

Format obrazu x64

Format obrazu wykonywalnego x64 to PE32+. Obrazy wykonywalne (zarówno biblioteki DLL, jak i pliki EXE) są ograniczone do maksymalnego rozmiaru 2 gigabajtów, więc względne adresowanie z przemieszczeniem 32-bitowym może służyć do adresowania danych statycznych obrazów. Te dane obejmują tabelę adresów importu, stałe ciągów, statyczne dane globalne itd.

Zobacz też

Konwencje wywoływania