Apakah instruksi x86 memerlukan penyandian sendiri dan semua argumennya harus ada di memori pada saat yang sama?

64

Saya mencoba mencari tahu apakah mungkin untuk menjalankan VM Linux yang RAM-nya hanya didukung oleh satu halaman fisik.

Untuk mensimulasikan ini, saya memodifikasi penangan kesalahan halaman bersarang di KVM untuk menghapus bit sekarang dari semua entri tabel halaman bersarang (NPT), kecuali yang sesuai dengan kesalahan halaman yang sedang diproses.

Ketika mencoba untuk memulai tamu Linux, saya mengamati bahwa instruksi perakitan yang menggunakan operan memori, seperti

add [rbp+0x820DDA], ebp

mengarah ke loop kesalahan halaman sampai saya mengembalikan bit sekarang untuk halaman yang berisi instruksi serta untuk halaman yang direferensikan dalam operan (dalam contoh ini [rbp+0x820DDA]).

Saya bertanya-tanya mengapa ini terjadi. Bukankah seharusnya CPU mengakses halaman memori secara berurutan, yaitu pertama membaca instruksi dan kemudian mengakses operan memori? Atau apakah x86 mensyaratkan bahwa halaman instruksi serta semua halaman operan dapat diakses pada saat yang sama?

Saya menguji AMD Zen 1.

savvybug
sumber
2
Mengapa Anda ingin melakukan ini?
SS Anne
11
Hanya karena minat teknis :)
savvybug
14
Upvoting untuk ide proyek lucu.
pipa
10
Ini gila pada level "boot Linux pada emulator 486 yang berjalan dalam JavaScript di browser". Aku menyukainya.
chrylis -pada mogok-
3
Heh, tampaknya saya mengambil pertanyaan ini ke kesimpulan logis yang sama yang sudah Anda pikirkan, tentang perangkat kerja minimum untuk kemajuan maju yang dijamin. Saya sudah menjawabnya sebelum Anda menambahkan paragraf pertama yang baru ke pertanyaan. : PI menambahkan beberapa tautan dan lebih banyak detail di beberapa tempat (misal, walker-halaman diperbolehkan untuk men-cache beberapa entri direktori-halaman tamu secara internal) karena pertanyaan ini mendapatkan perhatian yang jauh lebih besar daripada yang saya harapkan berkat cara membuatnya ke HNQ.
Peter Cordes

Jawaban:

56

Ya, mereka memang membutuhkan kode mesin dan semua operan memori.

Bukankah seharusnya CPU mengakses halaman memori secara berurutan, yaitu pertama membaca instruksi dan kemudian mengakses operan memori?

Ya itulah yang secara logis terjadi, tetapi pengecualian kesalahan halaman mengganggu proses 2 langkah dan membuang kemajuan apa pun. CPU tidak memiliki cara untuk mengingat instruksi apa yang ada di tengah-tengah saat terjadi kesalahan halaman.

Ketika penangan kesalahan halaman kembali setelah menangani kesalahan halaman yang valid, RIP = alamat instruksi yang salah, sehingga CPU mencoba mengeksekusinya dari awal .

Merupakan hal yang sah bagi OS untuk memodifikasi kode mesin dari instruksi yang salah dan mengharapkannya untuk menjalankan instruksi yang berbeda setelah iretdari penangan-kesalahan halaman (atau pengecualian lain atau interrupt handler). Jadi AFAIK secara arsitektural diperlukan agar CPU mengulang pengambilan kode dari CS: RIP jika Anda membicarakannya. (Menganggapnya bahkan kembali ke CS rusak: RIP alih-alih menjadwalkan proses lain sambil menunggu disk pada kesalahan halaman keras, atau mengirimkan SIGSEGV ke penangan sinyal pada kesalahan halaman yang tidak valid.)

Mungkin juga diperlukan secara arsitektur untuk masuk / keluar hypervisor. Dan bahkan jika itu tidak secara eksplisit dilarang di atas kertas, itu bukan cara kerja CPU.

@torek berkomentar bahwa Some (CISC) mikroprosesor mendekode sebagian instruksi dan membuang status microregister pada kesalahan halaman , tetapi x86 tidak seperti itu.


Beberapa instruksi bersifat interruptible dan dapat membuat kemajuan parsial, seperti rep movs(memcpy dalam kaleng) dan instruksi string lainnya, atau mengumpulkan banyak / menyimpan toko. Tetapi satu-satunya mekanisme memperbarui register arsitektur seperti RCX / RSI / RDI untuk string ops, atau register tujuan dan topeng untuk kumpulkan (misalnya manual untuk AVX2vpgatherdd ). Tidak menyimpan opcode / decode menghasilkan beberapa register internal tersembunyi dan memulai kembali setelah menerima dari penangan kesalahan halaman. Ini adalah instruksi yang melakukan beberapa akses data terpisah.

Juga perlu diingat bahwa x86 (seperti kebanyakan ISA) menjamin bahwa instruksi adalah atomic wrt. interupsi / pengecualian: mereka sepenuhnya terjadi, atau tidak terjadi sama sekali, sebelum interupsi. Menginterupsi instruksi perakitan saat sedang beroperasi . Jadi misalnya add [mem], regakan diminta untuk membuang beban jika bagian toko rusak, bahkan tanpa lockawalan.


