x64 çağırma kuralı

Bu makalede, x64 kodunda bir işlevin (çağıran) başka bir işleve (çağıran) çağrı yapmak için kullandığı standart işlemler ve kurallar açıklanmaktadır.

Çağırma kuralı hakkında __vectorcall daha fazla bilgi için bkz. __vectorcall.
Çağırma kuralı hakkında daha fazla bilgi için __preserve_none, __preserve_none'e bakınız.

Çağırma kuralı varsayılanları

x64 Uygulama İkili Arabirimi (ABI), varsayılan olarak dört yazmaçlı, hızlı çağrı çağrı kuralı kullanır. Çağrı yığınında, çağrılanlar için bu yazmaçları kaydetmek amacıyla bir gölge depo olarak alan ayrılır.

İşlev çağrısının bağımsız değişkenleri ile bu bağımsız değişkenler için kullanılan yazmaçlar arasında sıkı bir birebir eşleşme vardır. 8 bayta sığmayan veya 1, 2, 4 veya 8 bayt olmayan bağımsız değişkenler başvuruyla geçirilmelidir. Tek bir bağımsız değişken hiçbir zaman birden çok yazmaç arasında dağıtılmaz.

x87 yazmaç yığını kullanılmamış. Çağrılan tarafından kullanılabilir, ancak işlev çağrıları arasında değişken olduğunu göz önünde bulundurun. Tüm kayan nokta işlemleri 16 XMM yazmaç kullanılarak yapılır.

Tamsayı argümanları RCX, RDX, R8 ve R9 yazmaçlarında aktarılır. Kayan noktalı argümanlar XMM0L, XMM1L, XMM2L ve XMM3L içinde iletilir. 16 baytlık argümanlar referansla geçirilir. Parametre geçirme, Parametre geçirme bölümünde ayrıntılı olarak açıklanmıştır. Bu yazmaçlar ve RAX, R10, R11, XMM4ve XMM5, geçici olarak kabul edilir veya dönüşte bir arayan tarafından potansiyel olarak değiştirilebilir. Kayıt kullanımı, x64 yazmaç kullanımı ve Çağıran/çağırılan kayıtlı yazmaçlar bölümünde ayrıntılı olarak belgelenmiştir.

Prototiplenen işlevler için, tüm bağımsız değişkenler geçirilmeden önce beklenen çağıran türlerine dönüştürülür. Çağıran, çağırılanın parametreleri için alan ayırmaktan sorumludur. Çağıran kişi bu kadar çok parametre almasa bile, çağıranın dört yazmaç parametresini depolamak için her zaman yeterli alan ayırması gerekir. Bu kural, hazırlanmamış C dili işlevleri ve vararg C/C++ işlevleri için desteği basitleştirir. Vararg veya unprototyped işlevleri için, tüm kayan nokta değerleri ilgili genel amaçlı yazmaçta çoğaltılmalıdır. İlk dört parametreden sonraki tüm diğer parametreler, çağrıdan önce shadow store'dan sonra stack'te depolanmalıdır. Vararg işlev ayrıntıları Varargs'da bulunabilir. Prototiplenmemiş fonksiyonlar hakkında bilgiler Prototiplenmemiş fonksiyonlar bölümünde detaylandırılmıştır.

Hizalama

Yapıların çoğu doğal hizalamalarına göre hizalanır. Birincil istisnalar, performansa yardımcı olmak için 16 bayt hizalı olan yığın işaretçisi ve malloc veya alloca belleğidir. 16 bayt'ın üzerinde hizalama el ile yapılmalıdır. 16 bayt, XMM işlemleri için ortak bir hizalama boyutu olduğundan, bu değer çoğu kod için işe yaramalıdır. Yapı düzeni ve hizalama hakkında daha fazla bilgi için bkz . x64 türü ve depolama düzeni. Yığın düzeni hakkında bilgi için bkz . x64 yığın kullanımı.

Geri sarılabilirlik

