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 bạn 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ư:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Để xem ví dụ đầy đủ, 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ã trình 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 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 pKVM. Cấu trúc này tuân theo các quy tắc ABI giống như giao diện GKI.
Tạo
hyp/Makefileđể tạo mã trình giám sát:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleThêm mã nhân EL1 (
el1.c). Phần init của mã này phải chứa lệnh gọi đếnpkvm_load_el2 moduleđể tải mã trình giám sát 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 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
Giống 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 quyền.
Để tải mô-đun pKVM, bạn phải đảm bảo các mô-đun của mình được đưa vào 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 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, 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 hàm EL2 (trình giám sát) từ EL1 (mô-đun nhân)
Lệnh gọi trình điều khiển ảo hoá (HVC) là một hướng dẫn cho phép nhân hệ điều hành gọi trình điều khiển ảo hoá. Khi giới thiệu các mô-đun nhà cung cấp pKVM, bạn có thể sử dụng HVC để gọi một hàm chạy ở EL2 (trong mô-đun trình điều khiển ảo hoá) từ EL1 (mô-đun nhân):
- 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
Tracefs hỗ trợ trình giám sát 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.trace: Việc ghi vào tệp này sẽ đặt lại dấu vết.trace_pipe: Việc đọc tệp này sẽ in các sự kiện trình giám sát.buffer_size_kb: Kích thước của bộ đệm trên mỗi CPU chứa các sự kiện. Hãy tăng giá trị này nếu các 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 điều khiển ảo hoá tại thời điểm khởi động bằng dòng lệnh nhân hệ điều hành
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 phần soạn sẵn 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 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 thông tin đăng ký bổ sung trong quá trình khởi chạy mô-đun. Bạn không cần 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 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 khó khăn cho việc gỡ lỗi nhanh. trace_hyp_printk()
cho phép trình gọi truyền tối đa 4 đối số đến 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 cần có một phần soạn sẵ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
tại thời điểm khởi động bằng dòng lệnh nhân hyp_event=__hyp_printk.
Chuyển hướng các sự kiện đến dmesg
Tham số dòng lệnh nhân hyp_trace_printk=1 sẽ khiến giao diện theo dõi trình điều khiển ảo hoá chuyển tiếp từng sự kiện đã ghi lại đến dmesg của nhân. Điều này hữu ích để đọc các sự kiện khi bạn không thể truy cập vào trace_pipe.
Kết xuất các sự kiện trong quá trình nhân gặp lỗi nghiêm trọng (Android 16 trở lên)
Các sự kiện trình giám sát đượ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à hoảng loạn hạt nhân, trong đó các sự kiện đã được phát nhưng không được kết xuất vào bảng điều khiển.
Tuỳ chọn cấu hình nhân CONFIG_PKVM_DUMP_TRACE_ON_PANIC sẽ 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 bạn đã bật hyp_trace_printk.
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à trả về.
Tương tự, trình giám sát 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 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 đối sánh glob 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
Các sự kiện func và func_ret chỉ có khi CONFIG_PKVM_FTRACE=y.
Tuỳ chọn này bị tắt theo mặc định đối với GKI.