Bagikan melalui


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 aslinya sp 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

Diagram yang memperlihatkan tata letak bingkai tumpukan untuk fungsi.

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.

  1. 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)
    
  2. 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
    
  3. 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.

  4. 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-lr stp yang diindeks sebelumnya tidak dapat diwakili dengan kode unwind.

    Semua penduduk lokal diakses berdasarkan sp. <x29> menunjuk ke bingkai sebelumnya.

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

  6. 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 berdasarkan sp.

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

Tata letak rekaman .pdata.

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:

Tata letak rekaman .xdata.

Data ini dipecah menjadi empat bagian:

  1. 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:

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

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

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

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

  4. 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 stpstr/alamat kecuali save_r19r20_x, di mana 248 cukup untuk semua area penyimpanan (10 Register Int + 8 register FP + 8 register input).

save_nextharus mengikuti simpan untuk pasangan register volatil Int atau FP: save_regp, , save_regp_x, save_fregpsave_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:

Catatan .pdata dengan data unwind yang dikemas.

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.
  • 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>
  • 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,240save_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:

  1. Jika melepas lelah dari dalam isi fungsi, mulai eksekusi kode unwind pada indeks 0 dan lanjutkan end hingga mencapai opcode.

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

  3. 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)

  1. 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_256end.

  2. 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 oleh end_c pasangan kode dan end melepas lelah. Di depan end_c menunjukkan ukuran prolog adalah nol. Indeks awal Epilog dari satu epilog menunjuk ke set_fp.

    Lepaskan kode untuk wilayah 2: end_c, , set_fp, save_regp 0,240, save_fplr_x_256end.

  3. 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 ke end_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_cset_fp, save_regp 0,240, save_fplr_x_256, end. Indeks Mulai Epilog menunjuk ke kode save_regp 2, 224unwind 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