Namun demikian, ada satu hal yang ingin saya bicarakan lebih detail.
Di artikel sebelumnya, kami menulis bahwa sebagai bagian dari implementasi dukungan SGX, layanan Nova perlu diajarkan untuk menghasilkan file XML dengan pengaturan yang diperlukan untuk domain tamu. Masalah ini ternyata kompleks dan menarik: selama mengerjakan solusinya, kami harus memahami secara detail, menggunakan contoh libvirt, bagaimana program secara umum berinteraksi dengan set instruksi dalam prosesor x86. Ada sangat, sangat sedikit materi yang terperinci dan yang paling penting - materi yang ditulis dengan jelas tentang topik ini. Kami berharap pengalaman kami bermanfaat bagi semua orang yang terlibat dalam virtualisasi. Namun, hal pertama yang pertama.
Upaya pertama
Mari kita ulangi perumusan tugasnya sekali lagi: kita perlu meneruskan parameter dukungan SGX ke file konfigurasi XML dari mesin virtual. Ketika kami baru mulai memecahkan masalah ini, tidak ada dukungan SGX di OpenStack dan libvirt, masing-masing, tidak mungkin untuk mentransfernya ke XML mesin virtual secara native.
Kami pertama kali mencoba memecahkan masalah ini dengan menambahkan blok baris perintah Qemu ke skrip untuk terhubung ke hypervisor melalui libvirt, seperti yang dijelaskan dalam panduan pengembang Intel:
<qemu:commandline>
<qemu:arg value='-cpu'/>
<qemu:arg value='host,+sgx,+sgxlc'/>
<qemu:arg value='-object'/>
<qemu:arg value='memory-backend-epc,id=mem1,size=''' + epc + '''M,prealloc'/>
<qemu:arg value='-sgx-epc'/>
<qemu:arg value='id=epc1,memdev=mem1'/>
</qemu:commandline>
Tetapi setelah itu, opsi prosesor kedua ditambahkan ke mesin virtual:
[root@compute-sgx ~] cat /proc/$PID/cmdline |xargs -0 printf "%s\n" |awk '/cpu/ { getline x; print $0 RS x; }'
-cpu
Skylake-Client-IBRS
-cpu
host,+sgx,+sgxlc
Opsi pertama disetel secara normal, dan opsi kedua ditambahkan secara langsung oleh kami di blok baris perintah Qemu . Hal ini menyebabkan ketidaknyamanan saat memilih model emulasi prosesor: model prosesor mana pun yang kami gantikan menjadi cpu_model di file konfigurasi node komputasi Nova, kami melihat tampilan prosesor host di mesin virtual.
Bagaimana cara mengatasi masalah ini?
Dalam mencari jawaban, pertama-tama kami mencoba bereksperimen dengan baris < qemu: arg value = 'host, + sgx, + sgxlc'/> dan mencoba mentransfer model prosesor ke sana, tetapi ini tidak membatalkan duplikasi opsi ini setelah VM dimulai. Kemudian diputuskan untuk menggunakan libvirt untuk menetapkan flag CPU dan mengontrolnya melalui file konfigurasi Nov'y dari node komputasi menggunakan parameter cpu_model_extra_flags .
Tugas tersebut ternyata lebih sulit dari yang kami harapkan: kami perlu mempelajari instruksi Intel IA-32 - CPUID, serta menemukan informasi tentang register dan bit yang diperlukan dalam dokumentasi Intel di SGX.
Pencarian lebih lanjut: menggali lebih dalam libvirt
Dokumentasi pengembang untuk layanan Nova menyatakan bahwa pemetaan bendera CPU harus didukung oleh libvirt itu sendiri.
Kami menemukan file yang mendeskripsikan semua flag CPU - ini adalah x86_features.xml (relevan sejak libvirt 4.7.0). Setelah meninjau file ini, kami berasumsi (ternyata kemudian, secara keliru) bahwa kami hanya perlu mendapatkan alamat hex dari register yang diperlukan di lembar ke-7 menggunakan utilitas cpuid. Dari dokumentasi Intel, kita belajar di register mana instruksi yang kita butuhkan disebut: sgx ada di register EBX, dan sgxlc ada di ECX.
[root@compute-sgx ~] cpuid -l 7 -1 |grep SGX
SGX: Software Guard Extensions supported = true
SGX_LC: SGX launch config supported = true
[root@compute-sgx ~] cpuid -l 7 -1 -r
CPU:
0x00000007 0x00: eax=0x00000000 ebx=0x029c6fbf ecx=0x40000000 edx=0xbc000600
Setelah menambahkan flag sgx dan sgxlc dengan nilai yang diperoleh menggunakan utilitas cpuid, kami menerima pesan kesalahan berikut:
error : x86Compute:1952 : out of memory
Pesannya, terus terang, tidak terlalu informatif. Untuk memahami apa masalahnya, kami membuka masalah di gitlab libvirt. Pengembang libvirt memperhatikan bahwa kesalahan yang salah ditampilkan dan memperbaikinya, menunjukkan bahwa libvirt tidak dapat menemukan instruksi yang benar yang kami panggil dan menyarankan di mana kami mungkin salah. Tetapi untuk memahami apa sebenarnya yang perlu kami tunjukkan agar tidak ada kesalahan, kami tidak berhasil.
Saya harus menggali sumber dan belajar, butuh waktu lama. Itu mungkin untuk mengetahuinya hanya setelah mempelajari kode dalam Qemu yang dimodifikasi dari Intel:
[FEAT_7_0_EBX] = {
.type = CPUID_FEATURE_WORD,
.feat_names = {
"fsgsbase", "tsc-adjust", "sgx", "bmi1",
"hle", "avx2", NULL, "smep",
"bmi2", "erms", "invpcid", "rtm",
NULL, NULL, "mpx", NULL,
"avx512f", "avx512dq", "rdseed", "adx",
"smap", "avx512ifma", "pcommit", "clflushopt",
"clwb", "intel-pt", "avx512pf", "avx512er",
"avx512cd", "sha-ni", "avx512bw", "avx512vl",
},
.cpuid = {
.eax = 7,
.needs_ecx = true, .ecx = 0,
.reg = R_EBX,
},
.tcg_features = TCG_7_0_EBX_FEATURES,
},
[FEAT_7_0_ECX] = {
.type = CPUID_FEATURE_WORD,
.feat_names = {
NULL, "avx512vbmi", "umip", "pku",
NULL /* ospke */, "waitpkg", "avx512vbmi2", NULL,
"gfni", "vaes", "vpclmulqdq", "avx512vnni",
"avx512bitalg", NULL, "avx512-vpopcntdq", NULL,
"la57", NULL, NULL, NULL,
NULL, NULL, "rdpid", NULL,
NULL, "cldemote", NULL, "movdiri",
"movdir64b", NULL, "sgxlc", NULL,
},
.cpuid = {
.eax = 7,
.needs_ecx = true, .ecx = 0,
.reg = R_ECX,
},
Dari daftar di atas, Anda dapat melihat bahwa di blok .feat_names , instruksi dari register EBX / ECX dari lembar ke-7 terdaftar sedikit demi sedikit (dari 0 hingga 31); jika instruksi tidak didukung oleh Qemu atau bit ini dicadangkan, maka itu akan diisi dengan nilai NULL . Berkat contoh ini, kami membuat asumsi berikut: mungkin kami perlu menentukan bukan alamat hex dari register yang diperlukan di libvirt, tetapi secara khusus bit dari instruksi ini. Lebih mudah untuk memahami ini dengan membaca tabel dari Wikipedia . Di sebelah kiri adalah sedikit dan tiga register. Kami menemukan instruksi kami di dalamnya - sgx. Dalam tabel, ini ditunjukkan di bawah bit kedua di register EBX:
Selanjutnya, kami memeriksa lokasi instruksi ini di kode Qemu. Seperti yang bisa kita lihat, dia adalah yang ketiga dalam daftar feat_names, tetapi ini karena penomoran bit dimulai dari 0:
[FEAT_7_0_EBX] = {
.type = CPUID_FEATURE_WORD,
.feat_names = {
"fsgsbase", "tsc-adjust", "sgx", "bmi1",
Anda dapat melihat instruksi lain dalam tabel ini dan memastikan, ketika menghitung dari 0, bahwa mereka berada di bawah bit mereka sendiri dalam daftar yang diberikan. Misalnya: fsgsbase berada di bawah bit 0 dari register EBX dan terdaftar terlebih dahulu.
Dalam dokumentasi Intel, kami menemukan konfirmasi ini dan memastikan bahwa set instruksi yang diperlukan dapat dipanggil menggunakan cpuid, meneruskan bit yang benar saat mengakses register sheet yang diinginkan, dan dalam beberapa kasus, sublist.
Kami mulai memahami lebih detail arsitektur prosesor 32-bit dan melihat bahwa prosesor tersebut memiliki lembaran yang berisi 4 register utama: EAX, EBX, ECX, EDX. Masing-masing register ini berisi 32 bit yang dicadangkan untuk satu set instruksi CPU tertentu. Bit adalah pangkat dua dan paling sering diteruskan ke program dalam format hex, seperti yang dilakukan di libvirt.
Untuk pemahaman yang lebih baik, pertimbangkan contoh lain dengan bendera virtualisasi VMX bersarang dari file x86_features.xml yang digunakan oleh libvirt:
<feature name = 'vmx ' >
<cpuid eax_in = ' 0x01 ' ecx = ' 0x00000020 '/> # 2 5 = 32 10 = 20 16
</ feature> Referensi
untuk instruksi ini dilakukan di lembar pertama ke register ECX di bawah bit 5 dan Anda dapat memverifikasi ini dengan melihat tabel Informasi Fitur di Wikipedia.
Setelah menangani hal ini dan membentuk pemahaman tentang bagaimana flag akhirnya ditambahkan ke libvirt, kami memutuskan untuk menambahkan flag SGX lainnya (selain yang utama: sgx dan sgxlc) yang ada di Qemu yang dimodifikasi:
[root@compute-sgx ~] /usr/libexec/qemu-kvm -cpu help |xargs printf '%s\n' |grep sgx
sgx
sgx-debug
sgx-exinfo
sgx-kss
sgx-mode64
sgx-provisionkey
sgx-tokenkey
sgx1
sgx2
sgxlc
Beberapa dari tanda ini bukan lagi instruksi, tetapi atribut Enclave Data Control Structure (SECS); Anda dapat membaca lebih lanjut tentang ini di dokumentasi Intel. Di dalamnya, kami menemukan bahwa set atribut SGX yang kami butuhkan ada di sheet 0x12 di sublist 1:
[root@compute-sgx ~] cpuid -l 0x12 -s 1 -1 CPU: SGX attributes (0x12/1): ECREATE SECS.ATTRIBUTES valid bit mask = 0x000000000000001f0000000000000036
Pada tangkapan layar Tabel 38-3, Anda dapat menemukan bit atribut yang kita butuhkan, yang akan kita tentukan nanti sebagai flag di libvirt: sgx-debug, sgx-mode64, sgx-provisionkey, sgx-tokenkey. Mereka terletak di bawah bit 1, 2, 4 dan 5.
Kami juga memahami dari jawaban dalam masalah kami : libvirt memiliki makro untuk memeriksa flag untuk dukungan mereka secara langsung oleh prosesor node komputasi. Ini berarti tidak cukup menentukan sheet, bit, dan register yang diperlukan dalam file x86_features.xml jika libvirt sendiri tidak mendukung lembar set instruksi. Namun untungnya bagi kami, ternyata kode libvirt memiliki kemampuan untuk bekerja dengan sheet ini:
/* Leaf 0x12: SGX capability enumeration
*
* Sub leaves 0 and 1 is supported if ebx[2] from leaf 0x7 (SGX) is set.
* Sub leaves n >= 2 are valid as long as eax[3:0] != 0.
*/
static int
cpuidSetLeaf12(virCPUDataPtr data,
virCPUx86DataItemPtr subLeaf0)
{
virCPUx86DataItem item = CPUID(.eax_in = 0x7);
virCPUx86CPUIDPtr cpuid = &item.data.cpuid;
virCPUx86DataItemPtr leaf7;
if (!(leaf7 = virCPUx86DataGet(&data->data.x86, &item)) ||
!(leaf7->data.cpuid.ebx & (1 << 2)))
return 0;
if (virCPUx86DataAdd(data, subLeaf0) < 0)
return -1;
cpuid->eax_in = 0x12;
cpuid->ecx_in = 1;
cpuidCall(cpuid);
if (virCPUx86DataAdd(data, &item) < 0)
return -1;
cpuid->ecx_in = 2;
cpuidCall(cpuid);
while (cpuid->eax & 0xf) {
if (virCPUx86DataAdd(data, &item) < 0)
return -1;
cpuid->ecx_in++;
cpuidCall(cpuid);
}
return 0;
}
Dari daftar ini, Anda dapat melihat bahwa ketika mengakses bit EBX ke-2 dari register daun ke-7 (yaitu instruksi SGX), libvirt dapat menggunakan daun 0x12 untuk memeriksa atribut yang tersedia dalam sublist 0, 1, dan 2.
Kesimpulan
Setelah penelitian selesai, kami menemukan cara menambahkan file x86_features.xml dengan benar. Kami mengubah bit yang diperlukan ke format hex - dan inilah yang kami dapatkan:
<!-- SGX features -->
<feature name='sgx'>
<cpuid eax_in='0x07' ecx_in='0x00' ebx='0x00000004'/>
</feature>
<feature name='sgxlc'>
<cpuid eax_in='0x07' ecx_in='0x00' ecx='0x40000000'/>
</feature>
<feature name='sgx1'>
<cpuid eax_in='0x12' ecx_in='0x00' eax='0x00000001'/>
</feature>
<feature name='sgx-debug'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000002'/>
</feature>
<feature name='sgx-mode64'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000004'/>
</feature>
<feature name='sgx-provisionkey'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000010'/>
</feature>
<feature name='sgx-tokenkey'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000020'/>
</feature>
Sekarang, untuk meneruskan flag ini ke mesin virtual, kita dapat menentukannya di file konfigurasi Nova menggunakan cpu_model_extra_flags :
[root@compute-sgx nova] grep cpu_mode nova.conf
cpu_mode = custom
cpu_model = Skylake-Client-IBRS
cpu_model_extra_flags = sgx,sgxlc,sgx1,sgx-provisionkey,sgx-tokenkey,sgx-debug,sgx-mode64
[root@compute-sgx ~] cat /proc/$PID/cmdline |xargs -0 printf "%s\n" |awk '/cpu/ { getline x; print $0 RS x; }'
-cpu
Skylake-Client-IBRS,sgx=on,sgx-mode64=on,sgx-provisionkey=on,sgx-tokenkey=on,sgx1=on,sgxlc=on
Dengan susah payah, kami belajar bagaimana menambahkan dukungan untuk flag SGX ke libvirt. Ini membantu kami memecahkan masalah duplikasi opsi prosesor dalam file XML mesin virtual. Kami akan menggunakan pengalaman yang diperoleh dalam pekerjaan kami di masa mendatang: jika serangkaian instruksi baru muncul di prosesor Intel atau AMD, kami dapat menambahkannya ke libvirt dengan cara yang sama. Keakraban dengan instruksi CPUID juga akan berguna bagi kami saat menulis solusi kami sendiri.
Jika Anda memiliki pertanyaan - selamat datang di komentar, kami akan mencoba menjawab. Dan jika Anda memiliki sesuatu untuk ditambahkan - terlebih lagi, tulis, kami akan sangat berterima kasih.