Auf dieser Seite wird beschrieben, wie Sie ein pKVM-Anbietermodul (Protected Kernel-based Virtual Machine) implementieren.
Bei android16-6.12 und höher sollte nach Abschluss dieser Schritte eine Verzeichnisstruktur wie die folgende vorhanden sein:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Ein vollständiges Beispiel finden Sie unter pKVM-Modul mit DDK erstellen.
Für android15-6.6 und niedriger:
Makefile
el1.c
hyp/
Makefile
el2.c
Fügen Sie den EL2-Hypervisor-Code (
el2.c) hinzu. Dieser Code muss mindestens eine Init-Funktion deklarieren, die einen Verweis auf diepkvm_module_ops-Struktur akzeptiert:#include <asm/kvm_pkvm_module.h> int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops) { /* Init the EL2 code */ return 0; }Die pKVM-Anbietermodul-API ist eine Struktur, die Callbacks zum pKVM-Hypervisor kapselt. Diese Struktur folgt denselben ABI-Regeln wie GKI-Schnittstellen.
Erstellen Sie das
hyp/Makefile, um den Hypervisor-Code zu erstellen:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleFügen Sie den EL1-Kernelcode (
el1.c) hinzu. Der Initialisierungsabschnitt dieses Codes muss einen Aufruf vonpkvm_load_el2 moduleenthalten, um den EL2-Hypervisorcode aus Schritt 1 zu laden.#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);Erstellen Sie die Build-Regeln.
Für android16-6.12 und höher finden Sie unter pKVM-Modul mit DDK erstellen Informationen zum Erstellen von
ddk_library()für EL2 undddk_module()für EL1.Erstellen Sie für android15-6.6 und früher das Root-Makefile, um den EL1- und EL2-Code zu verknüpfen:
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
pKVM-Modul laden
Wie bei GKI-Anbietermodulen können pKVM-Anbietermodule mit modprobe geladen werden.
Aus Sicherheitsgründen muss das Laden jedoch vor dem Entfernen der Berechtigungen erfolgen.
Damit Sie ein pKVM-Modul laden können, müssen Sie dafür sorgen, dass Ihre Module im Stammdateisystem (initramfs) enthalten sind, und der Kernel-Befehlszeile Folgendes hinzufügen:
kvm-arm.protected_modules=mod1,mod2,mod3,...
pKVM-Anbietermodule, die in initramfs gespeichert sind, erben die Signatur und den Schutz von initramfs.
Wenn eines der pKVM-Anbietermodule nicht geladen werden kann, gilt das System als unsicher und es ist nicht möglich, eine geschützte virtuelle Maschine zu starten.
EL2-Funktion (Hypervisor) aus EL1 (Kernelmodul) aufrufen
Ein Hypervisor-Aufruf (HVC) ist ein Befehl, mit dem der Kernel den Hypervisor aufrufen kann. Mit der Einführung von pKVM-Anbietermodulen kann ein HVC verwendet werden, um eine Funktion aufzurufen, die auf EL2 (im Hypervisor-Modul) von EL1 (dem Kernelmodul) aus ausgeführt werden soll:
- Deklarieren Sie im EL2-Code (
el2.c) den EL2-Handler:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 oder höher
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
Registrieren Sie den EL2-Handler in Ihrem pKVM-Anbietermodul in Ihrem EL1-Code (
el1.c):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);Rufen Sie den HVC in Ihrem EL1-Code (
el1.c) auf:pkvm_el2_mod_call(hvc_number);
EL2-Code debuggen und Profile für EL2-Code erstellen
Dieser Abschnitt enthält mehrere Optionen zum Debuggen von EL2-Code für das pKVM-Modul.
Hypervisor-Trace-Ereignisse ausgeben und lesen
Tracefs unterstützt den pKVM-Hypervisor. Der Root-Nutzer hat Zugriff auf die Schnittstelle, die sich unter /sys/kernel/tracing/hypervisor/ befindet:
tracing_on: Aktiviert oder deaktiviert die Ablaufverfolgung.trace: Wenn Sie in diese Datei schreiben, wird der Trace zurückgesetzt.trace_pipe: Wenn diese Datei gelesen wird, werden die Hypervisor-Ereignisse ausgegeben.buffer_size_kb: Die Größe des Puffers pro CPU, der Ereignisse enthält. Erhöhen Sie diesen Wert, wenn Ereignisse verloren gehen.
Standardmäßig sind Ereignisse deaktiviert. Wenn Sie Ereignisse aktivieren möchten, verwenden Sie die entsprechende /sys/kernel/tracing/hypervisor/events/my_event/enable-Datei in Tracefs. Sie können auch jedes Hypervisor-Ereignis beim Booten mit der Kernel-Befehlszeile von hyp_event=event1,event2 aktivieren.
Bevor Sie ein Ereignis deklarieren, muss im EL2-Code des Moduls der folgende Boilerplate-Code deklariert werden, wobei pkvm_ops der struct pkvm_module_ops * ist, der an die Modulfunktion init übergeben wird:
#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
Ereignisse deklarieren
Deklarieren Sie Ereignisse in einer eigenen .h-Datei:
$ 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
Ereignisse ausgeben
Sie können Ereignisse im EL2-Code protokollieren, indem Sie die generierte C-Funktion aufrufen:
trace_pkvm_driver_event(id);
Zusätzliche Registrierung hinzufügen (Android 15 oder niedriger)
Bei Android 15 und niedriger ist eine zusätzliche Registrierung während der Modulinitialisierung erforderlich. Unter Android 16 und höher ist dies nicht erforderlich.
#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;
}
Ereignisse ohne vorherige Deklaration ausgeben (Android 16 und höher)
Das Deklarieren von Ereignissen kann für schnelles Debugging umständlich sein. trace_hyp_printk()
ermöglicht es dem Aufrufer, bis zu vier Argumente an einen Formatstring zu übergeben, ohne dass eine Ereignisdeklaration erforderlich ist:
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);
Außerdem ist ein Boilerplate-Text im EL2-Code erforderlich. trace_hyp_printk() ist ein Makro, das die Funktion trace___hyp_printk() aufruft:
#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
Aktivieren Sie das Ereignis __hyp_printk in /sys/kernel/tracing/hypervisor/events/ oder beim Booten mit der Kernel-Befehlszeile hyp_event=__hyp_printk.
Ereignisse an dmesg weiterleiten
Der Kernel-Befehlszeilenparameter hyp_trace_printk=1 sorgt dafür, dass die Hypervisor-Tracing-Schnittstelle jedes protokollierte Ereignis an den Kernel-dmesg weiterleitet. Das ist nützlich, um Ereignisse zu lesen, wenn trace_pipe nicht zugänglich ist.
Ereignisse während einer Kernel Panic ausgeben (Android 16 und höher)
Hypervisor-Ereignisse werden abgefragt. Zwischen der letzten Abfrage und einem Kernel Panic-Ereignis gibt es daher ein Zeitfenster, in dem Ereignisse ausgegeben, aber nicht in die Konsole geschrieben wurden.
Mit der Kernelkonfigurationsoption CONFIG_PKVM_DUMP_TRACE_ON_PANIC wird versucht, die letzten Ereignisse in der Konsole auszugeben, wenn hyp_trace_printk aktiviert wurde.
Diese Option ist für GKI standardmäßig deaktiviert.
Funktionsaufrufe und Rückgaben mit Ftrace verfolgen (Android 16 und höher)
Ftrace ist eine Kernelfunktion, mit der Sie jeden Funktionsaufruf und jede Rückgabe verfolgen können.
Der pKVM-Hypervisor bietet auf ähnliche Weise zwei Ereignisse: func und func_ret.
Sie können die zu verfolgenden Funktionen mit der Kernel-Befehlszeile hyp_ftrace_filter= oder mit einer der tracefs-Dateien auswählen:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Für Filter wird das Glob-Matching im Shell-Stil verwendet.
Der folgende Filter verfolgt die Funktionen, die mit pkvm_hyp_driver beginnen:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
func- und func_ret-Ereignisse sind nur mit CONFIG_PKVM_FTRACE=y verfügbar.
Diese Option ist für GKI standardmäßig deaktiviert.