pKVM वेंडर मॉड्यूल लागू करना

इस पेज पर, सुरक्षित कर्नल पर आधारित वर्चुअल मशीन (पीकेवीएम) वेंडर मॉड्यूल को लागू करने का तरीका बताया गया है.

Android 16-6.12 और इसके बाद के वर्शन के लिए, इन चरणों को पूरा करने के बाद, आपके पास इस तरह का डायरेक्ट्री ट्री होना चाहिए:

BUILD.bazel
el1.c
hyp/
    BUILD.bazel
    el2.c

पूरे उदाहरण के लिए, डीडीके की मदद से pKVM मॉड्यूल बनाना लेख पढ़ें.

android15-6.6 और इससे पहले के वर्शन के लिए:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. 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;
    }
    

    pKVM वेंडर मॉड्यूल एपीआई एक स्ट्रक्चर है. इसमें pKVM हाइपरवाइज़र को कॉल करने के लिए इस्तेमाल किए जाने वाले कॉलबैक शामिल होते हैं. यह स्ट्रक्ट, GKI इंटरफ़ेस के लिए बने ABI के नियमों का पालन करता है.

  2. हाइपरवाइज़र कोड बनाने के लिए, hyp/Makefile बनाएं:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. EL1 कर्नल कोड (el1.c) जोड़ें. इस कोड के init सेक्शन में, pkvm_load_el2 module को कॉल करना ज़रूरी है, ताकि पहले चरण से EL2 हाइपरवाइज़र कोड लोड किया जा सके.

    #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);
    
  4. बिल्ड के नियम बनाएं.

    Android 16-6.12 और इसके बाद के वर्शन के लिए, EL2 के लिए ddk_library() और EL1 के लिए ddk_module() बनाने के लिए, डीडीके की मदद से pKVM मॉड्यूल बनाएं लेख पढ़ें.

    android15-6.6 और इससे पहले के वर्शन के लिए, 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 (हाइपरवाइज़र) फ़ंक्शन को कॉल करना

हाइपरवाइज़र कॉल (एचवीसी) एक ऐसा निर्देश होता है जिसकी मदद से कर्नल, हाइपरवाइज़र को कॉल कर सकता है. pKVM वेंडर मॉड्यूल के लॉन्च होने के बाद, एचवीसी का इस्तेमाल इन कामों के लिए किया जा सकता है: EL1 (कर्नेल मॉड्यूल) से EL2 (हाइपरवाइज़र मॉड्यूल) पर फ़ंक्शन चलाने के लिए कॉल करना:

  1. 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;
   }
  1. अपने 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);
    
  2. अपने EL1 कोड (el1.c) में, एचवीसी को कॉल करें:

    pkvm_el2_mod_call(hvc_number);
    

EL2 कोड को डीबग और प्रोफ़ाइल करना

इस सेक्शन में, pKVM मॉड्यूल के EL2 कोड को डीबग करने के कई विकल्प दिए गए हैं.

हाइपरवाइज़र के ट्रेस इवेंट को भेजना और पढ़ना

Tracefs, pKVM हाइपरवाइज़र के साथ काम करता है. रूट उपयोगकर्ता के पास इंटरफ़ेस का ऐक्सेस होता है. यह इंटरफ़ेस /sys/kernel/tracing/hypervisor/ में मौजूद होता है:

  • tracing_on: इस विकल्प से, ट्रेसिंग की सुविधा चालू या बंद की जाती है.
  • trace: इस फ़ाइल में लिखने से, ट्रेस रीसेट हो जाता है.
  • trace_pipe: इस फ़ाइल को पढ़ने पर, हाइपरवाइज़र इवेंट प्रिंट होते हैं.
  • buffer_size_kb: हर सीपीयू के हिसाब से इवेंट सेव करने वाले बफ़र का साइज़. अगर इवेंट का डेटा नहीं मिल रहा है, तो इस वैल्यू को बढ़ाएं.

डिफ़ॉल्ट रूप से, इवेंट बंद होते हैं. इवेंट चालू करने के लिए, 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() कॉल करने वाले को, इवेंट का एलान किए बिना फ़ॉर्मैट स्ट्रिंग में ज़्यादा से ज़्यादा चार आर्ग्युमेंट पास करने की अनुमति देता है:

  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_event=__hyp_printk की मदद से, बूट टाइम पर या /sys/kernel/tracing/hypervisor/events/ में इवेंट __hyp_printk चालू करें.

इवेंट को dmesg पर रीडायरेक्ट करना

कर्नल कमांड-लाइन पैरामीटर hyp_trace_printk=1, हाइपरवाइज़र ट्रेसिंग इंटरफ़ेस को हर लॉग किए गए इवेंट को कर्नल के dmesg पर फ़ॉरवर्ड करने के लिए कहता है. trace_pipe के ऐक्सेस न होने पर, इवेंट पढ़ने के लिए यह कुकी काम की होती है.

कर्नल पैनिक के दौरान डंप इवेंट (Android 16 और इसके बाद के वर्शन)

हाइपरवाइज़र इवेंट पोल किए जाते हैं. इसलिए, आखिरी पोल और कर्नल पैनिक के बीच एक विंडो होती है, जहां इवेंट भेजे जाते हैं, लेकिन उन्हें कंसोल में डंप नहीं किया जाता है. अगर CONFIG_PKVM_DUMP_TRACE_ON_PANIC चालू है, तो कर्नल कॉन्फ़िगरेशन का विकल्प 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 के लिए डिफ़ॉल्ट रूप से बंद होता है.