Questa pagina spiega come implementare un modulo fornitore di macchine virtuali basate sul kernel protetto (pKVM).
Per android16-6.12 e versioni successive, al termine di questi passaggi, dovresti avere una struttura di directory simile a:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Per un esempio completo, vedi Compilare un modulo pKVM con DDK .
Per android15-6.6 e versioni precedenti:
Makefile
el1.c
hyp/
Makefile
el2.c
Aggiungi il codice dell'hypervisor EL2 (
el2.c). Come minimo, questo codice deve dichiarare una funzione di inizializzazione che accetti un riferimento alla 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; }L'API del modulo fornitore pKVM è una struttura che incapsula i callback all'hypervisor pKVM. Questa struttura segue le stesse regole ABI delle interfacce GKI.
Crea
hyp/Makefileper creare il codice dell'hypervisor:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleAggiungi il codice del kernel EL1 (
el1.c). La sezione init di questo codice deve contenere una chiamata apkvm_load_el2 moduleper caricare il codice dell'hypervisor EL2 dal passaggio 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);Crea le regole di compilazione.
Per android16-6.12 e versioni successive, consulta Crea un modulo pKVM con DDK per creare
ddk_library()per EL2 eddk_module()per EL1.Per android15-6.6 e versioni precedenti, crea il makefile principale per collegare il codice EL1 e 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
Caricare un modulo pKVM
Come per i moduli del fornitore GKI, i moduli del fornitore pKVM possono essere caricati utilizzando modprobe.
Tuttavia, per motivi di sicurezza, il caricamento deve avvenire prima della rimozione dei privilegi.
Per caricare un modulo pKVM, devi assicurarti che i moduli siano inclusi nel
file system root (initramfs) e devi aggiungere quanto segue alla
riga di comando del kernel:
kvm-arm.protected_modules=mod1,mod2,mod3,...
I moduli del fornitore pKVM archiviati in initramfs ereditano la firma e la protezione di initramfs.
Se uno dei moduli del fornitore pKVM non viene caricato, il sistema viene considerato non sicuro e non sarà possibile avviare una macchina virtuale protetta.
Chiamare una funzione EL2 (hypervisor) da EL1 (modulo del kernel)
Una chiamata hypervisor (HVC) è un'istruzione che consente al kernel di chiamare l'hypervisor. Con l'introduzione dei moduli del fornitore pKVM, è possibile utilizzare un HVC per chiamare una funzione da eseguire a EL2 (nel modulo hypervisor) da EL1 (il modulo kernel):
- Nel codice EL2 (
el2.c), dichiara il gestore EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 o versioni successive
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
Nel codice EL1 (
el1.c), registra il gestore EL2 nel modulo fornitore 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);Nel codice EL1 (
el1.c), chiama l'HVC:pkvm_el2_mod_call(hvc_number);
Esegui il debug e la profilazione del codice EL2
Questa sezione contiene diverse opzioni per eseguire il debug del codice EL2 del modulo pKVM.
Emettere e leggere gli eventi di traccia dell'hypervisor
Tracefs supporta l'hypervisor pKVM. L'utente root ha accesso all'interfaccia,
che si trova in /sys/kernel/tracing/hypervisor/:
tracing_on: attiva o disattiva la tracciabilità.trace: la scrittura in questo file reimposta la traccia.trace_pipe: La lettura di questo file stampa gli eventi dell'hypervisor.buffer_size_kb: la dimensione del buffer per CPU che contiene gli eventi. Aumenta questo valore se gli eventi vengono persi.
Per impostazione predefinita, gli eventi sono disattivati. Per attivarli, utilizza il file
/sys/kernel/tracing/hypervisor/events/my_event/enable corrispondente in Tracefs. Puoi
anche abilitare qualsiasi evento hypervisor all'avvio con la riga di comando del kernel di
hyp_event=event1,event2.
Prima di dichiarare un evento, il codice EL2 del modulo deve dichiarare il
seguente boilerplate, dove pkvm_ops è struct pkvm_module_ops *
passato alla funzione init del modulo:
#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
Dichiarare gli eventi
Dichiarare gli eventi nel proprio file .h:
$ 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
Emettere eventi
Puoi registrare gli eventi nel codice EL2 chiamando la funzione C generata:
trace_pkvm_driver_event(id);
Aggiungere una registrazione aggiuntiva (Android 15 o versioni precedenti)
Per Android 15 e versioni precedenti, includi una registrazione aggiuntiva durante l'inizializzazione del modulo. Non è necessario in Android 16 e versioni successive.
#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;
}
Emettere eventi senza dichiarazione precedente (Android 16 e versioni successive)
La dichiarazione degli eventi può essere complessa per il debug rapido. trace_hyp_printk()
consente al chiamante di passare fino a quattro argomenti a una stringa di formato senza alcuna
dichiarazione di evento:
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);
È necessario anche un boilerplate nel codice EL2. trace_hyp_printk() è una macro
che chiama la funzione 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
Attiva l'evento __hyp_printk in /sys/kernel/tracing/hypervisor/events/ o
al momento dell'avvio con la riga di comando del kernel hyp_event=__hyp_printk.
Reindirizzare gli eventi a dmesg
Il parametro della riga di comando del kernel hyp_trace_printk=1 fa sì che l'interfaccia di tracciamento dell'hypervisor inoltri ogni evento registrato a dmesg del kernel. È utile per leggere gli eventi quando trace_pipe non è accessibile.
Dump degli eventi durante un kernel panic (Android 16 e versioni successive)
Gli eventi dell'hypervisor vengono sottoposti a polling. Esiste quindi un intervallo tra l'ultimo polling
e un kernel panic in cui sono stati emessi eventi, ma non sono stati scaricati nella console.
L'opzione di configurazione del kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC tenta di eseguire il dump degli
eventi più recenti nella console se hyp_trace_printk è stato attivato.
Questa opzione è disattivata per impostazione predefinita per GKI.
Utilizzare Ftrace per tracciare la chiamata e il ritorno della funzione (Android 16 e versioni successive)
Ftrace è una funzionalità del kernel che consente di tracciare ogni chiamata di funzione e il relativo valore restituito.
In modo simile, l'hypervisor pKVM offre due eventi func e
func_ret.
Puoi selezionare le funzioni tracciate con la riga di comando del kernel
hyp_ftrace_filter= o con uno dei file tracefs:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
I filtri utilizzano la corrispondenza con caratteri jolly in stile shell.
Il seguente filtro traccia le funzioni che iniziano con
pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Gli eventi func e func_ret sono disponibili solo con CONFIG_PKVM_FTRACE=y.
Questa opzione è disattivata per impostazione predefinita per GKI.