pKVM tedarikçi modülü uygulama

Bu sayfada, korumalı çekirdek tabanlı sanal makine (pKVM) satıcı modülünün nasıl uygulanacağı açıklanmaktadır.

android16-6.12 ve sonraki sürümlerde bu adımları tamamladığınızda şuna benzer bir dizin ağacınız olmalıdır:

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

Eksiksiz bir örnek için DDK ile pKVM modülü oluşturma başlıklı makaleyi inceleyin .

Android 15-6.6 ve önceki sürümler için:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. EL2 hiper yönetici kodunu (el2.c) ekleyin. Bu kod, en azından pkvm_module_ops yapısına referans kabul eden bir init işlevi bildirmelidir:

    #include <asm/kvm_pkvm_module.h>
    
    int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops)
    {
      /* Init the EL2 code */
    
      return 0;
    }
    

    pKVM satıcı modülü API'si, pKVM hipervizörüne geri çağırmaları kapsayan bir yapıdır. Bu yapı, GKI arayüzleriyle aynı ABI kurallarını izler.

  2. Hiper yönetici kodunu oluşturmak için hyp/Makefile oluşturun:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. EL1 çekirdek kodunu (el1.c) ekleyin. Bu kodun init bölümü, 1. adımdaki EL2 hiper yönetici kodunu yüklemek için pkvm_load_el2 module çağrısını içermelidir.

    #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. Derleme kurallarını oluşturun.

    android16-6.12 ve sonraki sürümlerde, EL2 için ddk_library(), EL1 için ddk_module() oluşturmak üzere DDK ile pKVM modülü oluşturma başlıklı makaleyi inceleyin.

    android15-6.6 ve önceki sürümlerde, EL1 ve EL2 kodunu birbirine bağlamak için kök makefile'ı oluşturun:

    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 modülü yükleme

GKI satıcı modüllerinde olduğu gibi, pKVM satıcı modülleri de modprobe kullanılarak yüklenebilir. Ancak güvenlik nedeniyle, ayrıcalık kaldırma işleminden önce yükleme yapılması gerekir. Bir pKVM modülünü yüklemek için modüllerinizin kök dosya sistemine (initramfs) dahil edildiğinden emin olmanız ve çekirdek komut satırınıza aşağıdakileri eklemeniz gerekir:

kvm-arm.protected_modules=mod1,mod2,mod3,...

initramfs içinde saklanan pKVM satıcı modülleri, initramfs'nın imzasını ve korumasını devralır.

pKVM satıcı modüllerinden biri yüklenemezse sistem güvenli kabul edilmez ve korumalı bir sanal makine başlatılamaz.

EL1'den (çekirdek modülü) EL2 (hipervizör) işlevini çağırma

Hipervizör çağrısı (HVC), çekirdeğin hipervizörü çağırmasına olanak tanıyan bir talimattır. pKVM satıcı modüllerinin kullanıma sunulmasıyla birlikte, bir HVC, EL1'den (çekirdek modülü) EL2'de (hiper yönetici modülünde) çalıştırılacak bir işlevi çağırmak için kullanılabilir:

  1. EL2 kodunda (el2.c), EL2 işleyiciyi bildirin:

Android 14

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

     cpu_reg(ctx, 1) = 0;
   }

Android 15 veya sonraki sürümler

   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 kodunuzda (el1.c), pKVM tedarikçi modülünüzde EL2 işleyicisini kaydedin:

    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 kodunuzda (el1.c) HVC'yi arayın:

    pkvm_el2_mod_call(hvc_number);
    

EL2 kodunda hata ayıklama ve profil oluşturma

Bu bölümde, pKVM modülü EL2 kodunda hata ayıklamak için çeşitli seçenekler yer alır.

Hipervizör izleme etkinliklerini yayınlama ve okuma

Tracefs, pKVM hipervizörünü destekler. Root kullanıcısı, /sys/kernel/tracing/hypervisor/ konumundaki arayüze erişebilir:

  • tracing_on: İzlemeyi açar veya kapatır.
  • trace: Bu dosyaya yazmak izlemeyi sıfırlar.
  • trace_pipe: Bu dosyanın okunması, hipervizör etkinliklerini yazdırır.
  • buffer_size_kb: Etkinlikleri tutan CPU başına arabelleğin boyutu. Etkinlikler kayboluyorsa bu değeri artırın.

