Nesta página, explicamos como implementar um módulo de fornecedor de máquina virtual baseada em kernel protegida (pKVM).
Para android16-6.12 e versões mais recentes, depois de concluir essas etapas, você terá uma árvore de diretórios semelhante a esta:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Para um exemplo completo, consulte Criar um módulo pKVM com DDK .
Para android15-6.6 e versões anteriores:
Makefile
el1.c
hyp/
Makefile
el2.c
Adicione o código do hipervisor EL2 (
el2.c). No mínimo, esse código precisa declarar uma função de inicialização que aceite uma referência à 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; }A API do módulo de fornecedor pKVM é uma struct que encapsula callbacks para o hipervisor pKVM. Essa struct segue as mesmas regras de ABI das interfaces do GKI.
Crie o
hyp/Makefilepara criar o código do hipervisor:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleAdicione o código do kernel EL1 (
el1.c). A seção de inicialização desse código precisa conter uma chamada parapkvm_load_el2 modulepara carregar o código do hipervisor EL2 da etapa 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);Crie as regras de build.
Para android16-6.12 e versões mais recentes, consulte Criar um módulo pKVM com DDK para criar
ddk_library()para EL2 eddk_module()para EL1.Para android15-6.6 e versões anteriores, crie o makefile raiz para vincular o código 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
Carregar um módulo pKVM
Assim como os módulos de fornecedor do GKI, os módulos de fornecedor do pKVM podem ser carregados usando o modprobe.
No entanto, por motivos de segurança, o carregamento precisa ocorrer antes da remoção de privilégios.
Para carregar um módulo pKVM, verifique se os módulos estão incluídos no sistema de arquivos raiz (initramfs) e adicione o seguinte à linha de comando do kernel:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Os módulos de fornecedor pKVM armazenados em initramfs herdam a assinatura e a proteção de initramfs.
Se um dos módulos do fornecedor do pKVM não for carregado, o sistema será considerado inseguro e não será possível iniciar uma máquina virtual protegida.
Chamar uma função EL2 (hipervisor) de EL1 (módulo do kernel)
Uma chamada de hipervisor (HVC) é uma instrução que permite ao kernel chamar o hipervisor. Com a introdução dos módulos de fornecedor pKVM, um HVC pode ser usado para chamar uma função para ser executada no EL2 (no módulo do hipervisor) do EL1 (o módulo do kernel):
- No código EL2 (
el2.c), declare o gerenciador EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 ou mais recente
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
No seu código EL1 (
el1.c), registre o gerenciador EL2 no módulo do fornecedor 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);No seu código EL1 (
el1.c), chame o HVC:pkvm_el2_mod_call(hvc_number);
Depurar e criar perfil do código EL2
Esta seção contém várias opções para depurar o código EL2 do módulo pKVM.
Emitir e ler eventos de rastreamento do hipervisor
O Tracefs é compatível com o hipervisor pKVM. O usuário root tem acesso à interface,
que está localizada em /sys/kernel/tracing/hypervisor/:
tracing_on: ativa ou desativa o rastreamento.trace: gravar nesse arquivo redefine o rastreamento.trace_pipe: ler esse arquivo imprime os eventos do hipervisor.buffer_size_kb: o tamanho do buffer por CPU que contém eventos. Aumente esse valor se os eventos forem perdidos.
Por padrão, os eventos ficam desativados. Para ativá-los, use o arquivo /sys/kernel/tracing/hypervisor/events/my_event/enable correspondente no Tracefs. Também é possível ativar qualquer evento do hipervisor na inicialização com a linha de comando do kernel de hyp_event=event1,event2.
Antes de declarar um evento, o código EL2 do módulo precisa declarar o
boilerplate a seguir, em que pkvm_ops é o struct pkvm_module_ops *
transmitido à função init do 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
Declarar eventos
Declare eventos no próprio arquivo .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
Emitir eventos
É possível registrar eventos no código EL2 chamando a função C gerada:
trace_pkvm_driver_event(id);
Adicionar outro registro (Android 15 ou versões anteriores)
No Android 15 e versões anteriores, inclua um registro adicional durante a inicialização do módulo. Isso não é necessário no Android 16 e versões mais recentes.
#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;
}
Emitir eventos sem declaração prévia (Android 16 e versões mais recentes)
Declarar eventos pode ser complicado para uma depuração rápida. trace_hyp_printk() permite que o autor da chamada transmita até quatro argumentos para uma string de formato sem declaração 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);
Um modelo no código EL2 também é necessário. trace_hyp_printk() é uma macro
que chama a função 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
Ative o evento __hyp_printk em /sys/kernel/tracing/hypervisor/events/ ou
na inicialização com a linha de comando do kernel hyp_event=__hyp_printk.
Redirecionar eventos para dmesg
O parâmetro de linha de comando do kernel hyp_trace_printk=1 faz com que a interface de rastreamento do hipervisor encaminhe cada evento registrado para o dmesg do kernel. Isso é útil para ler eventos quando trace_pipe está inacessível.
Despejar eventos durante um pânico do kernel (Android 16 e versões mais recentes)
Os eventos do hipervisor são pesquisados. Portanto, há uma janela entre a última pesquisa
e um kernel panic em que os eventos foram emitidos, mas não despejados no console.
A opção de configuração do kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC tenta despejar os
eventos mais recentes no console se hyp_trace_printk estiver ativado.
Essa opção é desativada por padrão para o GKI.
Usar o Ftrace para rastrear chamadas e retornos de função (Android 16 e versões mais recentes)
O Ftrace é um recurso do kernel que permite rastrear cada chamada e retorno de função.
Da mesma forma, o hipervisor pKVM oferece dois eventos, func e func_ret.
É possível selecionar as funções rastreadas com a linha de comando do kernel
hyp_ftrace_filter= ou com um dos arquivos tracefs:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Os filtros usam a correspondência de glob no estilo shell.
O filtro a seguir rastreia as funções que começam com
pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Os eventos func e func_ret estão disponíveis apenas com o CONFIG_PKVM_FTRACE=y.
Essa opção é desativada por padrão para o GKI.