ARM64 özel durum işleme
ARM64 üzerinde Windows, zaman uyumsuz donanım tarafından oluşturulan özel durumlar ve zaman uyumlu yazılım tarafından oluşturulan özel durumlar için aynı yapılandırılmış özel durum işleme mekanizmasını kullanır. Dile özgü özel durum işleyicileri, dil yardımcı işlevleri kullanılarak Windows yapılandırılmış özel durum işleme üzerine oluşturulur. Bu belgede ARM64 üzerinde Windows'ta özel durum işleme açıklanmaktadır. Microsoft ARM derleyicisi ve MSVC derleyicisi tarafından oluşturulan kod tarafından kullanılan dil yardımcılarını gösterir.
Hedefler ve motivasyon
Veri geri sarma kurallarını ve bu açıklamayı kaldırma özel durumu şunlara yöneliktir:
Her durumda kod yoklama olmadan geri sarmaya izin vermek için yeterli açıklama sağlayın.
Kodun çözümlenmesi için kodun sayfalanması gerekir. Yararlı olduğu bazı durumlarda (izleme, örnekleme, hata ayıklama) geri sarmayı önler.
Kodu analiz etmek karmaşıktır; derleyicisi, yalnızca unwinder'ın kodunu çözebileceği yönergeleri oluşturmaya dikkat etmelidir.
Geri alma işlemi, geri alma kodları kullanılarak tam olarak açıklanamazsa, bazı durumlarda yönerge kod çözme işlemine geri dönmesi gerekir. Yönerge kod çözme genel karmaşıklığı artırır ve ideal olarak kaçınılmalıdır.
Ön izlemenin ortasında ve ortasında geri sarmayı destekler.
- Windows'ta geri sarma, özel durum işlemeden daha fazlası için kullanılır. Bir giriş veya kapsam kod dizisinin ortasındayken bile kodun doğru şekilde geri alabilmesi kritik önem taşır.
Çok az yer kaplar.
İkili boyutu önemli ölçüde artırmak için geri sarma kodları toplanmamalıdır.
Geri sarma kodlarının bellekte kilitlenme olasılığı yüksek olduğundan, küçük bir ayak izi, yüklenen her ikili dosya için minimum ek yük sağlar.
Varsayımlar
Bu varsayımlar özel durum işleme açıklamasında yapılır:
Prolog'lar ve epiloglar birbirini yansıtma eğilimindedir. Bu ortak özelliğin avantajlarından yararlanarak, geri almayı açıklamak için gereken meta verilerin boyutu büyük ölçüde azaltılabilir. İşlevin gövdesinde, prolog işlemlerinin geri alınıp alınmadığı veya epilog işlemlerinin ileriye doğru yapılması önemli değildir. Her ikisi de aynı sonuçları üretmelidir.
İşlevler bütünde nispeten küçük olma eğilimindedir. Alan için çeşitli iyileştirmeler, en verimli veri paketlemeyi elde etmek için bu olguyu kullanır.
Kapsamlarda koşullu kod yoktur.
Ayrılmış çerçeve işaretçisi yazmaç: öğesi girişteki başka bir yazmaçta (
x29
) kaydedilirsesp
, bu kayıt işlevin tamamında dokunulmaz kalır. Bu, orijinalininsp
herhangi bir zamanda kurtarılabileceği anlamına gelir.sp
başka bir kayıtta kaydedilmediği sürece, yığın işaretçisinin tüm işlemesi kesinlikle giriş ve kapsam içinde gerçekleşir.Yığın çerçevesi düzeni, sonraki bölümde açıklandığı gibi düzenlenmiştir.
ARM64 yığın çerçevesi düzeni
Çerçeve zincirleme işlevler için ve lr
çifti, fp
iyileştirme konularına bağlı olarak yerel değişken alanındaki herhangi bir konumda kaydedilebilir. Amaç, çerçeve işaretçisine () veya yığınsp
işaretçisine (x29
) göre tek bir yönergeyle ulaşabileceğiniz yerel ayarların sayısını en üst düzeye çıkarmaktır. Ancak işlevler için alloca
zincirlenmiş x29
ve yığının alt kısmına işaret etmelidir. Kayıt-çifti adresleme modu kapsamının daha iyi olmasını sağlamak için, kalıcı olmayan yazmaç kaydetme alanları Yerel alan yığınının en üstüne yerleştirilir. En verimli prolog dizilerinden birkaçını gösteren örnekler aşağıda verilmiştir. Netlik ve daha iyi önbellek yerelliği için, çağrıya kayıtlı yazmaçları tüm kurallı girişlerde depolama sırası "büyüyen" sıradadır. #framesz
aşağıda yığının tamamının boyutu (alan hariç) alloca
temsil eder. #localsz
ve #outsz
sırasıyla yerel alan boyutunu (çiftin kaydetme alanı <x29, lr>
dahil) ve giden parametre boyutunu belirtir.
Zincirlenmiş, #localsz <= 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] stp x29,lr,[sp,#-localsz]! // save <x29,lr> at bottom of local area mov x29,sp // x29 points to bottom of local sub sp,sp,#outsz // (optional for #outsz != 0)
Zincirli, #localsz > 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] sub sp,sp,#(localsz+outsz) // allocate remaining frame stp x29,lr,[sp,#outsz] // save <x29,lr> at bottom of local area add x29,sp,#outsz // setup x29 points to bottom of local area
Zincirsiz, yaprak işlevleri (
lr
kaydedilmemiş)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] str x23,[sp,#32] stp d8,d9,[sp,#40] // save FP regs (optional) stp d10,d11,[sp,#56] sub sp,sp,#(framesz-80) // allocate the remaining local area
Tüm yerel öğelere temelinde
sp
erişilir.<x29,lr>
önceki çerçeveyi gösterir. Çerçeve boyutu <= 512 için,sub sp, ...
kaydedilen kayıt defterleri alanı yığının altına taşınırsa iyileştirilebilir. Dezavantajı, yukarıdaki diğer düzenlerle tutarlı olmamasıdır. Ayrıca kaydedilen kayıt defterleri, çift kayıt defterleri ile önceden ve sonra dizine alınan uzaklık adresleme modu aralığının bir parçası olur.Zincirsiz, yapraksız işlevler (Int kaydedilmiş alanında kaydeder
lr
)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... stp x23,lr,[sp,#32] // save last Int reg and lr stp d8,d9,[sp,#48] // save FP reg-pair (optional) stp d10,d11,[sp,#64] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Alternatif olarak, kayıtlı çift sayıyla Int yazmaçları,
stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... str lr,[sp,#32] // save lr stp d8,d9,[sp,#40] // save FP reg-pair (optional) stp d10,d11,[sp,#56] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Yalnızca
x19
kaydedildi:sub sp,sp,#16 // reg save area allocation* stp x19,lr,[sp] // save x19, lr sub sp,sp,#(framesz-16) // allocate the remaining local area
* Önceden dizinlenmiş
stp
bir reg-lrstp
geri sarma kodlarıyla temsil edilemediğinden kayıt defteri kaydetme alanı ayırma içine katlanmamıştır.Tüm yerel öğelere temelinde
sp
erişilir.<x29>
önceki çerçeveyi gösterir.Zincirli, #framesz <= 512, #outsz = 0
stp x29,lr,[sp,#-framesz]! // pre-indexed, save <x29,lr> mov x29,sp // x29 points to bottom of stack stp x19,x20,[sp,#(framesz-32)] // save INT pair stp d8,d9,[sp,#(framesz-16)] // save FP pair
Yukarıdaki ilk prolog örneğine kıyasla, bu örneğin bir avantajı vardır: tüm yazmaç kaydetme yönergeleri yalnızca bir yığın ayırma yönergesi sonrasında yürütülmeye hazırdır. Bu, yönerge düzeyi paralelliğini önleyen bir bağımlılık karşıtlığı
sp
olmadığı anlamına gelir.Zincirli, çerçeve boyutu > 512 (olmayan
alloca
işlevler için isteğe bağlı)stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area sub sp,sp,#(framesz-80) // allocate the remaining local area
İyileştirme amacıyla,
x29
"reg-pair" ve pre-/post-indexed offset adresleme modu için daha iyi bir kapsam sağlamak üzere yerel alanda herhangi bir konuma yerleştirilebilir. Çerçeve işaretçilerinin altındaki yerel ayarlara temelsp
alınarak erişilebilir.Zincirli, çerçeve boyutu > 4K, alloca() ile veya olmadan,
stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area mov x15,#(framesz/16) bl __chkstk sub sp,sp,x15,lsl#4 // allocate remaining frame // end of prolog ... sub sp,sp,#alloca // more alloca() in body ... // beginning of epilog mov sp,x29 // sp points to top of local area ldp d10,d11,[sp,#64] ... ldp x29,lr,[sp],#80 // post-indexed, reload <x29,lr>
ARM64 özel durum işleme bilgileri
.pdata
Kayıt
Kayıtlar .pdata
, pe ikilisindeki her yığın işleme işlevini açıklayan sıralı bir sabit uzunluklu öğe dizisidir. "Yığın işleme" ifadesi önemlidir: Yerel depolama gerektirmeyen ve geçici olmayan kayıtları kaydetmesi/geri yüklemesi gerekmeyen yaprak işlevler kayıt gerektirmez .pdata
. Alan kazanmak için bu kayıtlar açıkça atlanmalıdır. Bu işlevlerden birinin geri alınması, dönüş adresini doğrudan öğesinden lr
alıp çağırana kadar taşıyabilir.
ARM64 için her .pdata
kayıt 8 bayt uzunluğundadır. Her kaydın genel biçimi, işlevin 32 bit RVA'sını ilk sözcükte başlatır ve ardından değişken uzunlukta .xdata
bir bloğun işaretçisini içeren ikinci bir sözcük ya da kurallı işlevin geri sarma dizisini açıklayan paketlenmiş bir sözcük içerir.
Alanlar aşağıdaki gibidir:
İşlev Başlatma RVA'sı , işlevin başlangıcının 32 bit RVA'dır.
Bayrak , ikinci
.pdata
sözcüğün kalan 30 bitinin nasıl yorumlandığını gösteren 2 bitlik bir alandır. Bayrak 0 ise, kalan bitler bir Özel Durum Bilgileri RVA'sı oluşturur (en düşük iki bit örtük olarak 0 olur). Bayrak sıfır değilse, kalan bitler Paketlenmiş Geri Sarma verilerinin yapısını oluşturur.Özel Durum Bilgileri RVA , bölümünde depolanan değişken uzunluklu özel durum bilgisi yapısının
.xdata
adresidir. Bu verilerin 4 bayt hizalanmış olması gerekir.Paketlenmiş Geri Sarma Verileri , kurallı bir form varsayılarak işlevden geri sarmak için gereken işlemlerin sıkıştırılmış bir açıklamasıdır. Bu durumda kayıt
.xdata
gerekmez.
.xdata
Kayıt
Paketlenmiş geri sarma biçimi bir işlevin geri sarmasını açıklamak için yetersizse, değişken uzunlukta .xdata
bir kayıt oluşturulmalıdır. Bu kaydın adresi, kaydın ikinci sözcüğünde .pdata
depolanır. biçimi .xdata
, paketlenmiş değişken uzunlukta bir sözcük kümesidir:
Bu veriler dört bölüme ayrılır:
Yapının genel boyutunu açıklayan ve önemli işlev verileri sağlayan 1 sözcüklü veya 2 sözcüklü üst bilgi. İkinci sözcük yalnızca Hem Kapsam Sayısı hem de Kod Sözcükleri alanları 0 olarak ayarlandıysa bulunur. Üst bilgi şu bit alanlarına sahiptir:
a. İşlev Uzunluğu 18 bitlik bir alandır. İşlevin toplam uzunluğunu bayt cinsinden ve 4'e bölünerek gösterir. bir işlev 1M'den büyükse, işlevi tanımlamak için birden çok
.pdata
ve.xdata
kayıt kullanılmalıdır. Daha fazla bilgi için Büyük işlevler bölümüne bakın.b. Vers , 2 bitlik bir alandır. Kalan
.xdata
sürümünü açıklar. Şu anda yalnızca sürüm 0 tanımlanmıştır, bu nedenle 1-3 değerlerine izin verilmez.c. X , 1 bitlik bir alandır. Özel durum verilerinin varlığını (1) veya yokluğunu (0) gösterir.
d. E , 1 bitlik bir alandır. Tek bir epilogu açıklayan bilgilerin daha sonra (0) daha fazla kapsam sözcüğü gerektirmek yerine üst bilgide (1) paketlendiğini gösterir.
e. Epilog Count, E bitin durumuna bağlı olarak iki anlamı olan 5 bitlik bir alandır:
E 0 ise, bölüm 2'de açıklanan toplam kapsam kapsamlarının sayısını belirtir. İşlevde 31'den fazla kapsam varsa, uzantı sözcüğünün gerekli olduğunu belirtmek için Kod Sözcükleri alanının 0 olarak ayarlanması gerekir.
E 1 ise, bu alan ilk geri sarma kodunun dizinini belirtir ve bu da tek ve tek tanımlamayı açıklar.
f. Kod Sözcükleri , bölüm 3'teki tüm geri sarma kodlarını içermek için gereken 32 bit sözcük sayısını belirten 5 bitlik bir alandır. 31'den fazla sözcük (yani 124 geri sarma kodu) gerekiyorsa, uzantı sözcüğünün gerekli olduğunu belirtmek için bu alanın 0 olması gerekir.
r. Genişletilmiş Kapsam Sayısı ve Genişletilmiş Kod Sözcükleri sırasıyla 16 bit ve 8 bit alanlardır. Çok fazla sayıda epilog kodlamak için daha fazla alan veya çok fazla sayıda geri sarma kodu sözcüğü sağlar. Bu alanları içeren uzantı sözcüğü yalnızca ilk üst bilgi sözcüğündeki Hem Kapsam Sayısı hem de Kod Sözcükleri alanları 0 olduğunda bulunur.
Kapsam sayısı sıfır değilse, bir sözcükle paketlenmiş olan kapsam kapsamları hakkındaki bilgilerin listesi üst bilgiden ve isteğe bağlı genişletilmiş üst bilgiden sonra gelir. Başlangıç uzaklığını artırmak için depolanırlar. Her kapsam aşağıdaki bitleri içerir:
a. Epilog Başlangıç Uzaklığı , işlevin başlangıcına göre, bölmenin bayt cinsinden uzaklığını 4'e bölen 18 bitlik bir alandır.
b. Res , gelecekteki genişletme için ayrılmış 4 bitlik bir alandır. Değeri 0 olmalıdır.
c. Epilog Başlangıç Dizini 10 bitlik bir alandır (Genişletilmiş Kod Sözcüklerinden 2 bit daha fazla). Bu derlemeyi açıklayan ilk geri sarma kodunun bayt dizinini gösterir.
Kapsam kapsamları listesi geldikten sonra, sonraki bir bölümde ayrıntılı olarak açıklanan, geri alma kodları içeren bir bayt dizisi gelir. Bu dizi, en yakın tam sözcük sınırına uçta doldurulur. Geri sarma kodları bu diziye yazılır. İşlevin gövdesine en yakın olanla başlar ve işlevin kenarlarına doğru hareket eder. Her geri sarma kodunun baytları büyük uç sırada depolanır, böylece en önemli bayt ilk olarak getirilir ve bu da işlemi ve kodun geri kalanının uzunluğunu tanımlar.
Son olarak, geri sarma kodu baytlarının ardından üst bilgideki X biti 1 olarak ayarlandıysa, özel durum işleyicisi bilgileri gelir. Özel durum işleyicisinin adresini sağlayan tek bir Özel Durum İşleyici RVA'sı oluşur. Hemen ardından özel durum işleyicisi tarafından gereken değişken uzunlukta veri miktarı gelir.
Kayıt .xdata
, ilk 8 baytı getirmek ve izleyen değişken boyutlu özel durum verilerinin uzunluğu çıkarılarak kaydın tam boyutunu hesaplamak için bunları kullanmak üzere tasarlanmıştır. Aşağıdaki kod parçacığı kayıt boyutunu hesaplar:
ULONG ComputeXdataSize(PULONG Xdata)
{
ULONG Size;
ULONG EpilogScopes;
ULONG UnwindWords;
if ((Xdata[0] >> 22) != 0) {
Size = 4;
EpilogScopes = (Xdata[0] >> 22) & 0x1f;
UnwindWords = (Xdata[0] >> 27) & 0x1f;
} else {
Size = 8;
EpilogScopes = Xdata[1] & 0xffff;
UnwindWords = (Xdata[1] >> 16) & 0xff;
}
if (!(Xdata[0] & (1 << 21))) {
Size += 4 * EpilogScopes;
}
Size += 4 * UnwindWords;
if (Xdata[0] & (1 << 20)) {
Size += 4; // Exception handler RVA
}
return Size;
}
Prolog ve her bir epilog unwind kodları içinde kendi dizini olsa da, tablo bunlar arasında paylaşılır. Hepsinin aynı kodları paylaşabilmesi tamamen mümkündür (ve hiç de yaygın değildir). (Örneğin, Örnekler bölümü.) Derleyici yazarları özellikle bu durum için iyileştirilmelidir. Bunun nedeni, belirtilebilen en büyük dizinin 255 olmasıdır ve bu da belirli bir işlev için toplam geri sarma kodu sayısını sınırlar.
Geri sarma kodları
Geri sarma kodları dizisi, prologun etkilerinin tam olarak nasıl geri alındığını açıklayan bir dizi havuzudur. Bunlar, işlemlerin geri alınması gereken sırayla depolanır. Geri sarma kodları, bayt dizesi olarak kodlanmış küçük bir yönerge kümesi olarak düşünülebilir. Yürütme tamamlandığında, çağıran işlevin dönüş adresi yazmaçtadır lr
. Ayrıca, geçici olmayan tüm yazmaçlar işlev çağrıldığında değerlerine geri yüklenir.
Özel durumların yalnızca bir işlev gövdesi içinde gerçekleşeceği garanti edilirse ve hiçbir zaman bir giriş veya kapsam içinde gerçekleşmezse, yalnızca tek bir dizi gerekir. Ancak Windows'un geri sarmalama modeli, kodun kısmen yürütülen bir giriş veya bitiş listesi içinden geri alabilmesini gerektirir. Bu gereksinimi karşılamak için, geri alma kodları dikkatle tasarlanmıştır, böylece 1:1'i giriş ve girişteki ilgili her opcode ile kesin bir şekilde eşlerler. Bu tasarımın çeşitli etkileri vardır:
Geri sarma kodlarının sayısını sayarak, giriş ve kapsam uzunluğunu hesaplamak mümkündür.
Bir kapsam kapsamının başındaki yönergelerin sayısını sayarak, geri alma kodlarının eşdeğer sayısını atlayabilirsiniz. Bölüm tarafından yapılan kısmen yürütülen geri sarmayı tamamlamak için bir dizinin geri kalanını yürütebiliriz.
Prolog sonundan önceki yönergeleri sayarak, geri sarma kodlarının eşdeğer sayısını atlayabilirsiniz. Yalnızca yürütmeyi tamamlamış olan prolog bölümlerini geri almak için sıranın geri kalanını yürütebiliriz.
Geri sarma kodları aşağıdaki tabloya göre kodlanmıştır. Tüm geri sarma kodları, büyük bir yığın (alloc_l
) ayıran kod dışında tek/çift bayttır. Toplamda 22 geri sarma kodu vardır. Her geri sarma kodu, kısmen yürütülen prolog'ların ve kapsamların geri alınabilmesi için prolog/epilog'daki bir yönergeyi tam olarak eşler.
Kodu geri sarma | Bitler ve yorumlama |
---|---|
alloc_s |
000xxxx: boyutu < 512 olan küçük yığın ayırın (2^5 * 16). |
save_r19r20_x |
001zzzz: önceden dizinlenmiş uzaklık >= -248 konumunda [sp-#Z*8]! çifti kaydedin <x19,x20> |
save_fplr |
01zzzzzz: , uzaklık <= 504 konumunda [sp+#Z*8] çift kaydedin<x29,lr> . |
save_fplr_x |
10zzzzzz: önceden dizinlenmiş uzaklık >= -512 konumunda [sp-(#Z+1)*8]! çift kaydedin <x29,lr> |
alloc_m |
11000xxx'xxxxxxxx: boyutu < 32K olan büyük yığın ayırın (2^11 * 16). |
save_regp |
110010xx'xxzzzz: save x(19+#X) pair at [sp+#Z*8] , offset <= 504 |
save_regp_x |
110011xx'xxzzzzzz: konumunda çift x(19+#X) [sp-(#Z+1)*8]! kaydetme, önceden dizine alınan uzaklık >= -512 |
save_reg |
110100xx'xxzzzzzz: kayıt defterini x(19+#X) kaydedin [sp+#Z*8] , uzaklık <= 504 |
save_reg_x |
1101010x'xxxzzzzz: save reg x(19+#X) at [sp-(#Z+1)*8]! , önceden dizinlenmiş uzaklık >= -256 |
save_lrpair |
1101011x'xxzzzzzz: save pair <x(19+2*#X),lr> at [sp+#Z*8] , offset <= 504 |
save_fregp |
1101100x'xxzzzzzz: save pair d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: konumunda çift d(8+#X) [sp-(#Z+1)*8]! kaydetme, önceden dizinlenmiş uzaklık >= -512 |
save_freg |
1101110x'xxzzzzzz: save reg d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_freg_x |
11011110'xxxzzzzz: kayıt defterini d(8+#X) önceden dizine alınan uzaklık >= -256 konumunda [sp-(#Z+1)*8]! kaydedin |
alloc_l |
11100000'xxxxxxxx'xxxxxx'xxxxxxxx: boyutu 256M olan < büyük yığın ayırma (2^24 * 16) |
set_fp |
11100001: ile ayarlama x29 mov x29,sp |
add_fp |
11100010'xxxxxxxx: ile ayarlama x29 add x29,sp,#x*8 |
nop |
11100011: geri sarma işlemi gerekmez. |
end |
11100100: geri sarma kodunun sonu. Epilogda ima eder ret . |
end_c |
11100101: Geçerli zincirlenmiş kapsamdaki geri sarma kodunun sonu. |
save_next |
11100110: Sonraki geçici olmayan Int veya FP yazmaç çifti kaydedin. |
11100111: ayrılmış | |
11101xx: Yalnızca asm yordamları için oluşturulan aşağıdaki özel yığın örnekleri için ayrılmıştır | |
11101000: Özel yığın MSFT_OP_TRAP_FRAME |
|
11101001: Için özel yığın MSFT_OP_MACHINE_FRAME |
|
11101010: Için özel yığın MSFT_OP_CONTEXT |
|
11101011: Için özel yığın MSFT_OP_EC_CONTEXT |
|
11101100: Için özel yığın MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: ayrılmış | |
11101110: ayrılmış | |
11101111: ayrılmış | |
11110xxx: ayrılmış | |
11111000'yy : ayrılmış | |
11111001'y'y : ayrılmış | |
11111010'y'y'y'y : ayrılmış | |
11111011'y'y'y'y'y : ayrılmış | |
pac_sign_lr |
11111100: İade adresini lr ile oturum açın pacibsp |
11111101: ayrılmış | |
11111110: ayrılmış | |
11111111: ayrılmış |
Birden çok baytı kapsayan büyük değerler içeren yönergelerde, en önemli bitler önce depolanır. Bu tasarım, kodun yalnızca ilk baytını arayarak geri sarma kodunun bayt cinsinden toplam boyutunu bulmayı mümkün kılar. Her geri sarma kodu bir giriş veya kapsam içindeki bir yönergeyle tam olarak eşlendiğinden, prolog veya epilog boyutunu hesaplayabilirsiniz. Sıra başından sonuna kadar yürüyün ve karşılık gelen opcode'un uzunluğunu belirlemek için bir arama tablosu veya benzer bir cihaz kullanın.
Bir girişte dizine alınan uzaklık sonrası adresleme işlemine izin verilmez. Tüm uzaklık aralıkları (#Z), tüm kaydetme alanları için 248'in stp
/str
yeterli olduğu (10 Int yazmacı + 8 FP yazmaç + 8 giriş yazmacı) dışında save_r19r20_x
adresleme kodlaması ile eşleşmektedir.
save_next
Int veya FP geçici kayıt çifti için kaydetmeyi izlemelidir: , , , save_fregp_x
, , save_r19r20_x
veya başka bir save_next
. save_fregp
save_regp_x
save_regp
Bir sonraki yazmaç çiftini sonraki 16 baytlık yuvada "büyüyen" sırada kaydeder. A save_next
, son Int yazmaç çiftini save-next
belirten FP yazmaç çiftini izlediğinde ilk FP yazmaç çiftini ifade eder.
Normal dönüş ve atlama yönergelerinin boyutları aynı olduğundan, kuyruk çağrısı senaryolarında ayrı end
ayrı geri sarma koduna gerek yoktur.
end_c
, iyileştirme amacıyla bitişik olmayan işlev parçalarını işlemek üzere tasarlanmıştır. end_c
Geçerli kapsamdaki geri sarma kodlarının sonunu gösteren bir, gerçek end
ile biten başka bir geri sarma kodu serisi tarafından takip edilmelidir. ve arasındaki end_c
end
geri sarma kodları, üst bölgedeki prolog işlemlerini temsil eder (bir "hayalet" prolog). Diğer ayrıntılar ve örnekler aşağıdaki bölümde açıklanmıştır.
Paketlenmiş geri sarma verileri
Kapsamları ve kapsamları aşağıda açıklanan kurallı biçimi izleyen işlevler için paketlenmiş geri alma verileri kullanılabilir. Kayıt gereksinimini .xdata
tamamen ortadan kaldırır ve geriye doğru veri sağlama maliyetini önemli ölçüde azaltır. Kurallı girişler ve kapsamlar, basit bir işlevin ortak gereksinimlerini karşılayacak şekilde tasarlanmıştır: Özel durum işleyici gerektirmeyen ve kurulum ve kaldırma işlemlerini standart sırada yapan bir işlevdir.
Paketlenmiş geri sarma verilerini içeren bir .pdata
kaydın biçimi şöyle görünür:
Alanlar aşağıdaki gibidir:
- İşlev Başlatma RVA'sı , işlevin başlangıcının 32 bit RVA'dır.
- Bayrak , yukarıda açıklandığı gibi aşağıdaki anlamlara sahip 2 bitlik bir alandır:
- 00 = paketlenmiş geri alma verileri kullanılmaz; kalan bitler bir
.xdata
kayda işaret eder - 01 = kapsamın başında ve sonunda tek bir giriş ve kapsam ile kullanılan paketlenmiş geri sarma verileri
- 10 = herhangi bir giriş ve kapsam olmadan kod için kullanılan paketlenmiş geri sarma verileri. Ayrılmış işlev kesimlerini tanımlamak için kullanışlıdır
- 11 = ayrılmış.
- 00 = paketlenmiş geri alma verileri kullanılmaz; kalan bitler bir
- İşlev Uzunluğu , işlevin tamamının bayt cinsinden uzunluğunu 4'e bölen 11 bitlik bir alandır. İşlev 8k'den büyükse bunun yerine tam
.xdata
kayıt kullanılmalıdır. - Çerçeve Boyutu , bu işlev için ayrılan yığın bayt sayısını 16'ya bölen 9 bitlik bir alandır. Yığından daha büyük (8k-16) bayt ayıran işlevlerin tam
.xdata
kayıt kullanması gerekir. Yerel değişken alanını, giden parametre alanını, aranan kaydedilmiş Int ve FP alanını ve giriş parametresi alanını içerir. Dinamik ayırma alanını dışlar. - CR , işlevin çerçeve zincirini ayarlamak ve bağlantı döndürmek için ek yönergeler içerip içermediğini gösteren 2 bitlik bir bayraktır:
- 00 = zincirsiz işlev,
<x29,lr>
çift yığına kaydedilmez - 01 = zincirsiz işlev,
<lr>
yığına kaydedilir - 10 = işaretli iade adresiyle
pacibsp
zincirlenmiş işlev - 11 = zincirlenmiş işlev, bir depo/yük çifti yönergesi prolog/epilog içinde kullanılır
<x29,lr>
- 00 = zincirsiz işlev,
- H , işlevin tamsayı parametre yazmaçlarını (x0-x7) işlevin en başında depolayarak kaydedip barındırmadığını gösteren 1 bitlik bir bayraktır. (0 = ev kayıtları değil, 1 = ev kayıtları).
- RegI , kurallı yığın konumuna kaydedilen geçici olmayan INT yazmaçlarının (x19-x28) sayısını gösteren 4 bitlik bir alandır.
- RegF , kurallı yığın konumuna kaydedilen geçici olmayan FP yazmaçlarının (d8-d15) sayısını gösteren 3 bitlik bir alandır. (RegF=0: hiçbir FP yazmaç kaydedilmez; RegF>0: RegF+1 FP yazmaçları kaydedilir). Paketlenmiş geri sarma verileri, yalnızca bir FP yazmaç kaydeden işlev için kullanılamaz.
Yukarıdaki bölümde 1, 2 (giden parametre alanı olmadan), 3 ve 4 kategorilerine giren kurallı prologlar paketlenmiş geri sarma biçimiyle temsil edilebilir. Kurallı işlevlerin epilogları, H'nin hiçbir etkisi olmaması, yönergenin atlanması ve adımların set_fp
sırası ile her adımdaki yönergelerin epilogda ters çevrilmesi dışında benzer bir biçimde izlenir. Paketlenmiş .xdata
algoritması aşağıdaki tabloda ayrıntılı olarak verilen şu adımları izler:
0. Adım: Her alanın boyutunu önceden hesaplama.
1. Adım: İade adresini imzalayın.
2. Adım: Int callee-saved yazmaçlarını kaydedin.
3. Adım: Bu adım, ilk bölümlerdeki tür 4'e özgüdür. lr
int alanının sonuna kaydedilir.
4. Adım: FP çağrılı kayıtlı yazmaçları kaydedin.
5. Adım: Giriş bağımsız değişkenlerini giriş parametresi alanına kaydedin.
6. Adım: Yerel alan, <x29,lr>
çift ve giden parametre alanı da dahil olmak üzere kalan yığını ayırın. 6a kurallı tip 1'e karşılık gelir. 6b ve 6c kurallı tür 2 içindir. 6d ve 6e hem tür 3 hem de tür 4 içindir.
Adım # | Bayrak değerleri | # of instructions | Işlem kodu | Kodu geri sarma |
---|---|---|---|---|
0 | #intsz = RegI * 8; if (CR==01) #intsz += 8; // lr #fpsz = RegF * 8; if(RegF) #fpsz += 8; #savsz=((#intsz+#fpsz+8*8*H)+0xf)&~0xf) #locsz = #famsz - #savsz |
|||
1 | CR == 10 | 1 | pacibsp |
pac_sign_lr |
2 | 0 <RegI<= 10 | RegI / 2 + RegI % 2 |
stp x19,x20,[sp,#savsz]! stp x21,x22,[sp,#16] ... |
save_regp_x save_regp ... |
3 | CR == 01* | 1 | str lr,[sp,#(intsz-8)] * |
save_reg |
4 | 0 <RegF<= 7 | (RegF + 1) / 2 + (RegF + 1) % 2) |
stp d8,d9,[sp,#intsz] **stp d10,d11,[sp,#(intsz+16)] ... str d(8+RegF),[sp,#(intsz+fpsz-8)] |
save_fregp ... save_freg |
5 | H == 1 | 4 | stp x0,x1,[sp,#(intsz+fpsz)] stp x2,x3,[sp,#(intsz+fpsz+16)] stp x4,x5,[sp,#(intsz+fpsz+32)] stp x6,x7,[sp,#(intsz+fpsz+48)] |
nop nop nop nop |
6a | (CR == 10 || CR == 11) &#locsz <= 512 |
2 | stp x29,lr,[sp,#-locsz]! mov x29,sp *** |
save_fplr_x set_fp |
6b | (CR == 10 || CR == 11) & 512 < #locsz <= 4080 |
3 | sub sp,sp,#locsz stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m save_fplr set_fp |
6c | (CR == 10 || CR == 11) &#locsz > 4080 |
4 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m alloc_s /alloc_m save_fplr set_fp |
6d | (CR == 00 || CR == 01) &#locsz <= 4080 |
1 | sub sp,sp,#locsz |
alloc_s /alloc_m |
6e | (CR == 00 || CR == 01) &#locsz > 4080 |
2 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) |
alloc_m alloc_s /alloc_m |
* CR == 01 ve RegI tek bir sayıysa, 3. adım ve 2. adımdaki son save_reg
sayı tek bir save_regp
içinde birleştirilir.
** RegI == CR == 0 ve RegF != 0 ise, kayan noktanın ilki stp
ön atamayı yapar.
Epilogda öğesine mov x29,sp
karşılık gelen hiçbir yönerge yoktur. bir işlev'den x29
geri yüklenmesini sp
gerektiriyorsa paketlenmiş geri sarma verileri kullanılamaz.
Kısmi kapsamları ve kapsamları geri alma
En yaygın geri sarmalama durumlarında, özel durum veya çağrı işlevin gövdesinde, giriş ve tüm kapsamlardan uzakta gerçekleşir. Bu durumlarda, geri sarma basit bir işlemdir: geri sarmalayıcı, kodları unwind dizisinde yürütür. Dizin 0'da başlar ve bir opcode algılanana end
kadar devam eder.
Bir prolog veya kapsam yürütülürken bir özel durum veya kesinti oluşması durumunda doğru şekilde gevşemek daha zordur. Bu gibi durumlarda, yığın çerçevesi yalnızca kısmen oluşturulur. Sorun, tam olarak ne yapıldığını belirlemek ve doğru şekilde geri almaktır.
Örneğin, şu giriş ve epilog dizisini alın:
0000: stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store)
0004: stp d8,d9,[sp,#224] // save_fregp 0, 224
0008: stp x19,x20,[sp,#240] // save_regp 0, 240
000c: mov x29,sp // set_fp
...
0100: mov sp,x29 // set_fp
0104: ldp x19,x20,[sp,#240] // save_regp 0, 240
0108: ldp d8,d9,[sp,224] // save_fregp 0, 224
010c: ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load)
0110: ret lr // end
Her işlem kodunun yanında bu işlemi açıklayan uygun geri sarma kodu bulunur. Giriş için geri sarma kodları serisinin, giriş için geri sarma kodlarının tam bir ayna görüntüsü olduğunu görebilirsiniz (son yönergesi sayılmaz). Bu yaygın bir durum: Bu nedenle her zaman prolog için geri sarma kodlarının prolog'un yürütme sırasına göre ters sırada depolandığını varsayarız.
Bu nedenle hem prolog hem de epilog için ortak bir geri alma kodları kümesiyle baş başa kalırız:
set_fp
, save_regp 0,240
, save_fregp,0,224
, save_fplr_x_256
, , end
Normal düzende olduğundan, epilog olayı basittir. Epilog içindeki uzaklık 0'dan başlayarak (işlevdeki uzaklık 0x100 başlar), henüz temizleme yapılmadığından tam geri sarma dizisinin yürütülmesini bekleriz. Kendi kendimize bir yönerge bulursak (epilogda 2 uzaklığında), ilk geri alma kodunu atlayarak başarıyla geri alabiliriz. Bu durumu genelleştirebilir ve opcode'lar ile geri sarma kodları arasında 1:1 eşlemesi olduğunu varsayabiliriz. Ardından, epilogdaki n yönergesinden geri sarmayı başlatmak için ilk n geri alma kodlarını atlayıp oradan yürütmeye başlamamız gerekir.
Bunun tersi dışında benzer bir mantığın prolog için çalıştığı ortaya çıktı. Prologdaki 0 uzaklığından geri sarmayı kaldırmaya başlarsak hiçbir şey yürütmek istemeyiz. 2 uzaklığından (bu da bir yönergedir) geri sarıyorsak, sonundan bir geri sarma kodu olan geri alma dizisini yürütmeye başlamak istiyoruz. (Kodların ters sırada depolandığını unutmayın.) Burada da genelleştirebiliriz: prologdaki n yönergesinden geri sarmayı kaldırmaya başlarsak, kod listesinin sonundan n geri sarma kodlarını yürütmeye başlamalıyız.
Prolog ve epilog kodları her zaman tam olarak eşleşmez, bu nedenle unwind dizisinin birkaç kod dizisi içermesi gerekebilir. Kodları işlemeye nereden başlayacağınızı belirlemek için aşağıdaki mantığı kullanın:
İşlevin gövdesinden geri sarıyorsanız, 0 dizininde geri sarma kodlarını yürütmeye başlayın ve bir
end
işlem koduna gelene kadar devam edin.Bir epilog içinden geri sarıyorsanız, başlangıç noktası olarak epilog kapsamıyla birlikte sağlanan kapsama özgü başlangıç dizinini kullanın. Söz konusu bilgisayarın, tanımlamanın başlangıcından itibaren kaç bayt olduğunu hesapla. Ardından, önceden yürütülen tüm yönergeler hesaba aktarılana kadar geri alma kodlarını atlayarak geri alma kodları arasında ilerleyin. Ardından bu noktadan başlayarak yürütür.
Prolog içinden geri sarıyorsanız başlangıç noktanız olarak dizin 0'ı kullanın. Dizideki prolog kodunun uzunluğunu hesaplayın ve ardından söz konusu bilgisayarın prologun sonundan kaç bayt olduğunu hesaplayın. Ardından, henüz yürütülmemiş tüm yönergeler hesaba aktarılana kadar geri alma kodlarını atlayarak geri alma kodları arasında ilerleyin. Ardından bu noktadan başlayarak yürütür.
Bu kurallar, prolog için geri sarma kodlarının dizide her zaman ilk olması gerektiği anlamına gelir. Ve bunlar aynı zamanda vücudun içinden geri sarmak için kullanılan kodlardır. Tüm epilog'a özgü kod dizileri hemen sonra izlenmelidir.
İşlev parçaları
Kod iyileştirme amaçları ve diğer nedenlerle, bir işlevi ayrılmış parçalara bölmek (bölgeler olarak da adlandırılır) tercih edilebilir. Bölündüğünde, sonuçta elde edilen her işlev parçası kendi ayrı .pdata
(ve büyük olasılıkla .xdata
) kaydını gerektirir.
Kendi girişine sahip her ayrılmış ikincil parça için, girişinde yığın ayarlaması yapılmamış olması beklenir. İkincil bölge için gereken tüm yığın alanı, üst bölgesi (veya konak bölgesi olarak adlandırılır) tarafından önceden ayrılmalıdır. Bu ön yükleme, yığın işaretçisi işlemesini kesinlikle işlevin özgün girişinde tutar.
İşlev parçalarının tipik bir örneği, derleyicinin bir kod bölgesini konak işlevinin dışına taşıyabileceği "kod ayrımı"dır. Kod ayrımından kaynaklanabilir üç olağan dışı durum vardır.
Örnek
(bölge 1: başlangıç)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(bölge 1: bitiş)
(bölge 3: başlangıç)
...
(bölge 3: bitiş)
(bölge 2: başlangıç)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(bölge 2: bitiş)
Yalnızca giriş (bölge 1: tüm kapsamlar ayrılmış bölgelerdedir):
Yalnızca prolog açıklanmalıdır. Bu prolog, sıkıştırılmış
.pdata
biçimde temsil edilemiyor. Tam.xdata
durumda, Kapsam Sayısı = 0 ayarlanarak temsil edilebilir. Yukarıdaki örnekte 1. bölgeye bakın.Geri sarma kodları:
set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Yalnızca epiloglar (bölge 2: prolog konak bölgesindedir)
Zaman denetimi bu bölgeye atlayarak tüm prolog kodlarının yürütüldüğünü varsayılır. Kısmi geri sarma, normal bir işlevdeki gibi epiloglarda da oluşabilir. Bu tür bir bölge, sıkıştırılmış
.pdata
ile temsil edilemez. Tam.xdata
kayıtta, bir veend
geri sarma kod çifti tarafından köşeli ayraçlı birend_c
"hayalet" giriş listesiyle kodlanabilir. Baştakiend_c
değer, giriş öğesinin boyutunun sıfır olduğunu gösterir. Tek bir epilogun başlangıç dizini öğesini işaret etmektedirset_fp
.Bölge 2 için geri sarma kodu:
end_c
,set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Prolog veya epilog yok (bölge 3: girişler ve tüm kapsamlar diğer parçalardadır):
Sıkıştırılmış
.pdata
biçim, Bayrak = 10 ayarıyla uygulanabilir. Tam.xdata
kayıtla, Kapsam Sayısı = 1. Geri sarma kodu yukarıdaki bölge 2'nin koduyla aynıdır, ancak Epilog Başlangıç Dizini de öğesine işaret ederend_c
. Kodun bu bölgesinde kısmi geri sarma hiçbir zaman gerçekleşmez.
İşlev parçalarının daha karmaşık bir diğer örneği de "küçültme sarmalama"dır. Derleyici, bazı callee-saved yazmaçlarının kaydedilmesini işlev girişi prologunun dışına kadar geciktirmeyi seçebilir.
(bölge 1: başlangıç)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(bölge 2: başlangıç)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(bölge 2: bitiş)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(bölge 1: bitiş)
Bölge 1'in girişinde yığın alanı önceden ayrılmıştır. 2. bölgenin ana bilgisayar işlevinden taşınsa bile aynı geri sarma koduna sahip olacağını görebilirsiniz.
Bölge 1: set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Epilog Başlangıç Dizini her zamanki gibi işaret etmektedir set_fp
.
Bölge 2: save_regp 2, 224
, end_c
, set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Epilog Başlangıç Dizini, ilk olarak kodunu save_regp 2, 224
geri sarmaya işaret eder.
Büyük işlevler
Parçalar, üst bilgideki .xdata
bit alanları tarafından uygulanan 1M sınırından daha büyük işlevleri açıklamak için kullanılabilir. Bunun gibi olağan dışı büyük bir işlevi tanımlamak için 1M'den küçük parçalara bölünmesi gerekir. Her parçanın, bir epilogu birden çok parçaya bölmemesi için ayarlanması gerekir.
İşlevin yalnızca ilk parçası bir prolog içerir; diğer tüm parçalar bir giriş işaretine sahip değil olarak işaretlenir. Mevcut epilog sayısına bağlı olarak, her parça sıfır veya daha fazla epilog içerebilir. Bir parçadaki her bir kapsam kapsamının, işlevin başlangıcına değil, parçanın başlangıcına göre başlangıç uzaklığını belirttiğini unutmayın.
Bir parçanın prologu ve kapsamı yoksa, işlevin gövdesinden nasıl geri alındığını açıklamak için yine de kendi .pdata
(ve muhtemelen .xdata
) kaydını gerektirir.
Örnekler
Örnek 1: Çerçeve zincirli, kompakt biçimli
|Foo| PROC
|$LN19|
str x19,[sp,#-0x10]! // save_reg_x
sub sp,sp,#0x810 // alloc_m
stp fp,lr,[sp] // save_fplr
mov fp,sp // set_fp
// end of prolog
...
|$pdata$Foo|
DCD imagerel |$LN19|
DCD 0x416101ed
;Flags[SingleProEpi] functionLength[492] RegF[0] RegI[1] H[0] frameChainReturn[Chained] frameSize[2080]
Örnek 2: Çerçeve zincirli, ayna Prolog ve Epilog ile tam form
|Bar| PROC
|$LN19|
stp x19,x20,[sp,#-0x10]! // save_regp_x
stp fp,lr,[sp,#-0x90]! // save_fplr_x
mov fp,sp // set_fp
// end of prolog
...
// begin of epilog, a mirror sequence of Prolog
mov sp,fp
ldp fp,lr,[sp],#0x90
ldp x19,x20,[sp],#0x10
ret lr
|$pdata$Bar|
DCD imagerel |$LN19|
DCD imagerel |$unwind$cse2|
|$unwind$Bar|
DCD 0x1040003d
DCD 0x1000038
DCD 0xe42291e1
DCD 0xe42291e1
;Code Words[2], Epilog Count[1], E[0], X[0], Function Length[6660]
;Epilog Start Index[0], Epilog Start Offset[56]
;set_fp
;save_fplr_x
;save_r19r20_x
;end
Epilog Başlangıç Dizini [0] aynı Prolog unwind kodu dizisine işaret eder.
Örnek 3: Değişken zincirsiz İşlev
|Delegate| PROC
|$LN4|
sub sp,sp,#0x50
stp x19,lr,[sp]
stp x0,x1,[sp,#0x10] // save incoming register to home area
stp x2,x3,[sp,#0x20] // ...
stp x4,x5,[sp,#0x30]
stp x6,x7,[sp,#0x40] // end of prolog
...
ldp x19,lr,[sp] // beginning of epilog
add sp,sp,#0x50
ret lr
AREA |.pdata|, PDATA
|$pdata$Delegate|
DCD imagerel |$LN4|
DCD imagerel |$unwind$Delegate|
AREA |.xdata|, DATA
|$unwind$Delegate|
DCD 0x18400012
DCD 0x200000f
DCD 0xe3e3e3e3
DCD 0xe40500d6
DCD 0xe40500d6
;Code Words[3], Epilog Count[1], E[0], X[0], Function Length[18]
;Epilog Start Index[4], Epilog Start Offset[15]
;nop // nop for saving in home area
;nop // ditto
;nop // ditto
;nop // ditto
;save_lrpair
;alloc_s
;end
Epilog Başlangıç Dizini [4] Prolog geri sarma kodunun ortasına işaret eder (kısmen geri sarma dizisini yeniden kullanın).