Etkinlikler varsayılan olarak devre dışıdır. Etkinlikleri etkinleştirmek için Tracefs'deki ilgili /sys/kernel/tracing/hypervisor/events/my_event/enable dosyasını kullanın. Ayrıca, hyp_event=event1,event2 çekirdek komut satırıyla önyükleme sırasında herhangi bir hipervizör etkinliğini etkinleştirebilirsiniz.

Bir etkinlik bildirilmeden önce, modülün EL2 kodu aşağıdaki standart metni bildirmelidir. Burada pkvm_ops, modülün init işlevine iletilen struct pkvm_module_ops *'dir:

  #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

Etkinlikleri bildirme

Etkinlikleri kendi .h dosyalarında bildirin:

  $ 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

Etkinlik yayınlama

Oluşturulan C işlevini çağırarak EL2 kodunda etkinlikleri günlüğe kaydedebilirsiniz:

  trace_pkvm_driver_event(id);

Ek kayıt ekleme (Android 15 veya önceki sürümler)

Android 15 ve önceki sürümlerde, modül başlatma sırasında ek bir kayıt ekleyin. Bu, Android 16 ve sonraki sürümlerde gerekli değildir.

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

Önceden bildirimde bulunmadan etkinlik yayınlama (Android 16 ve sonraki sürümler)

Etkinlikleri bildirmek, hızlı hata ayıklama için zahmetli olabilir. trace_hyp_printk() Arayanın, bir biçim dizesine herhangi bir etkinlik bildirimi olmadan en fazla dört bağımsız değişken geçirmesine olanak tanır:

  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 kodunda standart bir metin de gereklidir. trace_hyp_printk(), trace___hyp_printk() işlevini çağıran bir makrodur:

  #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

Etkinliği /sys/kernel/tracing/hypervisor/events/ içinde veya hyp_event=__hyp_printk çekirdek komut satırıyla başlatma sırasında __hyp_printk etkinleştirin.

Etkinlikleri dmesg'ye yönlendirme

Çekirdek komut satırı parametresi hyp_trace_printk=1, hiper yönetici izleme arayüzünün her kaydedilen etkinliği çekirdeğin dmesg bölümüne iletmesini sağlar. Bu, trace_pipe erişilemediğinde etkinlikleri okumak için yararlıdır.

Kernel panik sırasında döküm etkinlikleri (Android 16 ve sonraki sürümler)

Hiper yönetici etkinlikleri yoklanır. Bu nedenle, son yoklama ile çekirdek panik arasında, etkinliklerin yayınlandığı ancak konsola dökülmediği bir pencere vardır. Çekirdek yapılandırma seçeneği CONFIG_PKVM_DUMP_TRACE_ON_PANIC, hyp_trace_printk etkinleştirilmişse konsoldaki en son etkinlikleri boşaltmaya çalışır.

Bu seçenek, GKI için varsayılan olarak devre dışıdır.

İşlev çağrısını ve dönüşünü izlemek için Ftrace'i kullanma (Android 16 ve sonraki sürümler)

Ftrace, her işlev çağrısını ve dönüşünü izlemenize olanak tanıyan bir çekirdek özelliğidir. Benzer şekilde, pKVM hipervizörü iki etkinlik sunar: func ve func_ret.

İzlenen işlevleri çekirdek komut satırıyla hyp_ftrace_filter= veya tracefs dosyalarından biriyle seçebilirsiniz:

  • /sys/kernel/tracing/hypervisor/set_ftrace_filter
  • /sys/kernel/tracing/hypervisor/set_ftrace_notrace

Filtreler, kabuk stili glob eşleşmesi kullanır.

Aşağıdaki filtre, pkvm_hyp_driver ile başlayan işlevleri izler:

  echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter

func ve func_ret etkinlikleri yalnızca CONFIG_PKVM_FTRACE=y ile kullanılabilir. Bu seçenek, GKI için varsayılan olarak devre dışıdır.