Aracılığıyla paylaş


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

RcX, RDX, R8 ve R9 yazmaçlarında tamsayı bağımsız değişkenleri geçirilir. Kayan nokta bağımsız değişkenleri XMM0L, XMM1L, XMM2L ve XMM3L'de aktarılır. 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, XMM4 ve XMM5, volatil olarak kabul edilir veya dönüşte çağrıyı alan kişi 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 bir işlev çağırarak değişken olmayan RSP'yi değiştirebilir. Alternatif olarak, yerel değişkenler için daha fazla yığın alanı ayırarak RSP'yi değiştirebilir. 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 bağımsız değişkenler sırasıyla RCX, RDX, R8 ve R9'da soldan sağa sırasıyla 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 parametredeki kayan nokta ve çift duyarlıklı bağımsız değişkenler, konuma bağlı olarak XMM0 - XMM3 arasında geçirilir. Kayan nokta değerleri yalnızca varargs bağımsız değişkenleri olduğunda RCX, RDX, R8 ve R9 tamsayı yazmaçlarına yerleştirilir. Ayrıntılar için bkz . Varargs. Benzer şekilde, karşılık gelen bağımsız değişken bir tamsayı veya işaretçi türü olduğunda XMM0 - XMM3 yazmaçları yoksayı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

64 bite sığabilen, __m64 türü de dahil bir skaler dönüş değeri RAX aracılığıyla döndürülür. Nonscalar türler, float'lar, double'lar ve __m128, __m128i, __m128d gibi vektör türleri dahil olmak üzere XMM0'da döndürülür. RAX veya XMM0'de döndürülen değerde 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. RAX'ta değ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 RAX'ta çağıran tarafından 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. Mevcut olduğunda, YMM0-YMM15 ve ZMM0-ZMM15'in üst kısımları da geçicidir. AVX512VL'da ZMM, YMM ve XMM 16-31 yazmaçları da geçicidir. AMX desteği mevcut olduğunda, TMM kutucuk 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ı değişken olmayan olarak kabul eder. Bunları kullanan bir işlev tarafından kaydedilmesi ve geri yüklenmesi gerekir.

İş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şiklikleri sırasında 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)

FPCSR içindeki 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 belgelenen amacının kalıcı 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.

MXCSR

Kayıt durumu MXCSR'yi de içerir. Çağırma kuralı bu kaydı geçici bir bölüme ve geçici olmayan bir bölüme böler. Geçici bölüm, MXCSR'de altı durum bayrağından oluşur[0:5], yazmacın geri kalanı ise MXCSR[6:15], geçici değil 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 tüm 1'leri maskeler (tüm istisnalar maskelenmiş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ı)

MXCSR içindeki 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:

  • Verilen işlevin belgelenen amacının kalıcı 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 belgesinde açıkça açıklanmadığı sürece, bir işlev sınırı boyunca MXCSR yazmacının geçici bölüm durumu hakkında hiçbir varsayımda bulunmayın.

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.

Geçerli yığın işaretçisini, kalıcı olmayan yazmaçları ve MXCSR yazmaçlarını korumaya yönelik bir çağrı setjmp . En son setjmp çağrı alanına dönen longjmp çağrıları, en son setjmp çağrısı tarafından korunmuş durumuna uygun olarak, yığın işaretçisini, kalıcı olmayan yazmaçları ve MXCSR yazmaçlarını sıfırlar.

Ayrıca bkz.