Trang này giải thích cách triển khai mô-đun nhà cung cấp máy ảo dựa trên nhân được bảo vệ (pKVM).
Đối với android16-6.12 trở lên, khi hoàn tất các bước này, bạn sẽ có một cây thư mục tương tự như sau:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Để xem ví dụ hoàn chỉnh, hãy xem phần Tạo mô-đun pKVM bằng DDK.
Đối với android15-6.6 trở xuống:
Makefile
el1.c
hyp/
Makefile
el2.c
Thêm mã siêu giám sát EL2 (
el2.c). Tối thiểu, mã này phải khai báo một hàm init chấp nhận một tham chiếu đến cấu trúcpkvm_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 mô-đun nhà cung cấp pKVM là một cấu trúc đóng gói các lệnh gọi lại cho trình giám sát ảo pKVM. Cấu trúc này tuân theo các quy tắc ABI giống như các giao diện GKI.
Tạo
hyp/Makefileđể tạo mã trình giám sát siêu ảo:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleThêm mã nhân EL1 (
el1.c). Phần khởi động của mã này phải chứa một lệnh gọi đếnpkvm_load_el2 moduleđể tải mã trình giám sát siêu ảo EL2 từ bước 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);Tạo các quy tắc xây dựng.
Đối với android16-6.12 trở lên, hãy tham khảo Tạo mô-đun pKVM bằng DDK để tạo
ddk_library()cho EL2 vàddk_module()cho EL1.Đối với android15-6.6 trở xuống, hãy tạo makefile gốc để liên kết mã EL1 và EL2 với nhau:
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
Tải mô-đun pKVM
Tương tự như các mô-đun nhà cung cấp GKI, các mô-đun nhà cung cấp pKVM có thể được tải bằng modprobe.
Tuy nhiên, vì lý do bảo mật, quá trình tải phải diễn ra trước khi tước đặc quyền.
Để tải một mô-đun pKVM, bạn phải đảm bảo các mô-đun của mình có trong hệ thống tệp gốc (initramfs) và bạn phải thêm nội dung sau vào dòng lệnh của hạt nhân:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Các mô-đun nhà cung cấp pKVM được lưu trữ trong initramfs sẽ kế thừa chữ ký và khả năng bảo vệ của initramfs.
Nếu một trong các mô-đun nhà cung cấp pKVM không tải được, thì hệ thống sẽ được coi là không an toàn và bạn sẽ không thể khởi động máy ảo được bảo vệ.
Gọi một hàm EL2 (trình ảo hoá) từ EL1 (mô-đun kernel)
Lệnh gọi hypervisor (HVC) là một chỉ dẫn cho phép nhân gọi hypervisor. Khi các mô-đun nhà cung cấp pKVM được giới thiệu, HVC có thể được dùng để gọi một hàm chạy ở EL2 (trong mô-đun trình ảo hoá) từ EL1 (mô-đun kernel):
- Trong mã EL2 (
el2.c), hãy khai báo trình xử lý EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 trở lên
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
Trong mã EL1 (
el1.c), hãy đăng ký trình xử lý EL2 trong mô-đun nhà cung cấp 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);Trong mã EL1 (
el1.c), hãy gọi HVC:pkvm_el2_mod_call(hvc_number);
Gỡ lỗi và phân tích mã EL2
Phần này chứa một số lựa chọn để gỡ lỗi mã EL2 của mô-đun pKVM.
Phát và đọc các sự kiện theo dõi trình giám sát siêu ảo hoá
Tracefs hỗ trợ trình ảo hoá pKVM. Người dùng gốc có quyền truy cập vào giao diện này, nằm trong /sys/kernel/tracing/hypervisor/:
tracing_on: Bật hoặc tắt tính năng theo dõi.- Việc ghi vào tệp này sẽ đặt lại dấu vết.
trace trace_pipe: Việc đọc tệp này sẽ in các sự kiện của trình giám sát siêu ảo.buffer_size_kb: Kích thước của vùng đệm trên mỗi CPU chứa các sự kiện. Tăng giá trị này nếu sự kiện bị mất.
Theo mặc định, các sự kiện sẽ bị tắt. Để bật các sự kiện, hãy sử dụng tệp /sys/kernel/tracing/hypervisor/events/my_event/enable tương ứng trong Tracefs. Bạn cũng có thể bật mọi sự kiện trình ảo hoá tại thời điểm khởi động bằng dòng lệnh của nhân hyp_event=event1,event2.
Trước khi khai báo một sự kiện, mã EL2 của mô-đun phải khai báo đoạn mã sau đây, trong đó pkvm_ops là struct pkvm_module_ops * được truyền đến hàm init của mô-đun:
#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
Khai báo sự kiện
Khai báo các sự kiện trong tệp .h riêng:
$ 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
Phát ra sự kiện
Bạn có thể ghi lại các sự kiện trong mã EL2 bằng cách gọi hàm C đã tạo:
trace_pkvm_driver_event(id);
Thêm thông tin đăng ký bổ sung (Android 15 trở xuống)
Đối với Android 15 trở xuống, hãy thêm một quy trình đăng ký khác trong quá trình khởi tạo mô-đun. Bạn không bắt buộc phải làm việc này trong Android 16 trở lên.
#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;
}
Phát ra các sự kiện mà không cần khai báo trước (Android 16 trở lên)
Việc khai báo các sự kiện có thể gây phiền toái cho việc gỡ lỗi nhanh. trace_hyp_printk() cho phép người gọi truyền tối đa 4 đối số vào một chuỗi định dạng mà không cần khai báo sự kiện:
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);
Bạn cũng phải có một đoạn mã chuẩn trong mã EL2. trace_hyp_printk() là một macro gọi hàm 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
Bật sự kiện __hyp_printk trong /sys/kernel/tracing/hypervisor/events/ hoặc khi khởi động bằng dòng lệnh của nhân hyp_event=__hyp_printk.
Chuyển hướng các sự kiện đến dmesg
Tham số dòng lệnh của hạt nhân hyp_trace_printk=1 làm cho giao diện theo dõi trình giám sát siêu dữ liệu chuyển tiếp từng sự kiện đã ghi nhật ký đến dmesg của hạt nhân. Điều này hữu ích khi đọc các sự kiện khi không truy cập được vào trace_pipe.
Kết xuất các sự kiện trong quá trình lỗi kernel (Android 16 trở lên)
Các sự kiện của trình giám sát siêu ảo được thăm dò. Do đó, có một khoảng thời gian giữa lần thăm dò cuối cùng và sự cố nghiêm trọng của hệ điều hành, trong đó các sự kiện đã được phát ra nhưng chưa được kết xuất vào bảng điều khiển.
Lựa chọn cấu hình kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC cố gắng kết xuất các sự kiện gần đây nhất trong bảng điều khiển nếu hyp_trace_printk đã được bật.
Tuỳ chọn này bị tắt theo mặc định đối với GKI.
Sử dụng Ftrace để theo dõi lệnh gọi hàm và trả về (Android 16 trở lên)
Ftrace là một tính năng của nhân cho phép bạn theo dõi từng lệnh gọi hàm và giá trị trả về.
Tương tự, trình giám sát ảo pKVM cung cấp 2 sự kiện func và func_ret.
Bạn có thể chọn các hàm được theo dõi bằng dòng lệnh của nhân hyp_ftrace_filter= hoặc bằng một trong các tệp tracefs:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Bộ lọc sử dụng tính năng so khớp mẫu chung theo kiểu shell.
Bộ lọc sau đây theo dõi các hàm bắt đầu bằng pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Sự kiện func và func_ret chỉ có trong CONFIG_PKVM_FTRACE=y.
Tuỳ chọn này bị tắt theo mặc định đối với GKI.