หน้านี้จะอธิบายวิธีใช้โมดูลผู้ให้บริการเครื่องเสมือน (pKVM) ที่ใช้เคอร์เนลที่ได้รับการปกป้อง
สำหรับ android16-6.12 ขึ้นไป เมื่อทำตามขั้นตอนเหล่านี้เสร็จแล้ว คุณควรมีแผนผังไดเรกทอรีที่คล้ายกับแผนผังด้านล่าง
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
ดูตัวอย่างฉบับเต็มได้ที่ สร้างโมดูล pKVM ด้วย DDK .
สำหรับ android15-6.6 ลงไป ให้ทำดังนี้
Makefile
el1.c
hyp/
Makefile
el2.c
เพิ่มโค้ดไฮเปอร์ไวเซอร์ EL2 (
el2.c) โค้ดนี้ต้องประกาศฟังก์ชัน init ที่ยอมรับการอ้างอิงไปยังโครงสร้าง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) ส่วน init ของโค้ดนี้ต้องมีการเรียก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);สร้างกฎการสร้าง
สำหรับ android16-6.12 ขึ้นไป โปรดดู สร้างโมดูล pKVM ด้วย DDK เพื่อสร้าง
ddk_library()สำหรับ EL2 และddk_module()สำหรับ EL1สำหรับ 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
โมดูลผู้ให้บริการ pKVM สามารถโหลดได้โดยใช้ modprobe เช่นเดียวกับโมดูลผู้ให้บริการ GKI
อย่างไรก็ตาม การโหลดต้องเกิดขึ้นก่อนการลดสิทธิ์ด้วยเหตุผลด้านความปลอดภัย
หากต้องการโหลดโมดูล pKVM คุณต้องตรวจสอบว่าได้รวมโมดูลไว้ในระบบไฟล์รูท (initramfs) แล้ว และต้องเพิ่มข้อมูลต่อไปนี้ลงในบรรทัดคำสั่งเคอร์เนล
kvm-arm.protected_modules=mod1,mod2,mod3,...
โมดูลผู้ให้บริการ pKVM ที่จัดเก็บไว้ใน initramfs จะรับช่วงการลงนามและการป้องกันของ initramfs
หากโมดูลผู้ให้บริการ pKVM โมดูลใดโมดูลหนึ่งโหลดไม่สำเร็จ ระบบจะถือว่าไม่ปลอดภัยและคุณจะเริ่มเครื่องเสมือนที่ได้รับการปกป้องไม่ได้
เรียกใช้ฟังก์ชัน EL2 (ไฮเปอร์ไวเซอร์) จาก EL1 (โมดูลเคอร์เนล)
การเรียกไฮเปอร์ไวเซอร์ (HVC) เป็นคำสั่งที่อนุญาตให้เคอร์เนลเรียกไฮเปอร์ไวเซอร์ เมื่อมีการเปิดตัวโมดูลผู้ให้บริการ pKVM คุณจะใช้ HVC เพื่อเรียกใช้ฟังก์ชันที่ทำงานที่ EL2 (ในโมดูลไฮเปอร์ไวเซอร์) จาก EL1 (โมดูลเคอร์เนล) ได้ดังนี้
- ประกาศตัวจัดการ EL2 ในโค้ด EL2 (
el2.c)
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;
}
ลงทะเบียนตัวจัดการ EL2 ในโมดูลผู้ให้บริการ pKVM ในโค้ด EL1 (
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);เรียกใช้ HVC ในโค้ด EL1 (
el1.c)pkvm_el2_mod_call(hvc_number);
ดีบักและโปรไฟล์โค้ด EL2
ส่วนนี้มีตัวเลือกหลายรายการสำหรับดีบักโค้ด EL2 ของโมดูล pKVM
ส่งและอ่านเหตุการณ์การติดตามไฮเปอร์ไวเซอร์
Tracefs รองรับไฮเปอร์ไวเซอร์ pKVM ผู้ใช้รากมีสิทธิ์เข้าถึงอินเทอร์เฟซซึ่งอยู่ใน /sys/kernel/tracing/hypervisor/ ดังนี้
tracing_on: เปิดหรือปิดการติดตามtrace: การเขียนลงในไฟล์นี้จะรีเซ็ตการติดตามtrace_pipe: การอ่านไฟล์นี้จะพิมพ์เหตุการณ์ไฮเปอร์ไวเซอร์buffer_size_kb: ขนาดบัฟเฟอร์ต่อ CPU ที่เก็บเหตุการณ์ เพิ่มค่านี้หากเหตุการณ์สูญหาย
เหตุการณ์จะปิดอยู่โดยค่าเริ่มต้น หากต้องการเปิดใช้เหตุการณ์ ให้ใช้ไฟล์ /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()
ช่วยให้ผู้เรียกส่งอาร์กิวเมนต์ได้สูงสุด 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
เปิดใช้เหตุการณ์ __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 มีเหตุการณ์ 2 รายการ ได้แก่ 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