Yaprak işlevler, geçici olmayan kayıtları değiştirmeyen işlevlerdir. Yaprak olmayan bir işlev, örneğin başka bir işlev çağırarak, kalıcı RSP değerini değiştirebilir. Alternatif olarak, yerel değişkenler için daha fazla yığın alanı ayırarak da değişebilir RSP . Bir özel durum işlendiğinde kalıcı kayıtları kurtarmak için, yaprak olmayan işlevlere statik verilerle açıklama eklenir. Veriler, rastgele bir yönergede işlevin nasıl düzgün bir şekilde geri sarıldığı açıklanır. Bu veriler pdata veya yordam verileri olarak depolanır ve bu da özel durum işleme verileri olan xdata'ya başvurur. xdata, geri alma bilgilerini içerir ve ek PData veya özel durum işleyici işlevine işaret edebilir.

Prologlar ve epiloglar, xdata'da düzgün bir şekilde açıklanabilmeleri için sıkı bir şekilde kısıtlanmıştır. Yığın işaretçisi, yaprak fonksiyonlar hariç, epilog veya prologun parçası olmayan herhangi bir kod bölgesinde 16 bayt hizalanmış olarak kalmalıdır. Yaprak işlevleri yalnızca bir dönüş simülasyonu yaparak çözülebilir, bu nedenle pdata ve xdata gerekli değildir. İşlev prologlarının ve epiloglarının düzgün yapısı hakkında ayrıntılı bilgi için x64 prolog ve epilog bölümüne bakın. Özel durum işlemenin yanı sıra pdata ve xdata'nın geri alınması hakkında daha fazla bilgi için bkz. x64 özel durum işleme.

Parametre geçirme

Varsayılan olarak, x64 çağırma kuralı ilk dört bağımsız değişkeni yazmaçlardaki bir işleve geçirir. Bu bağımsız değişkenler için kullanılan yazmaçlar, bağımsız değişkenin konumuna ve türüne bağlıdır. Kalan bağımsız değişkenler sağdan sola sırasıyla yığına geçirilir. Çağıran gerekli yığın alanını ayırır ve depolama veya taşıma yönergelerini kullanarak her bağımsız değişken için 8 baytlık hizalamayı koruyarak bu bağımsız değişkenleri yığın belleğine yazar.

En soldaki dört konumdaki tamsayı değerli argümanlar, soldan sağa sırayla, sırasıyla RCX, RDX, R8 ve R9 üzerinden geçirilir. Beşinci ve daha yüksek bağımsız değişkenler daha önce açıklandığı gibi yığına geçirilir. Yazmaçlardaki tüm tamsayı bağımsız değişkenleri sağa yaslandığından, çağrılan yazmaçların üst bitlerini yoksayabilir ve yalnızca gerekli yazmaç bölümüne erişebilir.

İlk dört parametre içindeki kayan noktalı ve çift duyarlıklı argümanlar, konumlarına bağlı olarak XMM0, - veya XMM3 aracılığıyla iletilir. Kayan noktalı değerler, yalnızca varargs argümanları olduğunda RCX, RDX, R8 ve R9 tamsayı yazmaçlarına yerleştirilir. Ayrıntılar için bkz . Varargs. Benzer şekilde, ilgili argüman bir tamsayı veya işaretçi türünde olduğunda XMM0 - XMM3 yazmaçları yok sayılır.

__m128 türleri, diziler ve dizeler hiçbir zaman doğrudan değerle geçirilmez. Bunun yerine, çağıran tarafından ayrılan belleğe bir işaretçi geçirilir. Boyutu 8, 16, 32 veya 64 bit olan yapılar ve birleşimler ile __m64 türler, aynı boyuttaki tamsayılar gibi aktarılır. Başka boyutlardaki yapılar veya birleşimler, çağıran tarafından tahsis edilen belleğe bir işaretçi olarak aktarılır. Çağıran tarafından ayrılan geçici bellek, __m128 dahil, bir işaretçi olarak geçirilen bu toplama türleri için 16 bayt hizalanmış olmalıdır.

İçsel işlevler, yığın alanı ayırmazlar ve diğer işlevleri çağırmazlar. Bazen başka yazmaç bağımsız değişkenlerini geçirmek için diğer geçici yazmaçları kullanırlar. Bu iyileştirme, derleyici ile iç işlev uygulaması arasındaki sıkı bağlama ile mümkün hale gelir.

Çağıran, gerekirse yazmaç parametrelerinin gölge alanına dökümünü almaktan sorumludur.

Aşağıdaki tablo, parametrelerin türe ve soldan konuma göre nasıl geçirildiğini özetler:

