이 페이지에서는 보호된 커널 기반 가상 머신(pKVM) 공급업체 모듈을 구현하는 방법을 설명합니다.
android16-6.12 이상에서는 이 단계를 완료하면 다음과 같은 디렉터리 트리가 생성됩니다.
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
전체 예시는 DDK로 pKVM 모듈 빌드를 참고하세요.
android15-6.6 이하의 경우:
Makefile
el1.c
hyp/
Makefile
el2.c
EL2 하이퍼바이저 코드(
el2.c)를 추가합니다. 최소한 이 코드는pkvm_module_ops구조체 참조를 허용하는 init 함수를 선언해야 합니다.#include <asm/kvm_pkvm_module.h> int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops) { /* Init the EL2 code */ return 0; }pKVM 공급업체 모듈 API는 pKVM 하이퍼바이저에 대한 콜백을 캡슐화하는 구조체입니다. 이 구조체는 GKI 인터페이스와 동일한 ABI 규칙을 따릅니다.
hyp/Makefile을 만들어 하이퍼바이저 코드를 빌드합니다.hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleEL1 커널 코드(
el1.c)를 추가합니다. 이 코드의 init 섹션에는 1단계의 EL2 하이퍼바이저 코드를 로드하는pkvm_load_el2 module호출이 포함되어야 합니다.#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);빌드 규칙을 만듭니다.
android16-6.12 이상의 경우 DDK로 pKVM 모듈 빌드를 참고하여 EL2용
ddk_library()와 EL1용ddk_module()를 만드세요.android15-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,...
initramfs에 저장된 pKVM 공급업체 모듈은 initramfs의 서명과 보호를 상속받습니다.
pKVM 공급업체 모듈 중 하나를 로드하지 못하면 시스템이 안전하지 않은 것으로 간주되어 보호된 가상 머신을 시작할 수 없습니다.
EL1 (커널 모듈)에서 EL2 (하이퍼바이저) 함수 호출
하이퍼바이저 호출 (HVC)은 커널이 하이퍼바이저를 호출할 수 있도록 하는 명령입니다. pKVM 공급업체 모듈의 도입으로 HVC는 다음과 같이 EL1 (커널 모듈)의 EL2(하이퍼바이저 모듈)에서 실행할 함수를 호출하는 데 사용할 수 있습니다.
- 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)에서 pKVM 공급업체 모듈에 EL2 핸들러를 등록합니다.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 하이퍼바이저를 지원합니다. 루트 사용자는 /sys/kernel/tracing/hypervisor/에 있는 인터페이스에 액세스할 수 있습니다.
tracing_on: 트레이싱을 사용 설정 또는 중지합니다.trace: 이 파일에 쓰면 트레이스가 재설정됩니다.trace_pipe: 이 파일을 읽으면 하이퍼바이저 이벤트가 출력됩니다.buffer_size_kb: 이벤트를 보유하는 CPU별 버퍼의 크기입니다. 이벤트가 손실되는 경우 이 값을 늘립니다.
기본적으로 이벤트는 사용 중지되어 있습니다. 이벤트를 사용 설정하려면 Tracefs에서 해당 /sys/kernel/tracing/hypervisor/events/my_event/enable 파일을 사용하세요. hyp_event=event1,event2의 커널 명령줄을 사용하여 부팅 시 하이퍼바이저 이벤트를 사용 설정할 수도 있습니다.
이벤트를 선언하기 전에 모듈의 EL2 코드는 다음 상용구를 선언해야 합니다. 여기서 pkvm_ops는 모듈 init 함수에 전달된 struct pkvm_module_ops *입니다.
#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
이벤트 내보내기
생성된 C 함수를 호출하여 EL2 코드에서 이벤트를 로깅할 수 있습니다.
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()를 사용하면 호출자가 이벤트 선언 없이 최대 4개의 인수를 형식 문자열에 전달할 수 있습니다.
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
/sys/kernel/tracing/hypervisor/events/에서 또는 부팅 시 커널 명령줄 hyp_event=__hyp_printk를 사용하여 이벤트 __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
필터는 셸 스타일 glob 일치를 사용합니다.
다음 필터는 pkvm_hyp_driver로 시작하는 함수를 추적합니다.
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
func 및 func_ret 이벤트는 CONFIG_PKVM_FTRACE=y에서만 사용할 수 있습니다.
이 옵션은 GKI에서 기본적으로 사용 중지되어 있습니다.