實作 pKVM 供應商模組

本頁說明如何實作受保護的以核心為基礎的虛擬機器 (pKVM) 供應商模組。

如果是 android16-6.12 以上版本,完成這些步驟後,您應該會有類似下列的目錄樹:

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

如需完整範例,請參閱「使用 DDK 建構 pKVM 模組」。

適用於 android15-6.6 以下版本:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. 新增 EL2 Hypervisor 程式碼 (el2.c)。這段程式碼至少須宣告接受 pkvm_module_ops 結構體參照的 init 函式:

    #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 的呼叫,才能載入步驟 1 中的 EL2 Hypervisor 程式碼。

    #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 以上版本,請參閱「使用 DDK 建構 pKVM 模組」,為 EL2 建立 ddk_library(),並為 EL1 建立 ddk_module()

    如果是 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 模組

與 GKI 供應商模組相同,pKVM 供應商模組可使用 modprobe 載入。不過,基於安全考量,載入作業必須在解除權限前完成。 如要載入 pKVM 模組,請務必將模組納入根檔案系統 (initramfs),並在核心指令列中新增下列項目:

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

儲存在 initramfs 中的 pKVM 供應商模組會沿用 initramfs 的簽章和保護機制。

如果其中一個 pKVM 供應商模組無法載入,系統會視為不安全,且無法啟動受保護的虛擬機器。

從 EL1 (核心模組) 呼叫 EL2 (管理程序) 函式

管理程序呼叫 (HVC) 是一種指令,可讓核心呼叫管理程序。隨著 pKVM 供應商模組的推出,HVC 可用於從 EL1 (核心模組) 呼叫函式,在 EL2 (在 Hypervisor 模組中) 執行:

  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) 中,呼叫 HVC:

    pkvm_el2_mod_call(hvc_number);
    

偵錯及剖析 EL2 程式碼

本節提供多個選項,可偵錯 pKVM 模組 EL2 程式碼。

發出及讀取管理程序追蹤記錄事件

Tracefs 支援 pKVM 管理程序。Root 使用者可以存取介面,該介面位於 /sys/kernel/tracing/hypervisor/

  • tracing_on:開啟或關閉追蹤功能。
  • trace:寫入這個檔案會重設追蹤記錄。
  • trace_pipe:讀取這個檔案會列印管理程序事件。
  • buffer_size_kb:每個 CPU 的緩衝區大小,用於保存事件。如果遺失事件,請增加這個值。

根據預設,事件會處於停用狀態。如要啟用事件,請使用 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

/sys/kernel/tracing/hypervisor/events/ 中啟用事件 __hyp_printk,或在啟動時使用核心指令列 hyp_event=__hyp_printk 啟用。

將事件重新導向至 dmesg

核心指令列參數 hyp_trace_printk=1 會讓管理程序追蹤介面將每個記錄的事件轉送至核心的 dmesg。如果無法存取 trace_pipe,這項功能就很有用。

在核心恐慌期間傾印事件 (Android 16 以上版本)

系統會輪詢 Hypervisor 事件。因此,在最後一次輪詢和核心恐慌之間,會有一段時間發出事件,但不會傾印至控制台。如果已啟用 hyp_trace_printk,核心設定選項 CONFIG_PKVM_DUMP_TRACE_ON_PANIC 會嘗試傾印控制台中的最新事件。

GKI 預設會停用這個選項。

使用 Ftrace 追蹤函式呼叫和傳回 (Android 16 以上版本)

Ftrace 是一項核心功能,可追蹤每個函式呼叫和傳回。同樣地,pKVM 管理程序提供 funcfunc_ret 兩種事件。

您可以使用核心指令列 hyp_ftrace_filter= 或其中一個 tracefs 檔案,選取要追蹤的函式:

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

篩選器會使用 Shell 樣式的 glob 比對。

下列篩選器會追蹤以 pkvm_hyp_driver 開頭的函式:

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

funcfunc_ret 事件僅適用於 CONFIG_PKVM_FTRACE=y。 GKI 預設會停用這個選項。