Parametre türü beşinci ve üzeri dördüncü üçüncü saniye En soldaki
kayan nokta yığın XMM3 XMM2 XMM1 XMM0
tam sayı yığın R9 R8 RDX RCX
Toplamalar (8, 16, 32 veya 64 bit) ve __m64 yığın R9 R8 RDX RCX
İşaretçi olarak diğer kümeler yığın R9 R8 RDX RCX
__m128, işaretçi olarak yığın R9 R8 RDX RCX

Argüman geçirme örneği 1 - tüm tamsayılar

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

2 - tüm kayan değerleri geçiren bağımsız değişken örneği

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

Karışık int ve float'ları içeren bağımsız değişken örneği 3

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

4 - __m64, __m128ve toplamlarını geçiren bağımsız değişken örneği

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

Parametreler değişken sayıda bağımsız değişken (örneğin, üç nokta operatörü) aracılığıyla geçirilirse, normal kayıt parametresi geçirme kuralı uygulanır. Bu kural, beşinci ve sonraki bağımsız değişkenleri yığına aktarmayı içerir. Adresinin alındığı bağımsız değişkenlerin dökümünü almak çağırılanın sorumluluğundadır. Yalnızca kayan nokta değerleri için, çağıranın tamsayı kayıtlarındaki değeri beklemesi durumunda hem tamsayı yazmaç hem de kayan nokta yazmaç değeri içermelidir.

Prototipsiz işlevler

Tam prototipi olmayan işlevler için çağıran tamsayı değerlerini tamsayı, kayan nokta değerlerini ise çift hassasiyet olarak geçirir. Yalnızca kayan nokta değerleri için, çağıranın tamsayı kayıtlarındaki değeri beklemesi durumunda hem tamsayı yazmaç hem de kayan nokta yazmaç float değerini içerir.

func1();
func2() {   // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
   func1(2, 1.0, 7);
}

Dönüş değerleri

__m64 türü de dahil olmak üzere 64 bite sığabilen skaler bir dönüş değeri, RAX aracılığıyla döndürülür. Float, double ve __m128, __m128i, __m128d gibi vektör türleri dahil olmak üzere skaler olmayan türler, XMM0 içinde döndürülür. veya RAX içinde döndürülen XMM0 değerdeki kullanılmayan bitlerin durumu tanımlanmamış.

Kullanıcı tanımlı türler, genel işlevlerden ve statik üye işlevlerinden değerle döndürülebilir. içindeki RAXdeğere göre kullanıcı tanımlı bir tür döndürmek için uzunluğu 1, 2, 4, 8, 16, 32 veya 64 bit olmalıdır. Ayrıca kullanıcı tanımlı oluşturucu, yıkıcı veya kopyalama atama işlecine sahip olmamalıdır. Özel veya korumalı statik olmayan veri üyelerine ve başvuru türüne ait statik olmayan veri üyelerine sahip olamaz. Temel sınıflara veya sanal işlevlere sahip olamaz. Ayrıca yalnızca bu gereksinimleri karşılayan veri üyelerine de sahip olabilir. Bu tanım temelde C++03 POD türüyle aynıdır. C++11 standardında tanım değiştiğinden, bu test için kullanılması std::is_pod önerilmez. Aksi takdirde, çağıranın önce dönüş değeri için bellek ayırması ve ardından ilk bağımsız değişken olarak buna bir işaretçi geçirmesi gerekir. Kalan bağımsız değişkenler daha sonra bir bağımsız değişken sağa kaydırılır. Aynı işaretçi, çağrılan tarafından RAX içinde geri döndürülmelidir.

Bu örnekler, belirtilen bildirimlere sahip işlevler için parametrelerin ve dönüş değerlerinin nasıl geçirildiğini gösterir:

1 - 64 bit sonuç dönüş değeri örneği

__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.

2 - 128 bit sonuç dönüş değeri örneği

__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.

3 dönüş değeri örneği - işaretçiye göre kullanıcı türü sonucu

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.

4 dönüş değeri örneği - değere göre kullanıcı türü sonucu

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.

Arayan/çağıran kayıtlı yazmaçlar

