En esta página, se explica cómo implementar un módulo del proveedor de máquinas virtuales protegidas basadas en el kernel (pKVM).
Para android16-6.12 y versiones posteriores, cuando termines con estos pasos, deberías tener un árbol de directorios similar al siguiente:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Para ver un ejemplo completo, consulta Cómo compilar un módulo de pKVM con el DDK.
Para android15-6.6 y versiones anteriores:
Makefile
el1.c
hyp/
Makefile
el2.c
Agrega el código del hipervisor EL2 (
el2.c). Como mínimo, este código debe declarar una función init que acepte una referencia a la estructurapkvm_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; }La API del módulo de proveedores de la pKVM es una estructura que encapsula devoluciones de llamada al hipervisor de la pKVM. Esta estructura sigue las mismas reglas de ABI que las interfaces de GKI.
Crea el
hyp/Makefilepara compilar el código del hipervisor:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleAgrega el código del kernel de EL1 (
el1.c). La sección de inicialización de este código debe contener una llamada apkvm_load_el2 modulepara cargar el código del hipervisor de EL2 del paso 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 las reglas de compilación.
Para android16-6.12 y versiones posteriores, consulta Cómo compilar un módulo de pKVM con el DDK para crear
ddk_library()para EL2 yddk_module()para EL1.Para android15-6.6 y versiones anteriores, crea el archivo makefile raíz para vincular el código de EL1 y 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
Cómo cargar un módulo de pKVM
Al igual que con los módulos de proveedores de GKI, los módulos de proveedores de pKVM se pueden cargar con modprobe.
Sin embargo, por motivos de seguridad, la carga debe ocurrir antes de la reducción de privilegios.
Para cargar un módulo de pKVM, debes asegurarte de que tus módulos estén incluidos en el sistema de archivos raíz (initramfs) y debes agregar lo siguiente a la línea de comandos del kernel:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Los módulos de proveedores de la pKVM almacenados en initramfs heredan la firma y la protección de initramfs.
Si uno de los módulos del proveedor de pKVM no se carga, el sistema se considera inseguro y no será posible iniciar una máquina virtual protegida.
Llama a una función de EL2 (hipervisor) desde EL1 (módulo del kernel)
Una llamada al hipervisor (HVC) es una instrucción que permite que el kernel llame al hipervisor. Con la introducción de los módulos de proveedores de la pKVM, se puede usar una HVC para llamar a una función que se ejecute en EL2 (en el módulo del hipervisor) desde EL1 (el módulo del kernel):
- En el código de EL2 (
el2.c), declara el controlador de EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 o versiones posteriores
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
En tu código de EL1 (
el1.c), registra el controlador de EL2 en tu módulo de proveedor de la 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);En tu código de EL1 (
el1.c), llama al HVC:pkvm_el2_mod_call(hvc_number);
Depura y genera perfiles del código de EL2
En esta sección, se incluyen varias opciones para depurar el código EL2 del módulo pKVM.
Emite y lee eventos de seguimiento del hipervisor
Tracefs admite el hipervisor pKVM. El usuario raíz tiene acceso a la interfaz, que se encuentra en /sys/kernel/tracing/hypervisor/:
tracing_on: Activa o desactiva el registro.trace: Si escribes en este archivo, se restablece el registro.trace_pipe: La lectura de este archivo imprime los eventos del hipervisor.buffer_size_kb: Es el tamaño del búfer por CPU que contiene eventos. Aumenta este valor si se pierden eventos.
De forma predeterminada, los eventos están desactivados. Para habilitarlos, usa el archivo /sys/kernel/tracing/hypervisor/events/my_event/enable correspondiente en Tracefs. También puedes habilitar cualquier evento del hipervisor en el momento del inicio con la línea de comandos del kernel de hyp_event=event1,event2.
Antes de declarar un evento, el código EL2 del módulo debe declarar el siguiente código estándar, en el que pkvm_ops es el struct pkvm_module_ops * que se pasa a la función init del módulo:
#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
Cómo declarar eventos
Declara eventos en su propio archivo .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
Emite eventos
Puedes registrar eventos en el código de EL2 llamando a la función C generada:
trace_pkvm_driver_event(id);
Agregar un registro adicional (Android 15 o versiones anteriores)
En Android 15 y versiones anteriores, incluye un registro adicional durante la inicialización del módulo. Esto no es necesario en Android 16 y versiones posteriores.
#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;
}
Emite eventos sin declaración previa (Android 16 y versiones posteriores)
Declarar eventos puede ser engorroso para una depuración rápida. trace_hyp_printk() permite que la persona que llama pase hasta cuatro argumentos a una cadena de formato sin ninguna declaración de 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);
También se requiere una plantilla en el código de EL2. trace_hyp_printk() es una macro que llama a la función 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
Habilita el evento __hyp_printk en /sys/kernel/tracing/hypervisor/events/ o en el momento del inicio con la línea de comandos del kernel hyp_event=__hyp_printk.
Redirecciona eventos a dmesg
El parámetro de línea de comandos del kernel hyp_trace_printk=1 hace que la interfaz de seguimiento del hipervisor reenvíe cada evento registrado al dmesg del kernel. Esto es útil para leer eventos cuando no se puede acceder a trace_pipe.
Volcado de eventos durante un kernel panic (Android 16 y versiones posteriores)
Se sondean los eventos del hipervisor. Por lo tanto, hay un período entre el último sondeo y un error irrecuperable del kernel en el que se emitieron eventos, pero no se volcaron en la consola.
La opción de configuración del kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC intenta volcar los eventos más recientes en la consola si se habilitó hyp_trace_printk.
Esta opción está inhabilitada de forma predeterminada para el GKI.
Usa Ftrace para hacer un seguimiento de las llamadas y los retornos de funciones (Android 16 y versiones posteriores)
Ftrace es una función del kernel que te permite hacer un seguimiento de cada llamada y retorno de función.
De manera similar, el hipervisor de la pKVM ofrece dos eventos: func y func_ret.
Puedes seleccionar las funciones registradas con el comando de kernel hyp_ftrace_filter= o con uno de los archivos de tracefs de la línea de comandos:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Los filtros usan la coincidencia de glob al estilo de shell.
El siguiente filtro registra las funciones que comienzan con pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Los eventos func y func_ret solo están disponibles con CONFIG_PKVM_FTRACE=y.
Esta opción está inhabilitada de forma predeterminada para el GKI.