Penanganan pengecualian ARM64
Windows di ARM64 menggunakan mekanisme penanganan pengecualian terstruktur yang sama untuk pengecualian yang dihasilkan perangkat keras asinkron dan pengecualian yang dihasilkan perangkat lunak sinkron. Penanganan pengecualian khusus bahasa dibangun di atas penanganan pengecualian terstruktur Windows dengan menggunakan fungsi pembantu bahasa. Dokumen ini menjelaskan penanganan pengecualian di Windows di ARM64. Ini menggambarkan pembantu bahasa yang digunakan oleh kode yang dihasilkan oleh perakit Microsoft ARM dan kompilator MSVC.
Tujuan dan motivasi
Pengecualian melepas konvensi data, dan deskripsi ini, dimaksudkan untuk:
Berikan deskripsi yang cukup untuk memungkinkan unwinding tanpa pemeriksaan kode dalam semua kasus.
Menganalisis kode mengharuskan kode untuk di-paged in. Ini mencegah melepas lelah dalam beberapa keadaan di mana itu berguna (pelacakan, pengambilan sampel, penelusuran kesalahan).
Menganalisis kode itu kompleks; pengkompilasi harus berhati-hati untuk hanya menghasilkan instruksi bahwa unwinder dapat mendekode.
Jika melepas lelah tidak dapat sepenuhnya dijelaskan dengan menggunakan kode unwind, maka dalam beberapa kasus, itu harus kembali ke decoding instruksi. Decoding instruksi meningkatkan kompleksitas keseluruhan, dan idealnya harus dihindari.
Mendukung unwinding di mid-prolog dan mid-epilog.
- Unwinding digunakan di Windows untuk penanganan pengecualian lebih dari. Sangat penting bahwa kode dapat bersantai secara akurat bahkan ketika di tengah urutan kode prolog atau epilog.
Ambil sedikit ruang.
Kode unwind tidak boleh dikumpulkan untuk secara signifikan meningkatkan ukuran biner.
Karena kode unwind kemungkinan akan dikunci dalam memori, jejak kecil memastikan overhead minimal untuk setiap biner yang dimuat.
Asumsi
Asumsi ini dibuat dalam deskripsi penanganan pengecualian:
Prolog dan epilog cenderung mencerminkan satu sama lain. Dengan memanfaatkan sifat umum ini, ukuran metadata yang diperlukan untuk menggambarkan melepas lelah dapat sangat berkurang. Dalam isi fungsi, tidak masalah apakah operasi prolog dibatalkan, atau operasi epilog dilakukan dengan cara yang lebih maju. Keduanya harus menghasilkan hasil yang identik.
Fungsi cenderung pada keseluruhan menjadi relatif kecil. Beberapa pengoptimalan untuk ruang mengandalkan fakta ini untuk mencapai pengemasan data yang paling efisien.
Tidak ada kode bersyarah dalam epilog.
Register penunjuk bingkai khusus: Jika
sp
disimpan di register lain (x29
) dalam prolog, register tersebut tetap tidak tersentuh di seluruh fungsi. Ini berarti aslinyasp
dapat dipulihkan kapan saja.sp
Kecuali disimpan di register lain, semua manipulasi pointer tumpukan terjadi secara ketat dalam prolog dan epilog.Tata letak bingkai tumpukan diatur seperti yang dijelaskan di bagian berikutnya.
Tata letak bingkai tumpukan ARM64
Untuk fungsi berantai bingkai, fp
pasangan dan lr
dapat disimpan pada posisi apa pun di area variabel lokal, tergantung pada pertimbangan pengoptimalan. Tujuannya adalah untuk memaksimalkan jumlah lokal yang dapat dicapai dengan satu instruksi berdasarkan penunjuk bingkai (x29
) atau penunjuk tumpukan (sp
). Namun, untuk alloca
fungsi, fungsi harus ditautkan, dan x29
harus menunjuk ke bagian bawah tumpukan. Untuk memungkinkan cakupan register-pair-addressing-mode yang lebih baik, area penyimpanan register nonvolatile diposisikan di bagian atas tumpukan area lokal. Berikut adalah contoh yang menggambarkan beberapa urutan prolog yang paling efisien. Demi kejelasan dan lokalitas cache yang lebih baik, urutan penyimpanan register yang disimpan callee di semua prolog kanonis dalam urutan "tumbuh dewasa". #framesz
di bawah ini mewakili ukuran seluruh tumpukan (tidak termasuk alloca
area). #localsz
dan #outsz
menunjukkan ukuran area lokal (termasuk area penyimpanan untuk <x29, lr>
pasangan) dan ukuran parameter keluar, masing-masing.
Berantai, #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)
Berantai, #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
Tidak di-rantai, fungsi daun (
lr
tidak disimpan)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
Semua penduduk lokal diakses berdasarkan
sp
.<x29,lr>
menunjuk ke bingkai sebelumnya. Untuk ukuran <bingkai = 512,sub sp, ...
dapat dioptimalkan jika area tersimpan regs dipindahkan ke bagian bawah tumpukan. Kelemahannya adalah tidak konsisten dengan tata letak lain di atas. Dan, reg yang disimpan mengambil bagian dari rentang untuk reg pasangan dan mode alamat offset pra-dan pasca-indeks.Fungsi non-daun yang tidak di-rantai (disimpan di area tersimpan
lr
Int)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
Atau, dengan nomor genap yang disimpan Int register,
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
Hanya
x19
disimpan: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
* Alokasi area penyimpanan reg tidak dilipat ke dalam
stp
karena reg-lrstp
yang diindeks sebelumnya tidak dapat diwakili dengan kode unwind.Semua penduduk lokal diakses berdasarkan
sp
.<x29>
menunjuk ke bingkai sebelumnya.Berantai, #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
Dibandingkan dengan contoh prolog pertama di atas, contoh ini memiliki keuntungan: semua instruksi penyimpanan register siap dijalankan setelah hanya satu instruksi alokasi tumpukan. Itu berarti tidak ada anti-ketergantungan pada
sp
yang mencegah paralelisme tingkat instruksi.Berantai, ukuran > bingkai 512 (opsional untuk fungsi tanpa
alloca
)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
Untuk tujuan pengoptimalan,
x29
dapat diletakkan pada posisi apa pun di area lokal untuk memberikan cakupan yang lebih baik untuk "pasangan reg" dan mode pengalamatan offset pra-/pasca-indeks. Lokal di bawah penunjuk bingkai dapat diakses berdasarkansp
.Berantai, ukuran > bingkai 4K, dengan atau tanpa alloca(),
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>
Informasi penanganan pengecualian ARM64
.pdata
Catatan
Rekaman .pdata
adalah array yang diurutkan dari item panjang tetap yang menjelaskan setiap fungsi manipulasi tumpukan dalam biner PE. Frasa "stack-manipulating" bersifat signifikan: fungsi daun yang tidak memerlukan penyimpanan lokal apa pun, dan tidak perlu menyimpan/memulihkan register non-volatile, tidak memerlukan .pdata
rekaman. Rekaman ini harus dihilangkan secara eksplisit untuk menghemat ruang. Melepas lelah dari salah satu fungsi ini bisa mendapatkan alamat pengembalian langsung dari lr
untuk berpindah ke pemanggil.
Setiap .pdata
catatan untuk ARM64 panjangnya 8 byte. Format umum setiap rekaman menempatkan RVA 32-bit fungsi dimulai pada kata pertama, diikuti dengan kata kedua yang berisi penunjuk ke blok panjang .xdata
variabel, atau kata yang dikemas yang menjelaskan urutan unwinding fungsi kanonis.
Bidangnya adalah sebagai berikut:
Function Start RVA adalah RVA 32-bit dari awal fungsi.
Bendera adalah bidang 2-bit yang menunjukkan cara menginterpretasikan 30 bit sisa kata kedua
.pdata
. Jika Bendera adalah 0, maka bit yang tersisa membentuk RVA Informasi Pengecualian (dengan dua bit terendah secara implisit 0). Jika Bendera bukan nol, bit yang tersisa membentuk struktur Data Unwind Kemasan.Informasi Pengecualian RVA adalah alamat struktur informasi pengecualian panjang variabel, yang disimpan di bagian .
.xdata
Data ini harus diratakan 4 byte.Packed Unwind Data adalah deskripsi terkompresi tentang operasi yang diperlukan untuk melepas lelah dari fungsi, dengan asumsi bentuk kanonis. Dalam hal ini, tidak ada
.xdata
catatan yang diperlukan.
.xdata
Catatan
Ketika format unwind yang dikemas tidak mencukupi untuk menggambarkan pelekatan fungsi, rekaman panjang .xdata
variabel harus dibuat. Alamat rekaman ini disimpan di kata .pdata
kedua rekaman. Format adalah .xdata
kumpulan kata panjang variabel yang dikemas:
Data ini dipecah menjadi empat bagian:
Header 1 kata atau 2 kata yang menjelaskan ukuran keseluruhan struktur dan menyediakan data fungsi utama. Kata kedua hanya ada jika bidang Jumlah Epilog dan Kata Kode diatur ke 0. Header memiliki bidang bit ini:
a. Panjang Fungsi adalah bidang 18-bit. Ini menunjukkan panjang total fungsi dalam byte, dibagi 4. Jika fungsi lebih besar dari 1M, maka beberapa
.pdata
dan.xdata
rekaman harus digunakan untuk menjelaskan fungsi. Untuk informasi selengkapnya, lihat bagian Fungsi besar.b. Vers adalah bidang 2-bit. Ini menjelaskan versi yang tersisa
.xdata
. Saat ini, hanya versi 0 yang ditentukan, sehingga nilai 1-3 tidak diizinkan.c. X adalah bidang 1-bit. Ini menunjukkan adanya (1) atau tidak adanya (0) data pengecualian.
d. E adalah bidang 1-bit. Ini menunjukkan bahwa informasi yang menjelaskan satu epilog dikemas ke dalam header (1) daripada memerlukan lebih banyak kata cakupan nanti (0).
e. Jumlah Epilog adalah bidang 5-bit yang memiliki dua arti, tergantung pada status E bit:
Jika E adalah 0, itu menentukan hitungan jumlah total cakupan epilog yang dijelaskan di bagian 2. Jika ada lebih dari 31 cakupan dalam fungsi, bidang Kata Kode harus diatur ke 0 untuk menunjukkan bahwa kata ekstensi diperlukan.
Jika E adalah 1, maka bidang ini menentukan indeks kode unwind pertama yang menjelaskan satu-satunya epilog.
f. Kata Kode adalah bidang 5-bit yang menentukan jumlah kata 32-bit yang diperlukan untuk berisi semua kode unwind di bagian 3. Jika diperlukan lebih dari 31 kata (yaitu, 124 kode unwind) diperlukan, maka bidang ini harus 0 untuk menunjukkan bahwa kata ekstensi diperlukan.
g. Jumlah Epilog yang Diperluas dan Kata Kode yang Diperluas masing-masing adalah bidang 16-bit dan 8-bit. Mereka menyediakan lebih banyak ruang untuk mengodekan sejumlah besar epilog, atau sejumlah besar kata kode unwind yang luar biasa besar. Kata ekstensi yang berisi bidang ini hanya ada jika bidang Jumlah Epilog dan Kata Kode di kata header pertama adalah 0.
Jika jumlah epilog bukan nol, daftar informasi tentang cakupan epilog, dikemas satu ke kata, muncul setelah header dan header opsional yang diperluas. Mereka disimpan dalam rangka meningkatkan memulai offset. Setiap cakupan berisi bit berikut:
a. Epilog Start Offset adalah bidang 18-bit yang memiliki offset dalam byte, dibagi 4, dari epilog relatif terhadap awal fungsi.
b. Res adalah bidang 4-bit yang dicadangkan untuk ekspansi di masa mendatang. Nilainya harus 0.
c. Epilog Start Index adalah bidang 10-bit (2 bit lebih banyak daripada Extended Code Words). Ini menunjukkan indeks byte dari kode unwind pertama yang menjelaskan epilog ini.
Setelah daftar cakupan epilog muncul array byte yang berisi kode unwind, dijelaskan secara rinci di bagian selanjutnya. Array ini diisi di akhir ke batas kata lengkap terdekat. Kode unwind ditulis ke array ini. Mereka mulai dengan yang paling dekat dengan isi fungsi, dan bergerak ke tepi fungsi. Byte untuk setiap kode unwind disimpan dalam urutan big-endian sehingga byte yang paling signifikan diambil terlebih dahulu, yang mengidentifikasi operasi dan panjang kode lainnya.
Terakhir, setelah byte kode unwind, jika X bit di header diatur ke 1, muncul informasi handler pengecualian. Ini terdiri dari satu RVA Handler Pengecualian yang menyediakan alamat handler pengecualian itu sendiri. Ini diikuti segera oleh jumlah data dengan panjang variabel yang diperlukan oleh handler pengecualian.
Catatan .xdata
dirancang sehingga dimungkinkan untuk mengambil 8 byte pertama, dan menggunakannya untuk menghitung ukuran penuh rekaman, dikurangi panjang data pengecualian berukuran variabel yang mengikuti. Cuplikan kode berikut menghitung ukuran rekaman:
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;
}
Meskipun prolog dan setiap epilog memiliki indeksnya sendiri ke dalam kode unwind, tabel dibagikan di antara mereka. Sangat mungkin (dan sama sekali tidak jarang) bahwa mereka semua dapat berbagi kode yang sama. (Misalnya, lihat Contoh 2 di Bagian contoh .) Penulis kompilator harus mengoptimalkan untuk kasus ini secara khusus. Ini karena indeks terbesar yang dapat ditentukan adalah 255, yang membatasi jumlah total kode unwind untuk fungsi tertentu.
Lepaskan kode
Array kode unwind adalah kumpulan urutan yang menjelaskan dengan tepat cara membatalkan efek prolog. Mereka disimpan dalam urutan yang sama operasi perlu dibatalkan. Kode unwind dapat dianggap sebagai set instruksi kecil, dikodekan sebagai string byte. Ketika eksekusi selesai, alamat pengembalian ke fungsi panggilan ada di lr
register. Dan, semua register non-volatil dipulihkan ke nilainya pada saat fungsi dipanggil.
Jika pengecualian dijamin hanya pernah terjadi dalam badan fungsi, dan tidak pernah dalam prolog atau epilog apa pun, maka hanya satu urutan yang diperlukan. Namun, model windows unwinding mengharuskan kode dapat melepas lelah dari dalam prolog atau epilog yang dijalankan sebagian. Untuk memenuhi persyaratan ini, kode unwind telah dirancang dengan hati-hati sehingga mereka secara tidak ambigu memetakan 1:1 ke setiap opcode yang relevan dalam prolog dan epilog. Desain ini memiliki beberapa implikasi:
Dengan menghitung jumlah kode unwind, dimungkinkan untuk menghitung panjang prolog dan epilog.
Dengan menghitung jumlah instruksi yang melewati awal cakupan epilog, dimungkinkan untuk melewati jumlah kode unwind yang setara. Kita dapat menjalankan urutan lainnya untuk menyelesaikan unwind yang dieksekusi sebagian yang dilakukan oleh epilog.
Dengan menghitung jumlah instruksi sebelum akhir prolog, dimungkinkan untuk melewati jumlah kode unwind yang setara. Kita dapat menjalankan sisa urutan untuk membatalkan hanya bagian prolog yang telah menyelesaikan eksekusi.
Kode unwind dikodekan sesuai dengan tabel di bawah ini. Semua kode unwind adalah byte tunggal/ganda, kecuali yang mengalokasikan tumpukan besar (alloc_l
). Total ada 22 kode unwind. Setiap kode unwind memetakan tepat satu instruksi dalam prolog/epilog, untuk memungkinkan unwinding prolog dan epilog yang dijalankan sebagian.
Lepaskan kode | Bit dan interpretasi |
---|---|
alloc_s |
000xxxxx: alokasikan tumpukan kecil dengan ukuran < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzz: simpan <x19,x20> pasangan di [sp-#Z*8]! , offset >pra-indeks = -248 |
save_fplr |
01zzzzzz: simpan <x29,lr> pasangan di [sp+#Z*8] , offset <= 504. |
save_fplr_x |
10zzzzzz: simpan <x29,lr> pasangan di [sp-(#Z+1)*8]! , offset >pra-indeks = -512 |
alloc_m |
11000xxx'xxxxxxxx: mengalokasikan tumpukan besar dengan ukuran < 32K (2^11 * 16). |
save_regp |
110010xx'xxzzzzzz: simpan x(19+#X) pasangan di [sp+#Z*8] , offset <= 504 |
save_regp_x |
110011xx'xxzzzzzz: simpan pasangan x(19+#X) di , offset >yang diindeks [sp-(#Z+1)*8]! sebelumnya = -512 |
save_reg |
110100xx'xxzzzzzz: simpan reg x(19+#X) di [sp+#Z*8] , offset <= 504 |
save_reg_x |
1101010x'xxxzzzzz: simpan reg x(19+#X) di , offset >yang telah diindeks [sp-(#Z+1)*8]! = -256 |
save_lrpair |
1101011x'xxzzzzzz: simpan pasangan <x(19+2*#X),lr> di [sp+#Z*8] , offset <= 504 |
save_fregp |
1101100x'xxzzzzzz: simpan pasangan d(8+#X) di [sp+#Z*8] , offset <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: simpan pasangan d(8+#X) di , offset >yang diindeks [sp-(#Z+1)*8]! sebelumnya = -512 |
save_freg |
1101110x'xxzzzzzz: simpan reg d(8+#X) di [sp+#Z*8] , offset <= 504 |
save_freg_x |
11011110'xxxzzzzz: simpan reg d(8+#X) di [sp-(#Z+1)*8]! , offset >pra-indeks = -256 |
alloc_l |
11100000'xxxxxxxxx'xxxxxxxx'xxxxx: mengalokasikan tumpukan besar dengan ukuran < 256M (2^24 * 16) |
set_fp |
11100001: siapkan x29 dengan mov x29,sp |
add_fp |
11100010'xxxxxxxx: disiapkan x29 dengan add x29,sp,#x*8 |
nop |
11100011: tidak diperlukan operasi unwind. |
end |
11100100: akhir kode unwind. Menyiratkan ret dalam epilog. |
end_c |
11100101: akhir kode unwind dalam cakupan berantai saat ini. |
save_next |
11100110: simpan pasangan register Int atau FP non-volatile berikutnya. |
11100111: dicadangkan | |
11101xxx: dicadangkan untuk kasus tumpukan kustom di bawah ini hanya dihasilkan untuk rutinitas asm | |
11101000: Tumpukan kustom untuk MSFT_OP_TRAP_FRAME |
|
11101001: Tumpukan kustom untuk MSFT_OP_MACHINE_FRAME |
|
11101010: Tumpukan kustom untuk MSFT_OP_CONTEXT |
|
11101011: Tumpukan kustom untuk MSFT_OP_EC_CONTEXT |
|
11101100: Tumpukan kustom untuk MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: dicadangkan | |
11101110: dicadangkan | |
11101111: dicadangkan | |
11110xxx: dicadangkan | |
11111000'yyyyy: dicadangkan | |
11111001'yyyy'yyyyyy: dicadangkan | |
11111010'yyyyy'yy'y: dicadangkan | |
11111011'y'yyyy'y'yy: dicadangkan | |
pac_sign_lr |
11111100: masukkan alamat lr pengembalian pacibsp |
11111101: dicadangkan | |
11111110: dicadangkan | |
11111111: dicadangkan |
Dalam instruksi dengan nilai besar yang mencakup beberapa byte, bit yang paling signifikan disimpan terlebih dahulu. Desain ini memungkinkan untuk menemukan ukuran total dalam byte kode unwind dengan hanya mencari byte pertama dari kode. Karena setiap kode unwind dipetakan persis ke instruksi dalam prolog atau epilog, Anda dapat menghitung ukuran prolog atau epilog. Berjalan dari urutan mulai hingga akhir, dan gunakan tabel pencarian atau perangkat serupa untuk menentukan panjang opcode yang sesuai.
Alamat offset pasca-diindeks tidak diizinkan dalam prolog. Semua rentang offset (#Z) cocok dengan pengodean stp
str
/alamat kecuali save_r19r20_x
, di mana 248 cukup untuk semua area penyimpanan (10 Register Int + 8 register FP + 8 register input).
save_next
harus mengikuti simpan untuk pasangan register volatil Int atau FP: save_regp
, , save_regp_x
, save_fregp
save_fregp_x
, save_r19r20_x
, atau lainnya save_next
. Ini menghemat pasangan register berikutnya di slot 16-byte berikutnya dalam urutan "tumbuh besar". save_next
Mengacu pada pasangan register FP pertama ketika mengikuti save-next
yang menunjukkan pasangan register Int terakhir.
Karena ukuran instruksi pengembalian dan lompat reguler sama, tidak perlu kode unwind yang dipisahkan end
dalam skenario tail-call.
end_c
dirancang untuk menangani fragmen fungsi yang tidak bersebelahan untuk tujuan pengoptimalan. end_c
Yang menunjukkan akhir kode unwind dalam cakupan saat ini harus diikuti oleh serangkaian kode unwind lain yang berakhir dengan nyata end
. Kode unwind antara end_c
dan end
mewakili operasi prolog di wilayah induk (prolog "phantom"). Detail dan contoh selengkapnya dijelaskan di bagian di bawah ini.
Data unwind yang dikemas
Untuk fungsi yang prolog dan epilognya mengikuti bentuk kanonis yang dijelaskan di bawah ini, data unwind yang dikemas dapat digunakan. Ini menghilangkan kebutuhan akan .xdata
catatan sepenuhnya, dan secara signifikan mengurangi biaya penyediaan data yang dilepaskan. Prolog dan epilog kanonis dirancang untuk memenuhi persyaratan umum fungsi sederhana: Yang tidak memerlukan handler pengecualian, dan yang melakukan operasi penyiapan dan teardown dalam urutan standar.
Format .pdata
rekaman dengan data unwind yang dikemas terlihat seperti ini:
Bidangnya adalah sebagai berikut:
- Function Start RVA adalah RVA 32-bit dari awal fungsi.
- Bendera adalah bidang 2-bit seperti yang dijelaskan di atas, dengan arti berikut:
- 00 = data unwind yang dikemas tidak digunakan; bit yang tersisa menunjuk ke
.xdata
rekaman - 01 = data unwind kemasan yang digunakan dengan satu prolog dan epilog di awal dan akhir cakupan
- 10 = data unwind kemasan yang digunakan untuk kode tanpa prolog dan epilog. Berguna untuk menjelaskan segmen fungsi yang dipisahkan
- 11 = dicadangkan.
- 00 = data unwind yang dikemas tidak digunakan; bit yang tersisa menunjuk ke
- Panjang Fungsi adalah bidang 11-bit yang menyediakan panjang seluruh fungsi dalam byte, dibagi 4. Jika fungsi lebih besar dari 8k, rekaman lengkap
.xdata
harus digunakan sebagai gantinya. - Ukuran Bingkai adalah bidang 9-bit yang menunjukkan jumlah byte tumpukan yang dialokasikan untuk fungsi ini, dibagi 16. Fungsi yang mengalokasikan tumpukan yang lebih besar dari (8k-16) harus menggunakan rekaman lengkap
.xdata
. Ini termasuk area variabel lokal, area parameter keluar, area Int dan FP yang disimpan callee, dan area parameter rumah. Ini mengecualikan area alokasi dinamis. - CR adalah bendera 2-bit yang menunjukkan apakah fungsi menyertakan instruksi tambahan untuk menyiapkan rantai bingkai dan tautan pengembalian:
- 00 = fungsi tidak ter-rantai,
<x29,lr>
pasangan tidak disimpan dalam tumpukan - 01 = fungsi tidak ter-rantai,
<lr>
disimpan dalam tumpukan - 10 = fungsi berantai dengan alamat pengembalian yang
pacibsp
ditandatangani - 11 = fungsi berantai, instruksi store/load pair digunakan dalam prolog/epilog
<x29,lr>
- 00 = fungsi tidak ter-rantai,
- H adalah bendera 1-bit yang menunjukkan apakah fungsi menampung daftar parameter bilangan bulat (x0-x7) dengan menyimpannya di awal fungsi. (0 = tidak mendaftar di rumah, 1 = homes register).
- RegI adalah bidang 4-bit yang menunjukkan jumlah register INT non-volatil (x19-x28) yang disimpan di lokasi tumpukan kanonis.
- RegF adalah bidang 3-bit yang menunjukkan jumlah register FP non-volatil (d8-d15) yang disimpan di lokasi tumpukan kanonis. (RegF=0: tidak ada register FP yang disimpan; RegF>0: RegF+1 register FP disimpan). Data unwind yang dikemas tidak dapat digunakan untuk fungsi yang hanya menyimpan satu register FP.
Prolog kanonis yang termasuk dalam kategori 1, 2 (tanpa area parameter keluar), 3 dan 4 di bagian di atas dapat diwakili dengan format unwind yang dikemas. Epilog untuk fungsi kanonis mengikuti bentuk yang sama, kecuali H tidak berpengaruh, set_fp
instruksi dihilangkan, dan urutan langkah-langkah dan instruksi di setiap langkah dibalik dalam epilog. Algoritma untuk dikemas .xdata
mengikuti langkah-langkah ini, yang dirinci dalam tabel berikut:
Langkah 0: Pra-komputasi ukuran setiap area.
Langkah 1: Tanda tangani alamat pengembalian.
Langkah 2: Simpan register int callee-saved.
Langkah 3: Langkah ini khusus untuk tipe 4 di bagian awal. lr
disimpan di akhir area Int.
Langkah 4: Simpan register FP yang disimpan callee.
Langkah 5: Simpan argumen input di area parameter beranda.
Langkah 6: Alokasikan tumpukan yang tersisa, termasuk area lokal, <x29,lr>
pasangan, dan area parameter keluar. 6a sesuai dengan tipe kanonis 1. 6b dan 6c adalah untuk kanonis tipe 2. 6d dan 6e adalah untuk tipe 3 dan tipe 4.
Langkah # | Bendera nilai | # dari instruksi | Opcode | Lepaskan kode |
---|---|---|---|---|
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 |
* Jika CR == 01 dan RegI adalah angka ganjil, langkah 3 dan yang terakhir save_reg
di langkah 2 digabungkan menjadi satu save_regp
.
** Jika RegI == CR == 0, dan RegF != 0, yang pertama stp
untuk floating-point melakukan pendahuluan.
Tidak ada instruksi yang sesuai dengan mov x29,sp
yang ada dalam epilog. Data unwind yang dikemas tidak dapat digunakan jika fungsi memerlukan pemulihan sp
dari x29
.
Melepas prolog dan epilog parsial
Dalam situasi unwinding yang paling umum, pengecualian atau panggilan terjadi di tubuh fungsi, jauh dari prolog dan semua epilog. Dalam situasi ini, melepas lelah sangat mudah: unwinder hanya menjalankan kode dalam array unwind. Ini dimulai pada indeks 0 dan berlanjut sampai end
opcode terdeteksi.
Lebih sulit untuk melepas lelah dengan benar jika terjadi pengecualian atau gangguan saat menjalankan prolog atau epilog. Dalam situasi ini, bingkai tumpukan hanya dibangun sebagian. Masalahnya adalah menentukan dengan tepat apa yang telah dilakukan, untuk membatalkannya dengan benar.
Misalnya, ambil urutan prolog dan epilog ini:
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
Di samping setiap opcode adalah kode unwind yang sesuai yang menjelaskan operasi ini. Anda dapat melihat bagaimana rangkaian kode unwind untuk prolog adalah gambar cermin yang tepat dari kode unwind untuk epilog (tidak menghitung instruksi akhir epilog). Ini adalah situasi umum: Itulah sebabnya kami selalu menganggap kode unwind untuk prolog disimpan dalam urutan terbalik dari urutan eksekusi prolog.
Jadi, untuk prolog dan epilog, kita dibiarkan dengan sekumpulan kode unwind umum:
set_fp
, , save_regp 0,240
save_fregp,0,224
, , save_fplr_x_256
,end
Kasus epilog mudah, karena dalam urutan normal. Mulai dari offset 0 dalam epilog (yang dimulai pada offset 0x100 dalam fungsi), kami akan mengharapkan urutan unwind penuh untuk dijalankan, karena belum ada pembersihan yang dilakukan. Jika kita menemukan diri kita satu instruksi di (pada offset 2 dalam epilog), kita dapat berhasil melepas lelah dengan melewati kode unwind pertama. Kita dapat menggeneralisasi situasi ini, dan mengasumsikan pemetaan 1:1 antara kode opcode dan unwind. Kemudian, untuk mulai melepas lelah dari instruksi n dalam epilog, kita harus melewati kode n unwind pertama, dan mulai mengeksekusi dari sana.
Ternyata logika serupa berfungsi untuk prolog, kecuali sebaliknya. Jika kita mulai melepas lelah dari offset 0 dalam prolog, kita ingin mengeksekusi apa-apa. Jika kita melepas kelelahan dari offset 2, yang merupakan salah satu instruksi di, maka kita ingin mulai menjalankan urutan unwind satu kode unwind dari akhir. (Ingat, kode disimpan dalam urutan terbalik.) Dan di sini juga, kita dapat menggeneralisasi: jika kita mulai melepas lelah dari instruksi n di prolog, kita harus mulai mengeksekusi kode n unwind dari akhir daftar kode.
Kode prolog dan epilog tidak selalu cocok persis, itulah sebabnya array unwind mungkin perlu berisi beberapa urutan kode. Untuk menentukan offset tempat untuk mulai memproses kode, gunakan logika berikut:
Jika melepas lelah dari dalam isi fungsi, mulai eksekusi kode unwind pada indeks 0 dan lanjutkan
end
hingga mencapai opcode.Jika melepas lelah dari dalam epilog, gunakan indeks awal khusus epilog yang disediakan dengan cakupan epilog sebagai titik awal. Komputasi berapa banyak byte PC yang dimaksud dari awal epilog. Kemudian maju melalui kode unwind, melompati kode unwind sampai semua instruksi yang sudah dijalankan diperhitungkan. Kemudian jalankan mulai pada saat itu.
Jika melepas lelah dari dalam prolog, gunakan indeks 0 sebagai titik awal Anda. Komputasi panjang kode prolog dari urutan, lalu komputasi berapa banyak byte PC yang dimaksud dari akhir prolog. Kemudian maju melalui kode unwind, melompati kode unwind sampai semua instruksi yang belum dijalankan diperhitungkan. Kemudian jalankan mulai pada saat itu.
Aturan ini berarti kode unwind untuk prolog harus selalu menjadi yang pertama dalam array. Dan, mereka juga kode yang digunakan untuk melepas lelah dalam kasus umum melepas lelah dari dalam tubuh. Setiap urutan kode khusus epilog harus segera diikuti setelahnya.
Fragmen fungsi
Untuk tujuan pengoptimalan kode dan alasan lain, mungkin lebih baik membagi fungsi menjadi fragmen terpisah (juga disebut wilayah). Saat dipisahkan, setiap fragmen fungsi yang dihasilkan memerlukan rekaman terpisah .pdata
sendiri (dan mungkin .xdata
) .
Untuk setiap fragmen sekunder terpisah yang memiliki prolog sendiri, diharapkan tidak ada penyesuaian tumpukan yang dilakukan dalam prolog-nya. Semua ruang tumpukan yang diperlukan oleh wilayah sekunder harus dialokasikan sebelumnya oleh wilayah induknya (atau disebut wilayah host). Pra-alokasi ini menjaga manipulasi penunjuk tumpukan secara ketat dalam prolog asli fungsi.
Kasus umum fragmen fungsi adalah "pemisahan kode", di mana pengkompilasi dapat memindahkan wilayah kode dari fungsi host-nya. Ada tiga kasus tidak biasa yang dapat dihasilkan dari pemisahan kode.
Contoh
(wilayah 1: mulai)
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 ...
(wilayah 1: akhir)
(wilayah 3: mulai)
...
(wilayah 3: akhir)
(wilayah 2: mulai)
... 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
(wilayah 2: berakhir)
Hanya prolog (wilayah 1: semua epilog berada di wilayah yang dipisahkan):
Hanya prolog yang harus dijelaskan. Prolog ini tidak dapat diwakili dalam format ringkas
.pdata
. Dalam kasus lengkapnya.xdata
, itu dapat diwakili dengan mengatur Jumlah Epilog = 0. Lihat wilayah 1 dalam contoh di atas.Lepaskan kode:
set_fp
, ,save_regp 0,240
,save_fplr_x_256
end
.Epilog saja (wilayah 2: prolog berada di wilayah host)
Diasumsikan bahwa pada kontrol waktu melompat ke wilayah ini, semua kode prolog telah dijalankan. Kelelahan parsial dapat terjadi dalam epilog dengan cara yang sama seperti dalam fungsi normal. Jenis wilayah ini tidak dapat diwakili oleh ringkas
.pdata
. Dalam catatan lengkap.xdata
, itu dapat dikodekan dengan prolog "phantom", dikurung olehend_c
pasangan kode danend
melepas lelah. Di depanend_c
menunjukkan ukuran prolog adalah nol. Indeks awal Epilog dari satu epilog menunjuk keset_fp
.Lepaskan kode untuk wilayah 2:
end_c
, ,set_fp
,save_regp 0,240
,save_fplr_x_256
end
.Tidak ada prolog atau epilog (wilayah 3: prolog dan semua epilog berada di fragmen lain):
Format ringkas
.pdata
dapat diterapkan melalui pengaturan Bendera = 10. Dengan catatan lengkap.xdata
, Jumlah Epilog = 1. Kode unwind sama dengan kode untuk wilayah 2 di atas, tetapi Indeks Mulai Epilog juga menunjuk keend_c
. Unwind parsial tidak akan pernah terjadi di wilayah kode ini.
Kasus fragmen fungsi lain yang lebih rumit adalah "menyusutkan pembungkusan." Pengkompilasi dapat memilih untuk menunda penyimpanan beberapa register yang disimpan callee hingga di luar prolog entri fungsi.
(wilayah 1: mulai)
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 ...
(wilayah 2: mulai)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(wilayah 2: berakhir)
... 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
(wilayah 1: akhir)
Dalam prolog wilayah 1, ruang tumpukan telah dialokasikan sebelumnya. Anda dapat melihat bahwa wilayah 2 akan memiliki kode unwind yang sama bahkan dipindahkan dari fungsi host-nya.
Wilayah 1: set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Indeks Mulai Epilog menunjuk ke set_fp
seperti biasa.
Wilayah 2: save_regp 2, 224
, , end_c
set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Indeks Mulai Epilog menunjuk ke kode save_regp 2, 224
unwind pertama .
Fungsi besar
Fragmen dapat digunakan untuk menggambarkan fungsi yang lebih besar dari batas 1M yang diberlakukan oleh bidang bit di .xdata
header. Untuk menggambarkan fungsi yang luar biasa besar seperti ini, fungsi ini perlu dipecah menjadi fragmen yang lebih kecil dari 1M. Setiap fragmen harus disesuaikan sehingga tidak membagi epilog menjadi beberapa bagian.
Hanya fragmen pertama fungsi yang akan berisi prolog; semua fragmen lainnya ditandai sebagai tidak memiliki prolog. Tergantung pada jumlah epilog yang ada, setiap fragmen mungkin berisi nol atau lebih epilog. Perlu diingat bahwa setiap cakupan epilog dalam fragmen menentukan offset awal relatif terhadap awal fragmen, bukan awal fungsi.
Jika fragmen tidak memiliki prolog dan tidak ada epilog, itu masih memerlukan catatan sendiri .pdata
(dan mungkin .xdata
) , untuk menjelaskan cara melepas lelah dari dalam isi fungsi.
Contoh
Contoh 1: Frame-chained, compact-form
|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]
Contoh 2: Frame-chained, full-form dengan cermin Prolog & Epilog
|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 Start Index [0] menunjuk ke urutan kode unwind Prolog yang sama.
Contoh 3: Fungsi Variadic unchained
|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 Start Index [4] menunjuk ke tengah kode unwind Prolog (sebagian menggunakan kembali unwind array).
Lihat juga
Gambaran umum konvensi ABI ARM64
Penanganan pengecualian ARM