x64 ABI, RAX, RCX, RDX, R8, R9, R10, R11 ve XMM0-XMM5 yazmaçlarını geçici olarak kabul eder. Bulunduklarında, YMM0-YMM15 ve ZMM0-ZMM15 öğelerinin üst kısımları da uçucudur. AVX512VL'de, ZMM, YMM ve XMM yazmaçları 16-31 de geçicidir. AMX desteği mevcut olduğunda kutucuk TMM yazmaçları geçicidir. Tüm program optimizasyonu gibi analizlerle güvenliğinin kanıtlanamadığı sürece, işlev çağrılarında yok edilen geçici yazmaçları dikkate alın.

x64 ABI, RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 ve XMM6-XMM15 kayıtlarını geçici olmayan kayıtlar olarak kabul eder. Bunları kullanan bir işlev tarafından kaydedilmesi ve geri yüklenmesi gerekir.

APX desteği mevcut olduğunda, yazmaçlar R16-R29 geçicidir. R30 ve R31 kalıcı değildir.

İşlev işaretçileri

İşlev işaretçileri yalnızca ilgili işlevin etiketine yönelik işaretçilerdir. İşlev işaretçileri için içindekiler tablosu gereksinimi yoktur.

Eski kod için kayan nokta desteği

MMX ve kayan nokta yığını yazmaçları (MM0-MM7/ST0-ST7) bağlam değiştirmeleri boyunca korunur. Bu yazmaçlar için belirlenmiş bir çağırma kuralı yoktur. Bu yazmaçların çekirdek modu kodunda kullanılması kesinlikle yasaktır.

FPCSR

Yazmaç durumu, x87 FPU denetim sözcüğünü de içerir. Çağrı kuralı bu kaydın değişken olmamasını gerektirir.

x87 FPU kontrol kelime yazmacı, programın yürütülmesinin başlangıcında, aşağıdaki standart değerler kullanılarak ayarlanır.

Kayıt[bitler] Ayar
FPCSR\[0:6] İstisna maskesi tüm 1'leri gizler (tüm istisnalar gizlenmiştir)
FPCSR\[7] Ayrılmış - 0
FPCSR\[8:9] Hassasiyet Kontrolü - 10B (çift hassasiyet)
FPCSR\[10:11] Yuvarlama kontrolü - 0 (en yakına yuvarlama)
FPCSR\[12] Sonsuzluk denetimi - 0 (kullanılmaz)

içindeki FPCSR alanlardan herhangi birini değiştiren bir çağıranın, çağırana geri dönmeden önce bunları geri yüklemesi gerekir. Ayrıca, bu alanlardan herhangi birini değiştiren bir çağıranın çağıranı çağırmadan önce standart değerlerine geri yüklemesi gerekir, ancak sözleşme gereği arayan değiştirilen değerleri beklemez.

Denetim bayraklarının kalıcı olmamasıyla ilgili kurallarda iki özel durum vardır:

  • Verilen işlevin belgelenmiş amacının uçucu olmayan FPCSR bayraklarını değiştirmek olduğu işlevlerde.

  • Kesin olarak doğru olduğu durumlarda, bu kuralların ihlali, (örneğin, tüm program analizi aracılığıyla) kuralları ihlal etmeyen bir programla aynı şekilde davranan bir programla sonuçlanır.

Kalıcı olmadığı düşünülse de, kaydedildiği yeri ve nereden geri yüklenmesi gerektiğini açıklayan statik geri sarma tanımlayıcısı yoktur. FPCSR öğesini değiştiren özel durum güvenli kod, yığın çözülürken onu açıkça geri yüklemek için bir özel durum sonlandırıcısına (örneğin C++ yıkıcısı veya bir __finally yan tümcesi) başvurmalıdır.

MXCSR

Kayıt durumu ayrıca MXCSR içerir. Çağırma kuralı bu kaydı geçici bir bölüme ve geçici olmayan bir bölüme böler. Uçucu bölüm, MXCSR\[0:5] içindeki altı durum bayrağından oluşurken, yazmacın geri kalanı olan MXCSR\[6:15] uçucu olmayan olarak kabul edilir.

Geçici olmayan bölüm, program yürütmenin başlangıcında aşağıdaki standart değerlere ayarlanır:

Kayıt[bitler] Ayar
MXCSR\[6] Denormaller sıfır ile temsil edilir - 0
MXCSR\[7:12] İstisna maskesi tüm 1'leri gizler (tüm istisnalar gizlenmiştir)
MXCSR\[13:14] Yuvarlama kontrolü - 0 (en yakına yuvarlama)
MXCSR\[15] Maskelenmiş alt akış için sıfıra boşalt - 0 (kapalı)

