На этой странице объясняется, как реализовать модуль поставщика защищенной виртуальной машины на основе ядра (pKVM).
Для Android 16-6.12 и более поздних версий после выполнения этих шагов у вас должна получиться структура каталогов, похожая на следующую:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Полный пример см. в разделе «Создание модуля pKVM с помощью DDK» .
Для Android 15-6.6 и более ранних версий:
Makefile
el1.c
hyp/
Makefile
el2.c
Добавьте код гипервизора EL2 (
el2.c). Как минимум, этот код должен объявлять функцию инициализации, принимающую ссылку на структуруpkvm_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 модуля поставщика pKVM представляет собой структуру, инкапсулирующую обратные вызовы к гипервизору pKVM. Эта структура соответствует тем же правилам ABI, что и интерфейсы GKI.
Создайте файл
hyp/Makefileдля сборки кода гипервизора:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleДобавьте код ядра EL1 (
el1.c). Раздел инициализации этого кода должен содержать вызовpkvm_load_el2 moduleдля загрузки кода гипервизора EL2 из шага 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);Создайте правила сборки.
Для Android 16-6.12 и более поздних версий обратитесь к разделу «Создание модуля pKVM с помощью DDK» , чтобы создать
ddk_library()для EL2 иddk_module()для EL1.Для Android 15-6.6 и более ранних версий создайте корневой файл makefile для объединения кода EL1 и 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
Загрузите модуль pKVM
Как и модули поставщиков GKI, модули поставщиков pKVM можно загрузить с помощью modprobe. Однако по соображениям безопасности загрузка должна происходить до снижения привилегий. Для загрузки модуля pKVM необходимо убедиться, что ваши модули включены в корневую файловую систему ( initramfs ), и добавить следующее в командную строку ядра:
kvm-arm.protected_modules= mod1 , mod2 , mod3 , ...
Модули pKVM от поставщика, хранящиеся в initramfs наследуют подпись и защиту initramfs .
Если один из модулей поставщика pKVM не загрузится, система будет считаться небезопасной, и запустить защищенную виртуальную машину будет невозможно.
Вызов функции EL2 (гипервизора) из модуля EL1 (ядра).
Инструкция вызова гипервизора (HVC) позволяет ядру вызывать гипервизор. С появлением модулей pKVM от поставщиков, HVC можно использовать для вызова функции, которая будет выполняться на уровне EL2 (в модуле гипервизора) из уровня EL1 (модуля ядра):
- В коде EL2 (
el2.c) объявите обработчик EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 или выше
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
В вашем коде EL1 (
el1.c) зарегистрируйте обработчик EL2 в модуле поставщика 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);В вашем коде EL1 (
el1.c) вызовите HVC:pkvm_el2_mod_call(hvc_number);
Отладка и профилирование кода EL2
В этом разделе представлено несколько вариантов отладки кода модуля pKVM на языке EL2.
Генерировать и считывать события трассировки гипервизора
Tracefs поддерживает гипервизор pKVM. Пользователь root имеет доступ к интерфейсу, расположенному в /sys/kernel/tracing/hypervisor/ :
-
tracing_on: Включает или выключает трассировку. -
trace: Запись в этот файл сбрасывает трассировку. -
trace_pipe: Чтение этого файла выводит события гипервизора. -
buffer_size_kb: Размер буфера событий для каждого процессора. Увеличьте это значение, если события теряются.
По умолчанию события отключены. Чтобы включить события, используйте соответствующий файл /sys/kernel/tracing/hypervisor/events/my_event/enable в Tracefs. Вы также можете включить любое событие гипервизора при загрузке с помощью команды ядра hyp_event= event1 , event2 .
Перед объявлением события код модуля на языке EL2 должен объявить следующий шаблонный код, где pkvm_ops — это struct pkvm_module_ops * передаваемая в функцию init модуля:
#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
Объявлять события
Объявляйте события в отдельных файлах .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
Инициировать события
Вы можете регистрировать события в коде EL2, вызывая сгенерированную функцию на языке C:
trace_pkvm_driver_event(id);
Добавить дополнительную регистрацию (Android 15 или ниже)
Для Android 15 и ниже необходимо добавить дополнительную регистрацию во время инициализации модуля. В Android 16 и выше это не требуется.
#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;
}
Генерация событий без предварительного объявления (Android 16 и выше)
Объявление событий может быть неудобным для быстрой отладки. trace_hyp_printk() позволяет вызывающей стороне передавать до четырех аргументов в строку форматирования без объявления каких-либо событий:
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);
В коде EL2 также требуется стандартный код. trace_hyp_printk() — это макрос, который вызывает функцию 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
Включите событие __hyp_printk в каталоге /sys/kernel/tracing/hypervisor/events/ или при загрузке с помощью команды ядра hyp_event=__hyp_printk .
Перенаправить события в dmesg
Параметр командной строки ядра hyp_trace_printk=1 заставляет интерфейс трассировки гипервизора пересылать каждое зарегистрированное событие в dmesg ядра. Это полезно для чтения событий, когда trace_pipe недоступен.
Сохранение событий во время сбоя ядра (Android 16 и выше)
События гипервизора опрашиваются. Поэтому между последним опросом и паникой ядра существует временной промежуток, в течение которого события были сгенерированы, но не выведены на консоль. Параметр конфигурации ядра CONFIG_PKVM_DUMP_TRACE_ON_PANIC пытается вывести на консоль самые последние события, если включена hyp_trace_printk .
Эта опция по умолчанию отключена для GKI.
Используйте Ftrace для отслеживания вызова и возврата функции (Android 16 и выше).
Ftrace — это функция ядра, позволяющая отслеживать каждый вызов функции и её возврат. Аналогичным образом, гипервизор pKVM предлагает два события: func и func_ret .
Вы можете выбрать отслеживаемые функции с помощью параметра командной строки ядра hyp_ftrace_filter= или с помощью одного из файлов tracefs:
-
/sys/kernel/tracing/hypervisor/set_ftrace_filter -
/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Фильтры используют сопоставление шаблонов в стиле командной оболочки.
Следующий фильтр отслеживает функции, начинающиеся с pkvm_hyp_driver :
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
События func и func_ret доступны только при CONFIG_PKVM_FTRACE=y . Эта опция по умолчанию отключена для GKI.