একটি pKVM ভেন্ডর মডিউল প্রয়োগ করুন

এই পৃষ্ঠায় একটি সুরক্ষিত কার্নেল-ভিত্তিক ভার্চুয়াল মেশিন (pKVM) ভেন্ডর মডিউল কীভাবে বাস্তবায়ন করতে হয় তা ব্যাখ্যা করা হয়েছে।

অ্যান্ড্রয়েড ১৬-৬.১২ এবং পরবর্তী সংস্করণগুলোর জন্য, এই ধাপগুলো সম্পন্ন করার পর আপনার ডিরেক্টরি ট্রি দেখতে অনেকটা এরকম হবে:

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

একটি সম্পূর্ণ উদাহরণের জন্য, "DDK দিয়ে একটি pKVM মডিউল তৈরি করুন" দেখুন।

অ্যান্ড্রয়েড ১৫-৬.৬ এবং পূর্ববর্তী সংস্করণগুলোর জন্য:

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 ভেন্ডর মডিউল API হলো একটি স্ট্রাক্ট যা 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. বিল্ড নিয়মগুলো তৈরি করুন।

    android16-6.12 এবং পরবর্তী সংস্করণগুলোর জন্য, EL2-এর জন্য ddk_library() এবং EL1-এর জন্য ddk_module() তৈরি করতে "Build a pKVM module with DDK" অংশটি দেখুন।

    অ্যান্ড্রয়েড ১৫-৬.৬ এবং তার পূর্ববর্তী সংস্করণগুলোর জন্য, 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 ভেন্ডর মডিউল চালু হওয়ার ফলে, EL1 (কার্নেল মডিউল) থেকে EL2 (হাইপারভাইজর মডিউলে) কোনো ফাংশন চালানোর জন্য HVC ব্যবহার করা যায়।

  1. EL2 কোডে ( el2.c ) EL2 হ্যান্ডলারটি ডিক্লেয়ার করুন:

অ্যান্ড্রয়েড ১৪

   void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
   {
     /* Handle the call */

     cpu_reg(ctx, 1) = 0;
   }

অ্যান্ড্রয়েড ১৫ বা তার উচ্চতর

   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 ) HVC-কে কল করুন:

    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);

অতিরিক্ত নিবন্ধন যোগ করুন (অ্যান্ড্রয়েড ১৫ বা তার নিম্নতর সংস্করণ)

অ্যান্ড্রয়েড ১৫ এবং এর নিচের সংস্করণগুলোর জন্য, মডিউল ইনিশিয়ালাইজেশনের সময় একটি অতিরিক্ত রেজিস্ট্রেশন অন্তর্ভুক্ত করতে হবে। অ্যান্ড্রয়েড ১৬ এবং এর উপরের সংস্করণগুলোতে এর প্রয়োজন নেই।

  #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;
  }

পূর্ব ঘোষণা ছাড়াই ইভেন্ট নির্গত করুন (অ্যান্ড্রয়েড ১৬ এবং উচ্চতর সংস্করণ)

দ্রুত ডিবাগিংয়ের জন্য ইভেন্ট ঘোষণা করা ঝামেলার হতে পারে। 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

/sys/kernel/tracing/hypervisor/events/ ফাইলে অথবা বুট করার সময় কার্নেল কমান্ড-লাইন hyp_event=__hyp_printk ব্যবহার করে __hyp_printk ইভেন্টটি সক্রিয় করুন।

ইভেন্টগুলিকে dmesg-এ পুনঃনির্দেশ করুন

কার্নেল কমান্ড-লাইন প্যারামিটার hyp_trace_printk=1 হাইপারভাইজর ট্রেসিং ইন্টারফেসকে প্রতিটি লগ করা ইভেন্ট কার্নেলের dmesg এ ফরোয়ার্ড করতে নির্দেশ দেয়। trace_pipe অ্যাক্সেসযোগ্য না হলে ইভেন্টগুলো পড়ার জন্য এটি কার্যকর।

কার্নেল প্যানিকের সময় ডাম্প ইভেন্ট (অ্যান্ড্রয়েড ১৬ এবং উচ্চতর সংস্করণ)

হাইপারভাইজর ইভেন্টগুলো পোল করা হয়। তাই, শেষ পোল এবং কার্নেল প্যানিকের মধ্যে একটি সময়কাল থাকে, যখন ইভেন্টগুলো নির্গত হলেও কনসোলে ডাম্প করা হয় না। যদি hyp_trace_printk সক্রিয় করা থাকে, তবে কার্নেল কনফিগ অপশন CONFIG_PKVM_DUMP_TRACE_ON_PANIC সবচেয়ে সাম্প্রতিক ইভেন্টগুলো কনসোলে ডাম্প করার চেষ্টা করে।

GKI-এর জন্য এই অপশনটি ডিফল্টরূপে নিষ্ক্রিয় থাকে।

ফাংশন কল এবং রিটার্ন ট্রেস করতে Ftrace ব্যবহার করুন (অ্যান্ড্রয়েড ১৬ এবং তার পরবর্তী সংস্করণ)

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-এর জন্য এই অপশনটি ডিফল্টরূপে নিষ্ক্রিয় থাকে।