içindeki MXCSR geçici olmayan alanlardan herhangi birini değiştiren bir çağıranın, çağırana geri dönmeden önce bunları geri yüklemesi gerekir. Ayrıca, bu alanlardan herhangi birini değiştiren bir çağıranın çağıranı çağırmadan önce standart değerlerine geri yüklemesi gerekir, ancak sözleşme gereği arayan değiştirilen değerleri beklemez.

Denetim bayraklarının kalıcı olmamasıyla ilgili kurallarda iki özel durum vardır:

  • Söz konusu işlevin belgelenmiş amacının uçucu olmayan MXCSR bayraklarını değiştirmek olduğu işlevlerde.

  • Kesin olarak doğru olduğu durumlarda, bu kuralların ihlali, (örneğin, tüm program analizi aracılığıyla) kuralları ihlal etmeyen bir programla aynı şekilde davranan bir programla sonuçlanır.

İşlev belgelerinde açıkça belirtilmedikçe, bir işlev sınırı boyunca MXCSR yazmacının uçucu bölümünün durumu hakkında hiçbir varsayımda bulunmayın.

MXCSR’in bazı bölümleri uçucu olmayan olarak kabul edilse de, bunların nereye kaydedildiğini ve nereden geri yükleneceğini açıklayan statik bir unwind tanımlayıcısı yoktur. < c0 /> öğesinin kalıcı olmayan kısımlarını değiştiren istisna güvenli kod, yığın çözülürken bunu açıkça geri yüklemek için bir istisna sonlandırıcısı (örneğin, C++ yıkıcısı veya yan tümcesi) kullanmalıdır.

setjmp/longjmp

setjmpex.h veya setjmp.h eklediğinizde, setjmp veya longjmp öğesine yapılan tüm çağrılar, destructor ve __finally çağrılarını tetikleyen bir geri sarmayla sonuçlanır. Bu davranış, setjmp.h eklendiği zaman __finally yan tümcelerin ve yıkıcıların çağrılmadığı x86'dan farklıdır.

setjmp çağrısı, mevcut yığın işaretçisini, kalıcı yazmaçları ve MXCSR yazmaçlarını korur. longjmp çağrıları, en son setjmp çağrı sitesine döner ve yığın işaretçisini, kalıcı yazmaçları ve MXCSR yazmaçlarını, en son setjmp çağrısı tarafından korunmuş durumuna geri sıfırlar.

APX destekleniyorsa, R30 ve R31, setjmp çağrıldığı noktadan nihayetinde longjmp ile sonuçlanan çağrının yapıldığı noktaya kadar bir işlev içinde değiştirilmemelidir. Bu sınırlama, R30 ve R31 öğelerinin jmp_buf öğesinin bir parçası olarak kaydedilmemesinden kaynaklanır; bu yapı tanımı değiştirilemez. Bunun yerine, geri sarma mekanizması aracılığıyla geri yüklenirler. Aşağıdaki örnek, verilerin nasıl geri yükleneceğiyle ilgili farkın bu kısıtlamayı nasıl etkilediğini gösterir:

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);

    ...
}

Bu örnekte, R30 değerinin, setjmp çağrıldığı noktadan function_b çağrıldığı noktaya kadar değiştiği görülür. function_b içinde, longjmp, setjmp öğesini çağıran işleve (function_a bu durumda) ulaşana kadar çağrı yığınını geri sarar. R30 için geri yüklenen değer, function_b çağrıldığı noktadaki değer olan 20 olacaktır; setjmp çağrıldığı noktadaki değer olan 10 değil. Bu, setjmp ikinci kez geri döndüğünde (longjmp sonucunda), R30 değerinin 10 yerine 20 olarak ayarlanacağı anlamına gelir; bu yanlıştır. Bu nedenle derleyiciler, setjmp çağrıldığı andan itibaren işlev içinde sonunda longjmp'ün çağrılmasına yol açabilecek son noktaya kadar R30 ile R31'in sabit kalmasını sağlamalıdır.

longjmp, bir özel durum filtresinden (yalnızca bir alt yordamdan değil) çağrılabildiğinden, bu da fiilen setjmp çağrıldığı andan işlevin geri kalanına kadar R30 ve R31'nin sabit kalması gerektiği anlamına gelir.

Ayrıca bkz.