Jumlah kasus terburuk dari halaman ruang pengguna tamu yang hadir untuk membuat kemajuan mungkin 6 (ditambah sub-tabel tabel-kernel tamu terpisah untuk masing-masing):

  • movsqatau movswinstruksi 2-byte yang mencakup batas halaman, sehingga kedua halaman diperlukan untuk memecahkan kode.
  • Operan sumber qword [rsi]juga merupakan pemisah halaman
  • operan tujuan qword [rdi]juga pemisah halaman

Jika salah satu dari 6 halaman ini salah, kita kembali ke titik awal.

rep movsdjuga merupakan instruksi 2-byte, dan membuat kemajuan pada satu langkah akan memiliki persyaratan yang sama. Kasus serupa suka push [mem]atau pop [mem]dapat dibangun dengan tumpukan yang tidak selaras.

Salah satu alasan (atau manfaat sampingan) untuk / membuat pengumpulan mengumpulkan / menyebarkan toko "interruptible" (memperbarui vektor topeng dengan kemajuan mereka) adalah untuk menghindari peningkatan jejak minimum ini untuk menjalankan instruksi tunggal. Juga untuk meningkatkan efisiensi penanganan beberapa kesalahan selama satu pertemuan atau pencar.


@Brandon menunjukkan dalam komentar bahwa seorang tamu akan membutuhkan tabel halamannya dalam memori , dan pemisahan halaman ruang pengguna juga dapat berupa pemisahan 1GiB sehingga kedua sisi berada dalam sub-pohon yang berbeda dari PML4 tingkat atas. HW page walk perlu menyentuh semua halaman tabel-tabel tamu ini untuk membuat kemajuan. Situasi patologis ini tidak mungkin terjadi secara kebetulan.

TLB (dan internal page-walker) diizinkan untuk men-cache beberapa data tabel-halaman, dan tidak diharuskan untuk memulai kembali page-walk dari awal kecuali OS melakukan invlpgatau menetapkan direktori halaman level atas CR3 yang baru. Tidak satu pun dari ini diperlukan ketika mengubah halaman dari tidak-hadir ke sekarang; x86 di atas kertas menjamin bahwa itu tidak diperlukan (jadi "caching negatif" dari PTE yang tidak hadir tidak diizinkan, setidaknya tidak terlihat oleh perangkat lunak). Jadi CPU mungkin tidak VMexit bahkan jika beberapa halaman fisik halaman tamu tidak benar-benar ada.

Penghitung kinerja PMU dapat diaktifkan dan dikonfigurasikan sedemikian rupa sehingga instruksi tersebut juga memerlukan acara perf untuk menulis ke dalam buffer PEBS untuk instruksi itu. Dengan topeng penghitung yang dikonfigurasikan untuk hanya menghitung instruksi ruang pengguna, bukan kernel, bisa jadi ia terus mencoba meluap penghitung dan menyimpan sampel dalam buffer setiap kali Anda kembali ke ruang pengguna, menghasilkan kesalahan halaman.

Peter Cordes
sumber
15
Kasus terburuk untuk satu instruksi mungkin kira-kira seperti " push dword [foo" (atau bahkan adil call [foo]) dengan segala yang tidak selaras di "batas tabel penunjuk direktori halaman" (menambahkan hingga 6 halaman, 6 halaman tabel, 6 halaman direktori, 6 PDPT, dan satu PML4); dengan fitur "pengambilan sampel berbasis peristiwa yang tepat dengan buffer PEBS" diaktifkan dan dikonfigurasi sehingga pushmenyebabkan data pemantauan kinerja ditambahkan ke buffer PEBS. Untuk konservatif "halaman minimum yang disediakan oleh tuan rumah sehingga tamu dapat membuat kemajuan dalam kasus patologis" Saya ingin setidaknya 16 halaman.
Brendan
4
Perhatikan bahwa hal semacam ini selalu umum dalam arsitektur CISC-y. Beberapa mikroprosesor mendekode sebagian instruksi dan membuang status microregister pada kesalahan halaman, tetapi yang lain tidak dan / atau mengharuskan operan alamat untuk instruksi "loop-y" (DBRA pada m68k, MOVC3 / MOVC5 pada Vax, dll) berada dalam register yang sama untuk contoh REP MOVS Anda.
torek
1
@ Brendan: seseorang menghitung kasus terburuk pada instruksi VAX sekitar 50 halaman. Saya lupa detailnya, tetapi Anda jelas akan menempatkan instruksi itu sendiri pada batas halaman, menggunakan sesuatu seperti pencarian tabel terjemahan dengan tabel yang mencakup batas halaman, gunakan (rX) [rY] dengan petunjuk di batas halaman, dan begitu seterusnya. Instruksi hairiest memakan waktu hingga 6 operan (memuatnya ke r0-r5) dan keenamnya bisa menjadi indirek ganda, saya pikir.
torek
3
OS bisa mengubah instruksi, tetapi juga bisa berubah EIP. Jadi ada pertanyaan tindak lanjut yang logis. Berapa jumlah minimum halaman yang dibutuhkan, dengan asumsi skema patch instruksi cerdas? Misalnya, salin nilai yang tidak selaras ke buffer awal yang disejajarkan, meniru instruksi, dan IRET ke instruksi berikutnya.
MSalters
1
Halaman yang berisi iretinstruksi OS juga harus ada dalam memori. Ini adalah instruksi satu byte, jadi satu halaman tambahan. Alamat interupsi penangan kesalahan halaman juga harus ada dalam memori, tetapi itu bisa menjadi halaman yang sama seperti di atas.
Stig Hemmer