Halaman ini menjelaskan cara mengimplementasikan modul vendor mesin virtual berbasis kernel terlindungi (pKVM).
Untuk android16-6.12 dan yang lebih baru, setelah menyelesaikan langkah-langkah ini, Anda akan memiliki pohon direktori yang mirip dengan:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Untuk contoh lengkap, lihat Membuat modul pKVM dengan DDK .
Untuk android15-6.6 dan yang lebih lama:
Makefile
el1.c
hyp/
Makefile
el2.c
Tambahkan kode hypervisor EL2 (
el2.c). Minimal, kode ini harus mendeklarasikan fungsi init yang menerima referensi ke structpkvm_module_ops:#include <asm/kvm_pkvm_module.h> int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops) { /* Init the EL2 code */ return 0; }API modul vendor pKVM adalah struct yang merangkum callback ke hypervisor pKVM. Struct ini mengikuti aturan ABI yang sama dengan antarmuka GKI.
.Buat
hyp/Makefileuntuk membuat kode hypervisor:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleTambahkan kode kernel EL1 (
el1.c). Bagian init kode ini harus berisi panggilan kepkvm_load_el2 moduleuntuk memuat kode hypervisor EL2 dari langkah 1.#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <asm/kvm_pkvm_module.h> int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops); static int __init pkvm_driver_init(void) { unsigned long token; return pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init, &token); } module_init(pkvm_driver_init);Buat aturan build.
Untuk android16-6.12 dan yang lebih baru, lihat Membuat modul pKVM dengan DDK untuk membuat
ddk_library()untuk EL2 danddk_module()untuk EL1.Untuk android15-6.6 dan yang lebih lama, buat makefile root untuk mengikat kode EL1 dan EL2:
ifneq ($(KERNELRELEASE),) clean-files := hyp/hyp.lds hyp/hyp-reloc.S obj-m := pkvm_module.o pkvm_module-y := el1.o hyp/kvm_nvhe.o $(PWD)/hyp/kvm_nvhe.o: FORCE $(Q)$(MAKE) $(build)=$(obj)/hyp $(obj)/hyp/kvm_nvhe.o else all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean endif
Memuat modul pKVM
Seperti modul vendor GKI, modul vendor pKVM dapat dimuat menggunakan modprobe.
Namun, karena alasan keamanan, pemuatan harus terjadi sebelum penghapusan hak istimewa.
Untuk memuat modul pKVM, Anda harus memastikan modul disertakan dalam sistem file root (initramfs) dan Anda harus menambahkan kode berikut ke command line kernel:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Modul vendor pKVM yang disimpan di initramfs mewarisi tanda tangan dan perlindungan initramfs.
Jika salah satu modul vendor pKVM gagal dimuat, sistem akan dianggap tidak aman dan mesin virtual terlindungi tidak dapat dimulai.
Memanggil fungsi EL2 (hypervisor) dari EL1 (modul kernel)
Panggilan hypervisor (HVC) adalah instruksi yang memungkinkan kernel memanggil hypervisor. Dengan diperkenalkannya modul vendor pKVM, HVC dapat digunakan untuk memanggil fungsi yang akan dijalankan di EL2 (dalam modul hypervisor) dari EL1 (modul kernel):
- Dalam kode EL2 (
el2.c), deklarasikan pengendali EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 atau yang lebih baru
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
Dalam kode EL1 (
el1.c), daftarkan pengendali EL2 di modul vendor pKVM:int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops); void __kvm_nvhe_pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx); // Android14 void __kvm_nvhe_pkvm_driver_hyp_hvc(struct user_pt_regs *regs); // Android15 static int hvc_number; static int __init pkvm_driver_init(void) { long token; int ret; ret = pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init,token); if (ret) return ret; ret = pkvm_register_el2_mod_call(__kvm_nvhe_pkvm_driver_hyp_hvc, token) if (ret < 0) return ret; hvc_number = ret; return 0; } module_init(pkvm_driver_init);Dalam kode EL1 (
el1.c), panggil HVC:pkvm_el2_mod_call(hvc_number);
Men-debug dan membuat profil kode EL2
Bagian ini berisi beberapa opsi untuk men-debug kode EL2 modul pKVM.
Mengirim dan membaca peristiwa pelacakan hypervisor
Tracefs mendukung hypervisor pKVM. Pengguna root memiliki akses ke antarmuka, yang terletak di /sys/kernel/tracing/hypervisor/:
tracing_on: Mengaktifkan atau menonaktifkan pelacakan.trace: Menulis ke file ini akan mereset pelacakan.trace_pipe: Membaca file ini akan mencetak peristiwa hypervisor.buffer_size_kb: Ukuran buffer per CPU yang menyimpan peristiwa. Tingkatkan nilai ini jika peristiwa hilang.
Secara default, peristiwa dinonaktifkan. Untuk mengaktifkan peristiwa, gunakan file /sys/kernel/tracing/hypervisor/events/my_event/enable yang sesuai di Tracefs. Anda juga dapat mengaktifkan peristiwa hypervisor saat waktu booting dengan command line kernel
hyp_event=event1,event2.
Sebelum mendeklarasikan peristiwa, kode EL2 modul harus mendeklarasikan boilerplate berikut, dengan pkvm_ops adalah struct pkvm_module_ops * yang diteruskan ke fungsi init modul:
#include "events.h"
#define HYP_EVENT_FILE ../../../../relative/path/to/hyp/events.h
#include <nvhe/define_events.h>
#ifdef CONFIG_TRACING
void *tracing_reserve_entry(unsigned long length)
{
return pkvm_ops->tracing_reserve_entry(length);
}
void tracing_commit_entry(void)
{
pkvm_ops->tracing_commit_entry();
}
#endif
Mendeklarasikan peristiwa
Deklarasikan peristiwa dalam file .h mereka sendiri:
$ cat hyp/events.h
#if !defined(__PKVM_DRIVER_HYPEVENTS_H_) || defined(HYP_EVENT_MULTI_READ)
#define __PKVM_DRIVER_HYPEVENTS_H_
#ifdef __KVM_NVHE_HYPERVISOR__
#include <nvhe/trace.h>
#endif
HYP_EVENT(pkvm_driver_event,
HE_PROTO(u64 id),
HE_STRUCT(
he_field(u64, id)
),
HE_ASSIGN(
__entry->id = id;
),
HE_PRINTK("id=0x%08llx", __entry->id)
);
#endif
Mengirim peristiwa
Anda dapat mencatat peristiwa dalam kode EL2 dengan memanggil fungsi C yang dihasilkan:
trace_pkvm_driver_event(id);
Menambahkan pendaftaran tambahan (Android 15 atau yang lebih lama)
Untuk Android 15 dan yang lebih lama, sertakan pendaftaran tambahan selama inisialisasi modul. Hal ini tidak diperlukan di Android 16 dan yang lebih baru.
#ifdef CONFIG_TRACING
extern char __hyp_event_ids_start[];
extern char __hyp_event_ids_end[];
#endif
int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops)
{
#ifdef CONFIG_TRACING
ops->register_hyp_event_ids((unsigned long)__hyp_event_ids_start,
(unsigned long)__hyp_event_ids_end);
#endif
/* init module ... */
return 0;
}
Mengirim peristiwa tanpa deklarasi sebelumnya (Android 16 dan yang lebih baru)
Mendeklarasikan peristiwa dapat menjadi rumit untuk proses debug cepat. trace_hyp_printk()
memungkinkan pemanggil meneruskan hingga empat argumen ke string format tanpa deklarasi peristiwa apa pun:
trace_hyp_printk("This is my debug");
trace_hyp_printk("This is my variable: %d", (int)foo);
trace_hyp_printk("This is my address: 0x%llx", phys);
Boilerplate dalam kode EL2 juga diperlukan. trace_hyp_printk() adalah makro yang memanggil fungsi trace___hyp_printk():
#include <nvhe/trace.h>
#ifdef CONFIG_TRACING
void trace___hyp_printk(u8 fmt_id, u64 a, u64 b, u64 c, u64 d)
{
pkvm_ops->tracing_mod_hyp_printk(fmt_id, a, b, c, d);
}
#endif
Aktifkan peristiwa __hyp_printk di /sys/kernel/tracing/hypervisor/events/ atau
saat waktu booting dengan command line kernel hyp_event=__hyp_printk.
Mengarahkan peristiwa ke dmesg
Parameter command line kernel hyp_trace_printk=1 membuat antarmuka pelacakan hypervisor meneruskan setiap peristiwa yang dicatat ke dmesg kernel. Hal ini berguna untuk membaca peristiwa saat trace_pipe tidak dapat diakses.
Membuang peristiwa selama kernel panic (Android 16 dan yang lebih baru)
Peristiwa hypervisor di-polling. Oleh karena itu, ada jendela antara polling terakhir dan kernel panic saat peristiwa telah dikirim, tetapi tidak dibuang ke konsol.
Opsi konfigurasi kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC mencoba membuang peristiwa terbaru di konsol jika hyp_trace_printk telah diaktifkan.
Opsi ini dinonaktifkan secara default untuk GKI.
Menggunakan Ftrace untuk melacak panggilan dan pengembalian fungsi (Android 16 dan yang lebih baru)
Ftrace adalah fitur kernel yang memungkinkan Anda melacak setiap panggilan dan pengembalian fungsi.
Dengan cara yang sama, hypervisor pKVM menawarkan dua peristiwa func dan
func_ret.
Anda dapat memilih fungsi yang dilacak dengan command line kernel hyp_ftrace_filter= atau dengan salah satu file tracefs:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Filter menggunakan pencocokan glob gaya shell.
Filter berikut melacak fungsi yang dimulai dengan pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Peristiwa func dan func_ret hanya tersedia dengan CONFIG_PKVM_FTRACE=y.
Opsi ini dinonaktifkan secara default untuk GKI.