供應商原生開發套件 (VNDK)


供應商原生開發套件 (VNDK)

供應商原生開發套件 (VNDK) 是專門用來讓供應商實現其 HAL 的一組庫。VNDK 包含在 system.img 中,並在運行時與供應商代碼動態關聯。

為何要使用 VNDK?

Android 8.0 及更高版本支持框架專用更新,在此類更新中,系統分區可以升級到最新版本,而供應商分區保持不變。這意味着在不同時間編譯的二進制文件必須能夠相互配合使用;VNDK 為 Android 版本迭代時發生的 API/ABI 變更所導致的問題提供了解決途徑。

框架專用更新面臨着以下挑戰:

  • 框架模塊與供應商模塊之間的依賴關系。在 Android 8.0 之前的版本中,兩種模塊可以互相關聯。但是,來自供應商模塊的依賴關系對框架模塊開發施加了不必要的限制。
  • AOSP 庫的擴展。Android 8.0 及更高版本要求所有 Android 設備在系統分區被替換為標准常規系統映像 (GSI) 時,都可以通過 CTS 測試。不過,當供應商擴展 AOSP 庫以提高性能或為其 HIDL 實現添加額外的功能時,使用標准 GSI 來刷寫系統分區可能會破壞供應商的 HIDL 實現(有關如何防止此類破壞的指南,請參閱 VNDK 擴展)。

為了克服這些挑戰,Android 8.0 引入了一些技術,例如 VNDK(本部分對其進行了介紹)、HIDL、hwbinder、設備樹疊加層和 sepolicy 疊加層。

VNDK 資源

本部分包含以下 VNDK 資源:

  • VNDK 概念:(請參閱下文)介紹了框架共享庫、Same-Process HAL (SP-HAL) 和 VNDK 術語。
  • VNDK 擴展:對專門針對供應商的更改進行了分類。例如,具有供應商模塊所依賴的擴展功能的庫必須復制到供應商分區中,但禁止進行 ABI 不兼容的更改。
  • VNDK 編譯系統支持:介紹了與 VNDK 相關的編譯系統配置和模塊定義語法。
  • VNDK 定義工具:可協助您將源代碼樹遷移到 Android 8.0 及更高版本。
  • 鏈接器命名空間:提供對共享庫關聯的精細控制。
  • 目錄、規則和 sepolicy:定義了搭載 Android 8.0 及更高版本的設備的目錄結構,以及 VNDK 規則和關聯的 sepolicy。
  • VK 設計演示文稿:闡述了 Android 8.0 及更高版本中使用的 VDNK 基本概念。

VNDK 概念

在理想的 Android 8.0 及更高版本環境中,框架進程不加載供應商共享庫,所有供應商進程僅加載供應商共享庫(和一部分框架共享庫),而框架進程與供應商進程之間的通信由 HIDL 和硬件 binder 控制。

這樣的環境存在以下可能:來自框架共享庫的穩定、公共 API 可能不足以滿足供應商模塊開發者的需求(盡管 API 在不同的 Android 版本之間會有所變化),要防止出現這種情況,供應商進程需要能夠訪問一部分框架共享庫。此外,由於性能要求可能會導致折中方案,因此必須區別對待某些對響應時間要求嚴格的 HAL。

以下部分詳細介紹了 VNDK 如何處理適用於供應商的框架共享庫以及 Same-Process HAL (SP-HAL)。

適用於供應商的框架共享庫

本部分介紹了供應商進程可訪問的共享庫的分類標准。要讓供應商模塊在多個 Android 版本上皆可正常工作,有以下兩種方法:

  1. 讓框架共享庫的 ABI/API 保持穩定。新的框架模塊和舊的供應商模塊可以使用同一共享庫,以減少內存占用和存儲空間占用。此外,通過使用同一共享庫,還可以避免一些雙重加載問題。不過,保持穩定的 ABI/API 的開發成本很高,因此讓每個框架共享庫導出的所有 ABI/API 都保持穩定是不現實的。
  2. 復制舊的框架共享庫。此方法會嚴重限制邊信道,即在框架模塊與供應商模塊之間進行通信的所有機制,包括(但不限於)binder、套接字、管道、共享內存、共享文件和系統屬性。除非通信協議被凍結且保持穩定(例如通過 hwbinder 的 HIDL),否則不能進行通信。雙重加載共享庫也可能會導致出現問題;例如,如果將新庫創建的對象傳遞到舊庫的函數中,則可能會出錯,因為這些庫可能會以不同的方式解讀該對象。

根據共享庫的特性不同,使用的方法也有差異。因此,框架共享庫可分為以下三個子類別:

  • LL-NDK 庫是已知穩定的框架共享庫。它們的開發者致力於保持其 API/ABI 穩定性。
    • LL-NDK 包含以下庫:libEGL.solibGLESv1_CM.solibGLESv2.solibGLESv3.solibandroid_net.solibc.solibdl.soliblog.solibm.solibnativewindow.solibneuralnetworks.solibsync.solibvndksupport.so 和 libvulkan.so
  • 符合條件的 VNDK 庫 (VNDK) 是指可以安全復制兩次的框架共享庫。框架模塊和供應商模塊可以與其各自的庫副本相關聯。框架共享庫只有滿足以下條件才能成為符合條件的 VNDK 庫:
    • 不向框架發送或從框架接收 IPC。
    • 與 ART 虛擬機無關。
    • 不讀取/寫入文件格式不穩定的文件/分區。
    • 沒有需要法律審查的特殊軟件許可。
    • 其代碼所有者不反對供應商使用該庫。
  • 框架專用庫 (FWK-ONLY) 是指不屬於上述類別的框架共享庫。此類庫具有以下特點:
    • 被視為框架內部實現細節。
    • 不得由供應商模塊訪問。
    • 具有不穩定的 ABI/API,無 API/ABI 兼容性保證。
    • 不會被復制。

Same-Process HAL (SP-HAL)

Same-Process HAL (SP-HAL) 是一組預先確定的 HAL,作為供應商共享庫進行實現,並被加載到框架進程中。SP-HAL 由鏈接器命名空間(控制共享庫可見的庫和符號)進行隔離。SP-HAL 必須僅依賴於 LL-NDK 和 VNDK-SP。

VNDK-SP 是一部分預定義的符合條件的 VNDK 庫。我們會仔細審查 VNDK-SP 庫,以確保將 VNDK-SP 庫雙重加載到框架進程中不會導致問題。SP-HAL 和 VNDK-SP 均由 Google 定義。

以下庫是經過批准的 SP-HAL:

  • libGLESv1_CM_${driver}.so
  • libGLESv2_${driver}.so
  • libGLESv3_${driver}.so
  • libEGL_${driver}.so
  • vulkan.${driver}.so
  • android.hardware.renderscript@1.0-impl.so
  • android.hardware.graphics.mapper@2.0-impl.so

以下庫是 SP-HAL 可以訪問的 VNDK-SP 庫:

  • android.hardware.graphics.common@1.0.so
  • android.hardware.graphics.mapper@2.0.so
  • android.hardware.renderscript@1.0.so (Renderscript)
  • libRS_internal.so (Renderscript)
  • libbase.so
  • libc++.so
  • libcutils.so
  • libhardware.so
  • libhidlbase.so
  • libhidltransport.so
  • libhwbinder.so
  • libion.so
  • libutils.so
  • libz.so

以下 VNDK-SP 依賴項 (VNDK-SP-Private) 對 SP-HAL 來說是不可見的:

  • libRSCpuRef.so (Renderscript)
  • libRSDriver.so (Renderscript)
  • libbacktrace.so
  • libblas.so (Renderscript)
  • libbcinfo.so (Renderscript)
  • liblzma.so
  • libunwind.so

以下是具有 RS 例外的框架專用庫 (FWK-ONLY-RS):

  • libft2.so (Renderscript)
  • libmediandk.so (Renderscript)

VNDK 術語

  • 模塊是指共享庫或可執行文件。
  • 進程是指可執行文件產生的操作系統任務。
  • 以“框架”打頭的術語是指與系統分區相關的概念。
  • 以“供應商”打頭的術語是指與供應商分區相關的概念。

例如:

    • 框架可執行文件是指 /system/bin 或 /system/xbin 中的可執行文件。
    • 框架共享庫是指 /system/lib[64] 下的共享庫。
    • 框架模塊是指框架共享庫和框架可執行文件。
    • 框架進程是指從框架可執行文件衍生而來的進程(例如 /system/bin/app_process)。
    • 供應商可執行文件是指 /vendor/bin 中的可執行文件。
    • 供應商共享庫是指 /vendor/lib[64] 下的共享庫。
    • 供應商模塊是指供應商可執行文件和供應商共享庫。
    • 供應商進程是指供應商可執行文件(例如
/vendor/bin/android.hardware.camera.provider@2.4-service
    )產生的進程。
注意:通用系統映像 (GSI) 是指根據相應分支(與版本分支類似,但更正了一些錯誤或進行了一些泛化)編譯並由 Google 發布的標准 Android 系統映像。

VNDK 版本控制

在 Android 9 中,VNDK 共享庫帶有版本編號:

  • ro.vndk.version 系統屬性將自動添加到 /vendor/default.prop
  • VNDK 共享庫將安裝到 /system/lib[64]/vndk-${ro.vndk.version} 中。
  • VNDK-SP 共享庫將安裝到 /system/lib[64]/vndk-sp-${ro.vndk.version} 中。
  • 動態鏈接器配置文件將安裝到 /system/etc/ld.config.${ro.vndk.version}.txt 中。

系統將按以下算法選擇 ro.vndk.version 的值:

  • 如果 BOARD_VNDK_VERSION 不等於 current,則使用 BOARD_VNDK_VERSION
  • 如果 BOARD_VNDK_VERSION 等於 current
    • 如果 PLATFORM_VERSION_CODENAME 為 REL,則使用 PLATFORM_SDK_VERSION(例如 28)。
    • 否則使用 PLATFORM_VERSION_CODENAME(例如 P)。

升級設備

如果 Android 8.x 設備停用了 VNDK 運行時增強功能(即,編譯時未使用 BOARD_VNDK_VERSION 或使用了 BOARD_VNDK_RUNTIME_DISABLE),則在升級到 Android 9 時,可能會將 PRODUCT_USE_VNDK_OVERRIDE := false 添加到 BoardConfig.mk

如果 PRODUCT_USE_VNDK_OVERRIDE 為 false,則 ro.vndk.lite 屬性將自動添加到 /vendor/default.prop,且其值將為 true。因此,動態鏈接器將加載 /system/etc/ld.config.vndk_lite.txt 中的鏈接器命名空間配置,這僅會隔離 SP-HAL 和 VNDK-SP。

要將 Android 7.0 或更低版本的設備升級到 Android 9,請將 PRODUCT_TREBLE_LINKER_NAMESPACES_OVERRIDE := false 添加到 BoardConfig.mk

供應商測試套件 (VTS)

Android 9 供應商測試套件 (VTS) 強制要求存在非空 ro.vndk.version 屬性。新發布的設備和升級設備都必須定義 ro.vndk.version。一些 VNDK 測試用例(例如 VtsVndkFilesTest 和 VtsVndkDependencyTest)依賴於 ro.vndk.version 屬性來加載符合條件且匹配的 VNDK 庫數據集。

如果 ro.product.first_api_level 屬性大於 27,則不能定義 ro.vndk.lite 屬性。 如果在新推出的 Android 9 設備中定義了 ro.vndk.lite,則 VtsTreblePlatformVersionTest 將失敗。

文檔歷史記錄

本部分跟蹤了對 VNDK 文檔進行的更改。

Android 9 的變化

  • 添加了“VNDK 版本編號”部分。
  • 添加了“VTS”部分。
  • 更改了部分 VNDK 類別的名稱:
    • LL-NDK-Indirect 已改名為 LL-NDK-Private。
    • VNDK-Indirect 已改名為 VNDK-Private。
    • VNDK-SP-Indirect-Private 已改名為 VNDK-SP-Private。
    • 移除了 VNDK-SP-Indirect。

Android 8.1 的變化

  • SP-NDK 庫已合並到 LL-NDK 庫中。
  • 在 RS 命名空間部分中將 libui.so 替換為 libft2.so。包含 libui.so 會出錯。
  • 將 libGLESv3.so 和 libandroid_net.so 添加到 LL-NDK 庫中。
  • 將 libion.so 添加到 VNDK-SP 庫中。
  • 從 LL-NDK 庫中移除 libstdc++.so。改用 libc++.so。某些版本的獨立工具鏈可以將 -lstdc++ 添加到默認鏈接器標記中。要停用默認設置,請將 -nodefaultlibs -lc -lm -ldl 添加到 LDFLAGS 中。
  • 將 libz.so 從 LL-NDK 移到 VNDK-SP 庫中。在某些配置中,libz.so 可能仍是 LL-NDK。但是,應該沒有明顯的差異。

啟用 VNDK

為了區隔供應商模塊與系統模塊,您在啟用 VNDK 前需要對代碼庫進行一些更改。請按照以下指南在供應商/OEM 代碼庫中啟用 VNDK。

編譯系統庫

編譯系統包含多種類型的對象,其中包括庫(共享、靜態或標頭)和二進制文件:

圖 1. 編譯系統庫。
  • core:位於系統映像中,由系統映像使用。vendorvendor_availablevndk 或 vndk-sp 庫不能使用此類庫。
    cc_library {
        name: "libThatIsCore",
        ...
    }
     
  • vendor-only(或 proprietary)。位於供應商映像中,由供應商映像使用。
    cc_library {
        name: "libThatIsVendorOnly",
        proprietary: true,
        # or: vendor: true, # (for things in AOSP)
        ...
    }

     
  • vendor_available:位於供應商映像中,由供應商映像使用(可能包含 core 的重復項)。
    cc_library {
        name: "libThatIsVendorAvailable",
        vendor_available: true,
        ...
    }
     
  • vndk:位於系統映像中,由供應商映像使用(vendor_available 的子集)。
    cc_library {
        name: "libThatIsVndk",
        vendor_available: true,
        vndk: {
            enabled: true,
        }
        ...
    }
     
  • vndk-sp:位於系統映像中,由系統映像間接使用(core 的子集)。
    cc_library {
        name: "libThatIsVndkSp",
        vendor_available: true,
        vndk: {
            enabled: true,
            support_system_process: true,
        }
        ...
    }
     
  • llndk:同時由系統映像和供應商映像使用。
    llndk_library {
        name: "libThasIsLlndk",
    }
     

當一個庫被標記為 vendor_available:true 時,它將編譯兩次:

  • 一次是為平台編譯(因此被安裝到 /system/lib 中)。
  • 一次是為供應商編譯(因此被安裝到 /vendor/lib/system/lib/vndk 或 /system/lib/vndk-sp中)。

庫的供應商版本使用 -D__ANDROID_VNDK__ 標記編譯。您可以使用此標記停用在 Android 未來版本中可能會發生顯著變化的專用系統組件。此外,不同的庫會導出一組不同的標頭(例如 liblog)。可以在 Android.bp 文件中指定相應目標的供應商變體特有的選項:

target: { vendor: {  } }
 

為代碼庫啟用 VNDK

要為代碼庫啟用 VNDK,請執行以下操作:

  1. 通過計算 vendor.img 和 system.img 分區的所需大小來確定是否符合條件。
  2. 啟用 BOARD_VNDK_VERSION=current。您可以將其添加到 BoardConfig.mk,也可以直接使用此選項編譯組件(即 m -j BOARD_VNDK_VERSION=current MY-LIB)。

啟用 BOARD_VNDK_VERSION=current 后,編譯系統會強制執行以下依賴項和標頭要求。

管理依賴項

如果 vendor 對象依賴的 core 組件在 vndk 中不存在或未以 vendor 對象的形式存在,則必須通過以下某種方式解決該問題:

  • 可以移除該依賴項。
  • 如果該 core 組件歸 vendor 所有,則可將其標記為 vendor_available 或 vendor
  • 可以在上游向 Google 提交更改請求,以便將此核心對象列入 vndk

此外,如果有 core 組件依賴於 vendor 組件,則必須使此 vendor 組件成為 core 組件,或者以其他方式移除此依賴項(例如,通過移除依賴項或將其移到 vendor 組件中)。

管理標頭

必須移除全局標頭依賴項,編譯系統才能知道在編譯標頭時是否帶 -D__ANDROID_VNDK__。例如,您仍然可以使用標頭庫 libutils_headers 訪問 utils/StrongPointer.h 等 libutils 標頭。

某些標頭(例如 unistd.h)無法再以傳遞方式包含在內,但可以包含在本地。

最后,private/android_filesystem_config.h 的公共部分已移至 cutils/android_filesystem_config.h。要管理這些標頭,請執行下列操作之一:

  • 通過將所有 AID_* 宏替換為 getgrnam/getpwnam 調用(如果可能),移除對 private/android_filesystem_config.h 的依賴。例如:
    • (uid_t)AID_WIFI 會變為 getpwnam("wifi")->pw_uid
    • (gid_t)AID_SDCARD_R 會變為 getgrnam("sdcard_r")->gr_gid
    如需了解詳情,請參閱 private/android_filesystem_config.h
  • 對於硬編碼的 AIS,請包含 cutils/android_filesystem_config.h

VNDK 編譯系統支持

在 Android 8.1 及更高版本中,編譯系統具有內置的 VNDK 支持。如果啟用了 VNDK 支持,編譯系統就會檢查各模塊之間的依賴關系,為供應商模塊編譯特定於供應商的變體,並自動將這些模塊安裝到指定目錄中。

VNDK 編譯支持示例

在此示例中,Android.bp 模塊定義定義了一個名為 libexample 的庫。vendor_available 屬性表示框架模塊和供應商模塊均可能依賴於 libexample

設置 vendor_available:true 和 vndk.enabled:true 的 libexample

圖 1. 已啟用 VNDK 支持

框架可執行文件 /system/bin/foo 和供應商可執行文件 /vendor/bin/bar 均依賴於 libexample,並且在其 shared_libs 屬性中具有 libexample

如果框架模塊和供應商模塊均使用 libexample,則編譯 libexample 的兩個變體。核心變體(以 libexample命名)由框架模塊使用,供應商變體(以 libexample.vendor 命名)由供應商模塊使用。這兩個變體將安裝到不同的目錄中。

  • 核心變體將安裝到 /system/lib[64]/libexample.so 中。
  • 供應商變體將安裝到 /system/lib[64]/vndk/libexample.so 中,因為 vndk.enabled 為 true

如需了解詳情,請參閱模塊定義

配置編譯支持

要為產品設備啟用完整編譯系統支持,請將 BOARD_VNDK_VERSION 添加到 BoardConfig.mk

BOARD_VNDK_VERSION := current
 

此設置會產生全局效應:如果在 BoardConfig.mk 中定義,系統會檢查所有模塊。由於沒有將違規模塊列入黑名單或白名單的機制,因此在添加 BOARD_VNDK_VERSION 之前應清除所有不必要的依賴項。您可以通過在環境變量中設置 BOARD_VNDK_VERSION 來測試和編譯模塊:

$ BOARD_VNDK_VERSION=current m module_name.vendor
 

如果啟用 BOARD_VNDK_VERSION,系統會移除多個默認的全局頭文件搜索路徑。其中包括:

  • frameworks/av/include
  • frameworks/native/include
  • frameworks/native/opengl/include
  • hardware/libhardware/include
  • hardware/libhardware_legacy/include
  • hardware/ril/include
  • libnativehelper/include
  • libnativehelper/include_deprecated
  • system/core/include
  • system/media/audio/include

如果某個模塊依賴於上述目錄中的標頭,則您必須明確指定與 header_libsstatic_libs 和/或 shared_libs 的依賴關系。

模塊定義

要使用 BOARD_VNDK_VERSION 編譯 Android,您必須在 Android.mk 或 Android.bp 中修改模塊定義。此部分介紹了不同種類的模塊定義,一些與 VNDK 相關的模塊屬性,以及在編譯系統中實現的依賴性檢查。

供應商模塊

供應商模塊是特定於供應商的可執行文件或共享庫(必須將這些模塊安裝到供應商分區中)。在 Android.bp 文件中,供應商模塊必須將供應商或專有屬性設置為 true。在 Android.mk 文件中,供應商模塊必須將 LOCAL_VENDOR_MODULE 或 LOCAL_PROPRIETARY_MODULE 設置為 true

如果定義了 BOARD_VNDK_VERSION,則編譯系統不允許在供應商模塊和框架模塊之間建立依賴關系,並且編譯系統會在以下情況下發出錯誤:

  • 不具有 vendor:true 的模塊依賴於具有 vendor:true 的模塊,或
  • 具有 vendor:true 的模塊依賴於既不具有 vendor:true 也不具有 vendor_available:true 的非 llndk_library 模塊。

依賴性檢查適用於 Android.bp 中的 header_libsstatic_libs 和 shared_libs 以及 Android.mk 中的 LOCAL_HEADER_LIBRARIESLOCAL_STATIC_LIBRARIES 和 LOCAL_SHARED_LIBRARIES

LL-NDK

LL-NDK 共享庫是具有穩定 ABI 的共享庫。框架模塊和供應商模塊均具有相同的最新實現。對於每個 LL-NDK 共享庫,Android.bp 都包含一個 llndk_library 模塊定義:

llndk_library {
    name: "libvndksupport",
    symbol_file: "libvndksupport.map.txt",
}
 

該模塊定義指定了模塊名稱和符號文件,后者描述了對供應商模塊可見的符號。例如:

LIBVNDKSUPPORT {
  global:
    android_load_sphal_library; # vndk
    android_unload_sphal_library; # vndk
  local:
    *;
};
 

編譯系統會根據符號文件為供應商模塊生成存根共享庫。如果啟用了 BOARD_VNDK_VERSION,供應商模塊將與這些存根共享庫建立關聯。只有在滿足以下條件時,存根共享庫中才會包含符號:

  • 它未在以 _PRIVATE 或 _PLATFORM 結尾的部分中定義,
  • 它不含 #platform-only 標記,並且
  • 不含 #introduce* 標記或者該標記與目標匹配。
注意:供應商不得定義自己的 LL-NDK 共享庫,因為供應商模塊無法在 通用系統映像 (GSI) 中找到它們。

VNDK

在 Android.bp 文件中,cc_librarycc_library_staticcc_library_shared 和 cc_library_headers 模塊定義支持三個與 VNDK 相關的屬性:vendor_availablevndk.enabled 和 vndk.support_system_process

如果 vendor_available 或 vndk.enabled 為 true,則可以編譯兩種變體(核心變體和供應商變體)。核心變體應被視為框架模塊,而供應商變體應被視為供應商模塊。如果某些框架模塊依賴於此模塊,則會編譯核心變體。如果某些供應商模塊依賴於此模塊,則會編譯供應商變體。編譯系統會強制執行以下依賴性檢查:

  • 核心變體始終供框架專用,無法供供應商模塊訪問。
  • 供應商變體始終無法供框架模塊訪問。
  • 供應商變體的所有依賴項(在 header_libsstatic_libs 和/或 shared_libs 中指定)必須是 llndk_library 或具有 vendor_available 或 vndk.enabled 的模塊。
  • 如果 vendor_available 為 true,則供應商變體可供所有供應商模塊訪問。
  • 如果 vendor_available 為 false,則供應商變體僅可供其他 VNDK 或 VNDK-SP 模塊訪問(即,具有 vendor:true 的模塊無法與 vendor_available:false 模塊相關聯)。

系統將通過以下規則確定 cc_library 或 cc_library_shared 的默認安裝路徑:

  • 將核心變體安裝到 /system/lib[64] 中。
  • 供應商變體安裝路徑可能會有所不同:
    • 如果 vndk.enabled 為 false,則將供應商變體將安裝到 /vendor/lib[64] 中。
    • 如果 vndk.enabled 為 true,則 vndk.support_system_process 可以是 true 或 false。如果:
      • 為 false,則供應商變體將安裝到 /system/lib[64]/vndk-${VER} 中。
      • 為 true,則供應商變體將安裝到 /system/lib[64]/vndk-sp-${VER} 中。

下表總結了編譯系統如何處理供應商變體:

vendor_available vndk
已啟用
vndk
support_same_process
供應商變體說明
true false false 供應商變體為 VND-ONLY。共享庫將安裝到 /vendor/lib[64]中。
true 無效(編譯錯誤)
true false 供應商變體為 VNDK。共享庫將安裝到 /system/lib[64]/vndk-${VER} 中。
true 供應商變體為 VNDK-SP。共享庫將安裝到 /system/lib[64]/vndk-sp-${VER} 中。

false

false

false

沒有供應商變體。此模塊為 FWK-ONLY。

true 無效(編譯錯誤)
true false 供應商變體為 VNDK-Private。共享庫將安裝到 /system/lib[64]/vndk-${VER} 中。供應商模塊不得直接使用這些變體。
true 供應商變體為 VNDK-SP-Private。共享庫將安裝到 /system/lib[64]/vndk-sp-${VER} 中。供應商模塊不得直接使用這些變體。
注意:供應商可以為其模塊設置  vendor_available,但不得設置  vndk.enabled 和  vndk.support_system_process,因為供應商模塊無法在 通用系統映像 (GSI) 中找到它們。

VNDK 擴展

VNDK 擴展是具有額外 API 的 VNDK 共享庫,將安裝到 /vendor/lib[64]/vndk[-sp] 中(不含版本后綴),並在系統運行時會替換原始的 VNDK 共享庫。

定義 VNDK 擴展

在 Android 9 及更高版本中,Android.bp 本身支持 VNDK 擴展。要編譯 VNDK 擴展,請定義另一個具有 vendor:true 和 extends 屬性的模塊:

cc_library {
    name: "libvndk",
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libvndk_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk",
    },
}
 

具有 vendor:truevndk.enabled:true 和 extends 屬性的模塊可定義 VNDK 擴展:

  • extends 屬性必須指定基礎 VNDK 共享庫名稱(或 VNDK-SP 共享庫名稱)。
  • VNDK 擴展(或 VNDK-SP 擴展)以擴展時所基於的基礎模塊名稱命名。例如,libvndk_ext 的輸出二進制文件是 libvndk.so,而非 libvndk_ext.so
  • VNDK 擴展將安裝到 /vendor/lib[64]/vndk 中。
  • VNDK-SP 擴展將安裝到 /vendor/lib[64]/vndk-sp 中。
  • 基礎共享庫必須同時具有 vndk.enabled:true 和 vendor_available:true

VNDK-SP 擴展必須從 VNDK-SP 共享庫進行擴展(vndk.support_system_process 必須相等):

cc_library {
    name: "libvndk_sp",
    vendor_available: true,
    vndk: {
        enabled: true,
        support_system_process: true,
    },
}

cc_library {
    name: "libvndk_sp_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk_sp",
        support_system_process: true,
    },
}
 

VNDK 擴展(或 VNDK-SP 擴展)可以依賴於其他供應商共享庫:

cc_library {
    name: "libvndk",
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libvndk_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk",
    },
    shared_libs: [
        "libvendor",
    ],
}

cc_library {
    name: "libvendor",
    vendor: true,
}
 
注意:與 SP-HAL-Dep 類似,VNDK-SP 擴展及其依賴項(包括供應商庫)在 sepolicy 中必須標記為  same_process_hal_file

使用 VNDK 擴展

如果供應商模塊依賴於由 VNDK 擴展定義的其他 API,則該模塊必須在其 shared_libs 屬性中指定 VNDK 擴展的名稱:

// A vendor shared library example
cc_library {
    name: "libvendor",
    vendor: true,
    shared_libs: [
        "libvndk_ext",
    ],
}

// A vendor executable example
cc_binary {
    name: "vendor-example",
    vendor: true,
    shared_libs: [
        "libvndk_ext",
    ],
}
 

如果供應商模塊依賴於 VNDK 擴展,則這些 VNDK 擴展將自動安裝到 /vendor/lib[64]/vndk[-sp] 中。如果某個模塊不再依賴於 VNDK 擴展,請向 CleanSpec.mk 添加一個清理步驟,以移除共享庫。例如:

$(call add-clean-step, rm -rf $(TARGET_OUT_VENDOR)/lib/libvndk.so)
 

條件編譯

本節介紹了如何處理以下三個 VNDK 共享庫之間的細微差別(例如,為其中一個變體添加或移除某項功能):

  • 核心變體(例如 /system/lib[64]/libexample.so
  • 供應商變體(例如 /system/lib[64]/vndk[-sp]-${VER}/libexample.so
  • VNDK 擴展(例如 /vendor/lib[64]/vndk[-sp]/libexample.so

條件編譯器標記

默認情況下,Android 編譯系統會為供應商變體和 VNDK 擴展定義 __ANDROID_VNDK__。您可以使用 C 預處理器防護程序保護代碼:

void all() { }

#if !defined(__ANDROID_VNDK__)
void framework_only() { }
#endif

#if defined(__ANDROID_VNDK__)
void vndk_only() { }
#endif
 

除了 __ANDROID_VNDK__,還可以在 Android.bp 中指定不同的 cflags 或 cppflags。在 target.vendor 中指定的 cflags 或 cppflags 是專門針對供應商變體的。

例如,以下 Android.bp 定義了 libexample 和 libexample_ext

cc_library {
    name: "libexample",
    srcs: ["src/example.c"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
    target: {
        vendor: {
            cflags: ["-DLIBEXAMPLE_ENABLE_VNDK=1"],
        },
    },
}

cc_library {
    name: "libexample_ext",
    srcs: ["src/example.c"],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample",
    },
    cflags: [
        "-DLIBEXAMPLE_ENABLE_VNDK=1",
        "-DLIBEXAMPLE_ENABLE_VNDK_EXT=1",
    ],
}
 

這是 src/example.c 的代碼清單:

void all() { }

#if !defined(LIBEXAMPLE_ENABLE_VNDK)
void framework_only() { }
#endif

#if defined(LIBEXAMPLE_ENABLE_VNDK)
void vndk() { }
#endif

#if defined(LIBEXAMPLE_ENABLE_VNDK_EXT)
void vndk_ext() { }
#endif
 

編譯系統會根據這兩個文件生成帶有以下導出符號的共享庫:

安裝路徑 導出的符號
/system/lib[64]/libexample.so allframework_only
/system/lib[64]/vndk-${VER}/libexample.so allvndk
/vendor/lib[64]/vndk/libexample.so allvndkvndk_ext

對導出符號的要求

VNDK ABI 檢查工具會將 VNDK 供應商變體和 VNDK 擴展的 ABI 同 prebuilts/abi-dumps/vndk 下的參考 ABI 轉儲進行比較。

  • 通過 VNDK 供應商變體(例如 /system/lib[64]/vndk-${VER}/libexample.so)導出的符號必須與 ABI 轉儲中定義的符號相同(而不是后者的超集)。
  • 通過 VNDK 擴展(例如 /vendor/lib[64]/vndk/libexample.so)導出的符號必須是 ABI 轉儲中定義的符號的超集。

如果 VNDK 供應商變體或 VNDK 擴展未能遵循上述要求,則 VNDK ABI 檢查工具會發出編譯錯誤並停止編譯。

從供應商變體中排除源文件或共享庫

要從供應商變體中排除源文件,請將這些文件添加到 exclude_srcs 屬性中。同樣,要確保共享庫未與供應商變體相關聯,請將這些庫添加到 exclude_shared_libs 屬性中。例如:

cc_library {
    name: "libexample_cond_exclude",
    srcs: ["fwk.c", "both.c"],
    shared_libs: ["libfwk_only", "libboth"],
    target: {
        vendor: {
            exclude_srcs: ["fwk.c"],
            exclude_shared_libs: ["libfwk_only"],
        },
    },
}
 

在本示例中,libexample_cond_exclude 的核心變體包含 fwk.c 和 both.c 中的代碼,並且依賴於共享庫 libfwk_only 和 libbothlibexample_cond_exclude 的供應商變體僅包含 both.c 中的代碼,因為 fwk.c已被 exclude_srcs 屬性排除。同樣,libexample_cond_exclude 僅依賴於共享庫 libboth,因為 libfwk_only 已被 exclude_shared_libs 屬性排除。

從 VNDK 擴展導出頭文件

VNDK 擴展可以向 VNDK 共享庫添加新類或新函數。建議將這些聲明保留在獨立頭文件中,並避免更改現有頭文件。

例如,為 VNDK 擴展 libexample_ext 創建了一個新的頭文件 include-ext/example/ext/feature_name.h

  • Android.bp
  • include-ext/example/ext/feature_name.h
  • include/example/example.h
  • src/example.c
  • src/ext/feature_name.c

在下面的 Android.bp 中,libexample 只會導出 include,而 libexample_ext 會同時導出 include 和 include-ext。這樣可以確保 libexample 的用戶不會錯誤地添加 feature_name.h

cc_library {
    name: "libexample",
    srcs: ["src/example.c"],
    export_include_dirs: ["include"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libexample_ext",
    srcs: [
        "src/example.c",
        "src/ext/feature_name.c",
    ],
    export_include_dirs: [
        "include",
        "include-ext",
    ],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample",
    },
}
 

如果將擴展分離到獨立的頭文件不可行,替代方案是添加 #ifdef 防護程序。但是,請確保所有 VNDK 擴展用戶都添加了 define 標記。您可以定義 cc_defaults 以向 cflags 添加定義標記並使用 shared_libs 鏈接共享庫。

例如,要將新成員函數 Example2::get_b() 添加到 VNDK 擴展 libexample2_ext 中,您必須修改現有的頭文件並添加 #ifdef 防護程序:

#ifndef LIBEXAMPLE2_EXAMPLE_H_
#define LIBEXAMPLE2_EXAMPLE_H_

class Example2 {
 public:
  Example2();

  void get_a();

#ifdef LIBEXAMPLE2_ENABLE_VNDK_EXT
  void get_b();
#endif

 private:
  void *impl_;
};

#endif  // LIBEXAMPLE2_EXAMPLE_H_
 

為 libexample2_ext 的用戶定義了一個名為 libexample2_ext_defaults 的 cc_defaults

cc_library {
    name: "libexample2",
    srcs: ["src/example2.cpp"],
    export_include_dirs: ["include"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libexample2_ext",
    srcs: ["src/example2.cpp"],
    export_include_dirs: ["include"],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample2",
    },
    cflags: [
        "-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
    ],
}

cc_defaults {
    name: "libexample2_ext_defaults",
    shared_libs: [
        "libexample2_ext",
    ],
    cflags: [
        "-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
    ],
}
 

libexample2_ext 的用戶只需在其 defaults 屬性中添加 libexample2_ext_defaults

cc_binary {
    name: "example2_user_executable",
    defaults: ["libexample2_ext_defaults"],
    vendor: true,
}
 

產品包

在 Android 編譯系統中,變量 PRODUCT_PACKAGES 指定應安裝到設備中的可執行文件、共享庫或軟件包。指定模塊的傳遞依賴項也會隱式安裝到設備中。

如果啟用了 BOARD_VNDK_VERSION,具有 vendor_available 或 vndk.enabled 的模塊會得到特殊處理。如果框架模塊依賴於具有 vendor_available 或 vndk.enabled 的模塊,則核心變體將納入傳遞安裝集中。同樣,如果供應商模塊依賴於具有 vendor_available 或 vndk.enabled 的模塊,則供應商變體將納入傳遞安裝集中。

當相關依賴項對編譯系統不可見時(例如,可以在運行時使用 dlopen() 打開的共享庫),您應該在 PRODUCT_PACKAGES 中指定模塊名稱來明確安裝這些模塊。

如果某個模塊具有 vendor_available 或 vndk.enabled,則模塊名稱代表該模塊的核心變體。要在 PRODUCT_PACKAGES 中明確指定供應商變體,請將 .vendor 后綴附加到模塊名稱上。例如:

cc_library {
    name: "libexample",
    srcs: ["example.c"],
    vendor_available: true,
}
 

在本示例中,libexample 代表 /system/lib[64]/libexample.solibexample.vendor 代表 /vendor/lib[64]/libexample.so。要安裝 /vendor/lib[64]/libexample.so,請將 libexample.vendor添加到 PRODUCT_PACKAGES

PRODUCT_PACKAGES += libexample.vendor


VNDK 擴展

Android 設備制造商會出於各種原因而更改 AOSP 庫的源代碼。一些供應商會為了提高性能而重新實現 AOSP 庫中的函數,另一些供應商則會向 AOSP 庫添加新鈎子、新 API 或新功能。本部分將介紹一些准則,以說明如何在不破壞 CTS/VTS 的前提下擴展 AOSP 庫。

簡易替換

所有修改后的共享庫都必須與其 AOSP 副本保持二進制兼容,且可以簡易替換該副本。所有現有的 AOSP 用戶都必須能在不進行重新編譯的情況下使用修改后的共享庫。此要求有以下幾點含義:

  • 不得移除 AOSP 函數。
  • 不得更改面向用戶提供的結構。
  • 不得強化函數的前提條件。
  • 函數必須提供等效功能。
  • 不得削弱函數的后置條件。

擴展后的模塊分類

按模塊所定義使用的功能對其進行分類。

注意:此處之所以使用“功能”一詞而未使用 API/ABI,是因為可在不更改任何 API/ABI 的情況下添加功能。

根據模塊中定義的功能,可將模塊分為 DA 模塊和 DX 模塊

  • Defining-only-AOSP 模塊(DA 模塊)不會定義 AOSP 副本中未包含的新功能。
    • 示例 1: 一個未經修改且完整無缺的 AOSP 庫即是一個 DA 模塊。
    • 示例 2: 如果供應商使用 SIMD 指令重寫 libcrypto.so 中的函數(不添加新函數),那么修改后的 libcrypto.so 將是一個 DA 模塊。
  • Defining-Extension 模塊(DX 模塊)要么會定義新功能,要么沒有 AOSP 副本。
    • 示例 1: 如果供應商向 libjpeg.so 添加一個 helper 函數以訪問某些內部數據,那么修改后的 libjpeg.so 將是一個 DX 庫,而這個新增函數將是該庫的擴展部分。
    • 示例 2: 如果供應商定義了一個名為 libfoo.so 的非 AOSP 庫,那么 libfoo.so 將是一個 DX 庫。

根據模塊所使用的功能,可將模塊分為 UA 模塊和 UX 模塊

  • Using-only-AOSP(UA 模塊)僅會在其實現過程中使用 AOSP 功能。它們不依賴任何非 AOSP 擴展功能。
    • 示例 1: 一個未經修改且完整無缺的 AOSP 庫即是一個 UA 模塊。
    • 示例 2: 如果修改后的共享庫 libjpeg.so 僅依賴於其他 AOSP API,那么它將是一個 UA 模塊。
  • Using-Extension 模塊(UX 模塊)會在其實現過程中依賴某些非 AOSP 功能。
    • 示例 1: 如果修改后的 libjpeg.so 依賴另一個名為 libjpeg_turbo2.so 的非 AOSP 庫,那么修改后的 libjpeg.so 將是一個 UX 模塊。
    • 示例 2: 如果供應商向其修改后的 libexif.so 添加了一個新函數,並且其修改后的 libjpeg.so 使用 libexif.so 中的這個新增函數,那么修改后的 libjpeg.so 將是一個 UX 模塊。

定義的功能和使用的功能相互獨立:

  使用的功能
Only AOSP (UA) Extended (UX)
定義的功能 Only AOSP (DA) DAUA DAUX
Extended (DX) DXUA DXUX

VNDK 擴展機制

由於同名的 AOSP 庫不包含擴展功能,因此依賴擴展功能的供應商模塊將無法正常工作。如果供應商模塊直接或間接依賴擴展功能,則供應商應將 DAUX、DXUA 和 DXUX 共享庫復制到供應商分區(供應商進程始終都會先在供應商分區中查找共享庫)。但是,由於不得復制 LL-NDK 庫,因此供應商模塊不得依賴由修改后的 LL-NDK 庫定義的擴展功能。

當系統分區被常規系統映像 (GSI) 覆蓋時,如果相應的 AOSP 庫可以提供相同的功能,且供應商模塊可以繼續正常工作,DAUA 共享庫便可保留在系統分區上。

簡易替換非常重要,因為 GSI 中未經修改的 VNDK 庫將會在名稱沖突時與修改后的共享庫關聯。如果以 API/ABI 不兼容的方式修改 AOSP 庫,那么 GSI 中的 AOSP 庫可能會無法進行關聯或會出現未定義的行為。

VNDK 定義工具

VNDK 定義工具可幫助供應商將其源代碼樹遷移到 Android 8.0 環境。該工具會先掃描系統映像及供應商映像中的二進制文件,然后解析依賴項。若有模塊依賴項圖為依據,該工具還可檢測出不符合 VNDK 概念的行為,以及為在分區之間移動模塊提供分析數據/建議。如果指定了常規系統映像 (GSI),VNDK 定義工具便可將您的系統映像與 GSI 進行比較,從而確定擴展后的庫。

本部分將介紹 VNDK 定義工具常用的 3 個命令:

  • vndk:為 Android 8.0 及更高版本中的編譯系統臨時解決方法計算 VNDK_SP_LIBRARIES、VNDK_SP_EXT_LIBRARIES 和 EXTRA_VENDOR_LIBRARIES。
  • check-dep:檢查是否有違規模塊依賴項(從供應商模塊指向不符合條件的框架共享庫)。
  • deps:顯示共享庫與可執行文件之間的依賴關系。

要詳細了解高級命令用法,請參閱 VNDK 定義工具代碼庫中的 README.md 文件。

vndk

vndk 子命令會從系統分區和供應商分區加載共享庫和可執行文件,然后解析模塊依賴項,從而確定必須被復制到 /system/lib[64]/vndk-sp-${VER} 和 /vendor/lib[64] 的庫。vndk 子命令包含以下選項:

選項 說明
--system 指向一個包含將會存放在系統分區中的文件的目錄。
--vendor 指向一個包含將會存放在供應商分區中的文件的目錄。
--aosp-system 指向一個包含將會存放在常規系統映像 (GSI) 中的文件的目錄。
--load-extra-deps 指向一個描述隱式依賴項(例如 dlopen())的文件。

例如,要計算 VNDK 庫集,請運行以下 vndk 子命令:

./vndk_definition_tool.py vndk \
    --system ${ANDROID_PRODUCT_OUT}/system \
    --vendor ${ANDROID_PRODUCT_OUT}/vendor \
    --aosp-system ${ANDROID_PRODUCT_OUT}/../generic_arm64_ab/system\
    --load-extra-deps dlopen.dep
 

請使用簡單的文件格式指定額外的依賴關系。每行表示一項依賴關系,其中冒號前面的文件依賴冒號后面的文件。例如:

/system/lib/libart.so: /system/lib/libart-compiler.so
 

通過此行,VNDK 定義工具可得知 libart.so 依賴 libart-compiler.so

安裝目標位置

VNDK 定義工具會列出以下類別的庫及相應的安裝目錄:

類別 目錄
vndk_sp 必須安裝到 /system/lib[64]/vndk-sp-${VER}
vndk_sp_ext 必須安裝到 /vendor/lib[64]/vndk-sp
extra_vendor_libs 必須安裝到 /vendor/lib[64]

編譯系統模板

在收集了 VNDK 定義工具的輸出信息之后,供應商可以創建一個 Android.mk 並填充 VNDK_SP_LIBRARIESVNDK_SP_EXT_LIBRARIES 和 EXTRA_VENDOR_LIBRARIES,以自動執行相應進程,將庫復制到指定的安裝目標位置。

ifneq ($(filter $(YOUR_DEVICE_NAME),$(TARGET_DEVICE)),)
VNDK_SP_LIBRARIES := ##_VNDK_SP_##
VNDK_SP_EXT_LIBRARIES := ##_VNDK_SP_EXT_##
EXTRA_VENDOR_LIBRARIES := ##_EXTRA_VENDOR_LIBS_##

#-------------------------------------------------------------------------------
# VNDK Modules
#-------------------------------------------------------------------------------
LOCAL_PATH := $(call my-dir)

define define-vndk-lib
include $$(CLEAR_VARS)
LOCAL_MODULE := $1.$2
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_PREBUILT_MODULE_FILE := $$(TARGET_OUT_INTERMEDIATE_LIBRARIES)/$1.so
LOCAL_STRIP_MODULE := false
LOCAL_MULTILIB := first
LOCAL_MODULE_TAGS := optional
LOCAL_INSTALLED_MODULE_STEM := $1.so
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_RELATIVE_PATH := $3
LOCAL_VENDOR_MODULE := $4
include $$(BUILD_PREBUILT)

ifneq ($$(TARGET_2ND_ARCH),)
ifneq ($$(TARGET_TRANSLATE_2ND_ARCH),true)
include $$(CLEAR_VARS)
LOCAL_MODULE := $1.$2
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_PREBUILT_MODULE_FILE := $$($$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_INTERMEDIATE_LIBRARIES)/$1.so
LOCAL_STRIP_MODULE := false
LOCAL_MULTILIB := 32
LOCAL_MODULE_TAGS := optional
LOCAL_INSTALLED_MODULE_STEM := $1.so
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_RELATIVE_PATH := $3
LOCAL_VENDOR_MODULE := $4
include $$(BUILD_PREBUILT)
endif  # TARGET_TRANSLATE_2ND_ARCH is not true
endif  # TARGET_2ND_ARCH is not empty
endef

$(foreach lib,$(VNDK_SP_LIBRARIES),\
    $(eval $(call define-vndk-lib,$(lib),vndk-sp-gen,vndk-sp,)))
$(foreach lib,$(VNDK_SP_EXT_LIBRARIES),\
    $(eval $(call define-vndk-lib,$(lib),vndk-sp-ext-gen,vndk-sp,true)))
$(foreach lib,$(EXTRA_VENDOR_LIBRARIES),\
    $(eval $(call define-vndk-lib,$(lib),vndk-ext-gen,,true)))

#-------------------------------------------------------------------------------
# Phony Package
#-------------------------------------------------------------------------------

include $(CLEAR_VARS)
LOCAL_MODULE := $(YOUR_DEVICE_NAME)-vndk
LOCAL_MODULE_TAGS := optional
LOCAL_REQUIRED_MODULES := \
    $(addsuffix .vndk-sp-gen,$(VNDK_SP_LIBRARIES)) \
    $(addsuffix .vndk-sp-ext-gen,$(VNDK_SP_EXT_LIBRARIES)) \
    $(addsuffix .vndk-ext-gen,$(EXTRA_VENDOR_LIBRARIES))
include $(BUILD_PHONY_PACKAGE)

endif  # ifneq ($(filter $(YOUR_DEVICE_NAME),$(TARGET_DEVICE)),)
 

check-dep

check-dep 子命令會掃描供應商模塊並檢查其依賴關系。如果它檢測到違規行為,就會顯示違規的依賴庫和符號用法:

./vndk_definition_tool.py check-dep \
    --system ${ANDROID_PRODUCT_OUT}/system \
    --vendor ${ANDROID_PRODUCT_OUT}/vendor \
    --tag-file eligible-list.csv \
    --module-info ${ANDROID_PRODUCT_OUT}/module-info.json \
    1> check_dep.txt \
    2> check_dep_err.txt
 

例如,下方的輸出信息示例表明,存在一項從 libRS_internal.so 指向 libmediandk.so 的違規依賴關系:

/system/lib/libRS_internal.so
        MODULE_PATH: frameworks/rs
        /system/lib/libmediandk.so
                AImageReader_acquireNextImage
                AImageReader_delete
                AImageReader_getWindow
                AImageReader_new
                AImageReader_setImageListener
 

check-dep 子命令包含以下選項:

選項 說明
--tag-file 必須引用一個符合條件的庫標記文件(如下文所述),即一個由 Google 提供的描述框架共享庫類別的電子表格。
--module-info 指向由 Android 編譯系統生成的 module-info.json。該選項可幫助 VNDK 定義工具將二進制模塊與源代碼關聯。

符合條件的庫標記文件

Google 會提供一個符合條件的 VNDK 電子表格(例如 eligible-list.csv),該電子表格會標記可由供應商模塊使用的框架共享庫:

標記 說明
LL-NDK 可由框架模塊和供應商模塊使用的共享庫(具有穩定的 ABI/API)。
LL-NDK-Private LL-NDK 庫的私有依賴項。供應商模塊不得直接訪問此類庫。
VNDK-SP SP-HAL 框架共享庫依賴項。
VNDK-SP-Private 所有供應商模塊都無法直接訪問的 VNDK-SP 依賴項。
VNDK 面向供應商模塊(SP-HAL 和 SP-HAL-Dep 除外)提供的框架共享庫。
VNDK-Private 所有供應商模塊都無法直接訪問的 VNDK 依賴項。
FWK-ONLY 供應商模塊不得(直接或間接)訪問、僅限框架使用的共享庫。
FWK-ONLY-RS 供應商模塊不得訪問(RS 用途除外)、僅限框架使用的共享庫。

下表描述了適用於供應商共享庫的標記:

標記 說明
SP-HAL Same-Process HAL 實現共享庫。
SP-HAL-Dep SP-HAL 供應商共享庫依賴項(也稱為 SP-HAL 依賴項,不包括 LL-NDK 和 VNDK-SP)。
VND-ONLY 框架模塊不可見且不得訪問的共享庫。所復制的擴展后 VNDK 庫也將被標記為 VND-ONLY。

標記之間的關系:

圖 1. 標記之間的關系。

deps

為了對庫依賴項進行調試,deps 子命令會顯示以下模塊依賴關系:

./vndk_definition_tool.py deps \
    --system ${ANDROID_PRODUCT_OUT}/system \
    --vendor ${ANDROID_PRODUCT_OUT}/vendor
 

輸出信息由多行內容組成。不含制表符的行會另起一部分。包含制表符的行則依賴前一部分。例如:

/system/lib/ld-android.so
/system/lib/libc.so
        /system/lib/libdl.so
 

此輸出信息表明:ld-android.so 沒有依賴項,而 libc.so 依賴 libdl.so

如果指定了 --revert 選項,deps 子命令就會顯示庫的使用情況(反向依賴項):

./vndk_definition_tool.py deps \
    --revert \
    --system ${ANDROID_PRODUCT_OUT}/system \
    --vendor ${ANDROID_PRODUCT_OUT}/vendor
 

例如:

/system/lib/ld-android.so
        /system/lib/libdl.so
       
 

此輸出信息表明:libdl.so 使用了 ld-android.so,即 libdl.so 依賴 ld-android.so。另外,此輸出信息還表明 libdl.so 是 ld-android.so 的唯一使用者。

如果指定了 --symbol 選項,deps 子命令便會顯示用到的符號:

./vndk_definition_tool.py deps \
    --symbol \
    --system ${ANDROID_PRODUCT_OUT}/system \
    --vendor ${ANDROID_PRODUCT_OUT}/vendor
   
 

例如:

/system/lib/libc.so
        /system/lib/libdl.so
                android_get_application_target_sdk_version
                dl_unwind_find_exidx
                dlclose
                dlerror
                dlopen
                dlsym
 

此輸出信息表明 libc.so 依賴從 libdl.so 導出的 6 個函數。如果同時指定了 --symbol 選項和 --revert 選項,該子命令則會顯示使用者所用的符號。

VNDK 快照設計

系統映像可以使用 VNDK 快照為供應商映像提供正確的 VNDK 庫,即使系統映像和供應商映像是基於不同版本的 Android 編譯的也是如此。創建 VNDK 快照需要以快照形式捕獲 VNDK 庫,並使用版本號標記它們。供應商映像可以與特定的 VNDK 版本相關聯,由此版本為供應商映像中的模塊提供所需 ABI。不過,在同一 VNDK 版本內,VNDK 庫必須具有穩定的 ABI

VNDK 快照設計包括用於執行以下兩項操作的方法:從當前系統映像生成預編譯的 VNDK 快照將這些預編譯的庫安裝到更高 Android 版本的系統分區。

VNDK 庫簡介

Android 8.0 中引入的 HIDL-HAL 支持單獨升級系統分區和供應商分區。VNDK 定義了可與供應商代碼相關聯的庫集(VNDK-core、VNDK-SP 和 LL-NDK),並阻止供應商使用不在 VNDK 集內的庫。因此,如果將系統映像上合適的 VNDK 集提供給供應商映像,則可以編譯並運行供應商映像。

注意:有關這些庫的詳細信息,請參閱  VNDK 概念

VNDK-core

VNDK-core 庫集安裝在 /system/lib[64]/vndk-${VER} 中,適用於 API 級別為 ${VER} 的供應商進程。系統進程不得使用這些庫,而必須使用安裝在 /system/lib[64] 中的庫。由於每個進程都具有嚴格的命名空間限制,因此不會造成重復加載 VNDK-core 庫。

要在 VNDK-core 中添加庫,請將以下內容添加到 Android.bp 中:

vendor_available: true,
vndk: {
    enabled: true,
},
 
注意:如果系統進程從  system/lib 加載庫  foo.so,並從  system/lib/vndk 加載另一個  foo.so,這種情況就屬於重復加載  foo.so。通常,在一個進程中兩次加載同一個庫是不安全的。

VNDK-SP

VNDK-SP 庫安裝在 /system/lib[64]/vndk-sp-${VER} 中,適用於供應商進程和系統進程(通過安裝在供應商分區中的 SP-HAL 庫)。VNDK-SP 庫可以重復加載。

要在 VNDK-SP 中添加庫,請將以下內容添加到 Android.bp 中:

vendor_available: true,
vndk: {
    enabled: true,
    support_system_process: true,
},
 

LL-NDK

LL-NDK 庫安裝在 /system/lib[64] 中。供應商模塊可以使用 LL-NDK 存根庫訪問 LL-NDK 庫的預選符號。LL-NDK 庫必須支持向后兼容且具有 ABI 穩定性,以便舊版供應商模塊使用新版 LL-NDK 庫。由於 LL-NDK 具有 ABI 穩定特性,VNDK 快照無需包含舊版供應商映像的 LL-NDK 庫。

VNDK 快照簡介

Android 8.1 包含根據源代碼編譯的 VNDK 庫。不過,對於更高版本的 Android,必須以快照形式捕獲每個 VNDK 版本,並作為預編譯版本提供,以便關聯到較舊版本的供應商映像。

從 Android 9 開始,新版 Android 將在 Android 源代碼中包含較低版本 Android 的 VNDK-core 和 VNDK-SP 目錄的至少一個快照。編譯時,所需快照將安裝到 /system/lib[64]/vndk-${VER} 和 /system/lib[64]/vndk-sp-${VER}(供應商分區可以使用的目錄),其中 ${VER} 是表示 VNDK 快照版本名稱的字符串變量。

由於每個 VNDK 版本的 VNDK 快照庫可能各不相同,因此 VNDK 快照還包含按以下格式安裝的鏈接器命名空間配置:etc/ld.config.${VER}.txt/etc/llndk.libraries.${VER}.txt 和 /etc/vndksp.libraries.${VER}.txt

示例:升級系統映像和供應商映像

無需快照;無需針對 VNDK 快照進行其他配置即可編譯。

示例:僅升級系統映像

必須在系統映像中包含供應商映像的 VNDK 快照和鏈接器命名空間配置文件。系統會自動配置鏈接器命名空間配置文件,以在 /system/lib[64]/vndk-${VER} 和 /system/lib[64]/vndk-sp-${VER} 中搜索 VNDK 庫。

圖 1. 僅升級系統映像

示例:升級系統映像,少量更改供應商映像

根據 VNDK 快照編譯供應商映像尚不受支持,因此您必須使用原始源代碼單獨編譯供應商映像,然后按上一示例中所述升級系統映像。

VNDK 快照架構

要使 Android 9 系統映像與 Android 8.1 供應商映像兼容,必須為 Android 9 系統映像提供與 Android 8.1 供應商映像匹配的 VNDK 快照,如下所示:

圖 2. VNDK 快照架構

VNDK 快照設計包括以下方法:

  • 為 VNDK-core 和 VNDK-SP 庫生成快照。Android 9 包含一個腳本,您可以使用它來制作當前 VNDK 版本的快照。此腳本將 /system/lib[64]/vndk-28 和 /system/lib[64]/vndk-sp-28 中的所有庫組合在一起,這些庫是采用當前源代碼以 VNDK 快照形式編譯的,其中 28 是 Android 9 的 VNDK 版本。快照還包含鏈接器命名空間配置文件 /etc/ld.config.28.txt/etc/llndk.libraries.28.txt 和 /etc/vndksp.libraries.28.txt。生成的快照將用於較新的 Android 版本(高於 Android 9 的版本)。
  • 從快照安裝預編譯的 VNDK-core 和 VNDK-SP 庫。在 Android 9 中,VNDK 快照具有一組預編譯的 VNDK-core 庫和一組 VNDK-SP 庫,以及鏈接器命名空間配置文件。如果您提供了要安裝的 VNDK 快照版本列表,系統映像會在編譯時將 VNDK 快照庫安裝到 /system/lib[64]/vndk-${VER} 和 /system/lib[64]/vndk-sp-${VER} 目錄,並將這些 VNDK 快照的鏈接器命名空間配置文件安裝到 /etc 目錄。

VNDK 版本控制

每個 Android 版本都只有一個 VNDK 快照,並且 SDK 版本用作 VNDK 版本(這意味着 VNDK 版本必須采用整數,例如 Android 8.1 的 VNDK 版本為 27)。Android 版本發布時,VNDK 版本已確定。供應商分區使用的 VNDK 版本自動存儲在 ro.vndk.version 屬性中,可在運行時讀取。然后,此版本用於識別一些庫的供應商 VNDK 版本及命名空間配置的 VNDK 快照版本。

編譯 VNDK 庫

make vndk 命令可用於編譯具有 vndk: { enabled: true, … } 的庫,包括依賴項和命名空間配置文件。如果設置了 BOARD_VNDK_VERSION := current,則可使用 make 命令編譯這些庫。

 

由於這種編譯方法不會從快照安裝 VNDK 庫,安裝的 VNDK 庫不具有 ABI 穩定性。不過,Android 版本發布時,當前 VNDK 版本的 ABI 已確定。此時,任何 ABI 損壞都屬於編譯錯誤,因此 Android 版本的補丁程序不得更改 VNDK 庫的 ABI。

 

生成 VNDK 快照

VNDK 快照是一組適用於 Android 版本的 VNDK-core 和 VNDK-SP 庫。如果 system.img 包含 vendor.img 所需的相應 VNDK 快照,那么,您只能升級系統分區。

注意:本頁提供了關於編譯和更新 VNDK 快照的設計細節。要詳細了解 VNDK 快照的背景、定義和用例,請參閱  VNDK 快照設計

正式的 VNDK 快照是在 Android 編譯服務器上自動編譯而成,並簽入 Android 源代碼樹的 /prebuilts/vndk 中。為了便於開發,您可以在本地編譯 VNDK 快照。arm、arm64、x86 和 x86_64 TARGET_ARCH 架構支持 VNDK 快照。

快照編譯工件

Android 編譯服務器使用以下編譯參數和編譯命令,生成適用於 VNDK 快照的編譯工件。

編譯參數

編譯目標名稱為 vndk,編譯目標配置如下所示:

  • TARGET_PRODUCT=aosp_{TARGET_ARCH}_ab
  • TARGET_BUILD_VARIANT=user
  • TARGET_ARCH。與常規系統映像 (GSI) 目標架構(arm、arm64、x86、x86_64)相同。
  • TARGET_ARCH_VARIANT。對於快照 v27 (Android 8.1),變體包含熱門配置(如下所示);未來版本可能會包含其他 arch/cpu 變體。
TARGET_PRODUCT TARGET_ARCH TARGET_ARCH_VARIANT
aosp_arm_ab arm armv7-a-neon
aosp_arm64_ab arm64 armv8-a
aosp_x86_ab x86 x86
aosp_x86_64_ab x86_64 x86_64

編譯命令

對於正式快照,Android 9 在 vndk.mk 中引入了新的虛擬目標 (vndk),該目標會編譯 VNDK 快照並將其輸出到 $DIST_DIR 中。快照的 ZIP 文件采用 android-vndk-{TARGET_ARCH}.zip 格式。例如:

$ lunch aosp_<ARCH>_ab-user
$ make -j vndk dist [BOARD_VNDK_VERSION=current]
 

Android 編譯服務器通過以下命令使用 build.sh 腳本來編譯所有受支持的架構類型:

$ DIST_DIR=%dist_dir% development/vndk/snapshot/build.sh
 

Android 版本的 VNDK 快照由 <Android Version>-release 分支生成。

在本地編譯

在開發過程中,您可以通過以下命令,從本地源代碼樹中編譯 VNDK 快照:

  • 要一次編譯所有受支持的架構,請通過以下命令執行編譯腳本 (build.sh):
    $ cd $ANDROID_BUILD_TOP
    $ development/vndk/snapshot/build.sh
     
  • 要編譯某個特定的 TARGET_ARCH,請執行以下命令:
    $ lunch aosp_<ARCH>_ab-user
    $ m -j vndk dist
     

相應的 android-vndk-<ARCH>.zip 文件會在 $DIST_DIR 下創建。

快照文件

VNDK 快照包含以下文件:

  • VNDK-core 和 VNDK-SP 共享庫的供應商變體。
    • 無需 LL-NDK 共享庫,因為這類庫是向后兼容的。
    • 對於 64 位目標,TARGET_ARCH 和 TARGET_2ND_ARCH 庫都將被編譯並包含在內。
  • VNDK-core、VNDK-SP、LL-NDK 和 VNDK-private 庫的列表位於 [vndkcore|vndksp|llndk|vndkprivate].libraries.txt
  • 鏈接器配置文件為 ld.config.txt
  • 許可文件。
  • module_paths.txt。記錄所有 VNDK 庫的模塊路徑;檢查 GPL 項目是否已在指定 Android 源代碼樹中發布源代碼時,需要用到這種文件。

對於指定 VNDK 快照 ZIP 文件 android-vndk-{TARGET_ARCH}.zip,系統會根據 ABI 位數將 VNDK 預編譯庫分組到名為 arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT} 的單獨目錄中。例如,對於 android-vndk-arm64.zip,64 位庫會位於 arch-arm64-armv8-a 下,而 32 位庫則位於 arch-arm-armv8-a 下。

示例:VNDK 快照目錄結構

以下示例展示了 arm64 (TARGET_ARCH=arm64) VNDK 快照 ZIP 文件 (android-vndk-arm64.zip) 的目錄結構。

圖 1. VNDK 快照目錄結構(示例)

上傳 VNDK 快照

VNDK 快照將簽入 /prebuilts/vndk/v<VER> 下的源代碼樹,其中 <VER> 為 VNDK 快照的版本(遵循相應 Android 版本的 SDK 版本)。例如,O MR1 VNDK 快照的版本為 27。

使用 update.py 腳本

update.py 腳本 (/development/vndk/snapshot/update.py) 可自動將預編譯的 VNDK 快照添加到源代碼樹中。此腳本將執行以下任務:

  1. 在 /prebuilts/vndk/v<VER> 中,使用 repo start 創建新的 git 分支。
  2. 獲取 VNDK 快照編譯軟件工件並將其解壓縮。
  3. 運行 gen_buildfiles.py 以自動生成編譯文件(Android.mkAndroid.bp)。
  4. 運行 check_gpl_license.py 以驗證根據通用公共許可證 (GPL) 獲得許可的預編譯庫是否在當前源代碼樹中發布了源代碼。
  5. 使用 git commit 提交新的更改。

使用本地編譯的 VNDK 快照

在開發過程中,您可以使用本地編譯的 VNDK 快照進行測試。在指定 --local 選項的情況下,update.py 會從本地 $DIST_DIR(而非 Android 編譯服務器中)提取 VNDK 快照編譯工件。用法如下:

$ python update.py <VER> --local
 

例如,要使用本地編譯軟件工件更新 O MR1 VNDK 快照,請運行以下命令:

$ python update.py 27 --local
 

由於本地模式僅用於測試,因此該腳本將跳過 GPL 許可檢查和 git commit 步驟。

prebuilts/vndk 的目錄結構

圖 2. Prebuilts/vndk 目錄結構

安裝 VNDK 快照

系統映像在編譯時使用 BOARD_VNDK_VERSIONPRODUCT_EXTRA_VNDK_VERSIONS 和 ro.vndk.version 中的信息安裝 VNDK 快照庫。您可以使用以下選項之一控制從 /prebuilts/vndk/v<VER> 中安裝哪些 VNDK 快照:

  • 選項 1BOARD_VNDK_VERSION。使用快照模塊編譯當前供應商模塊,並僅安裝供應商模塊所需的快照模塊。
  • 選項 2PRODUCT_EXTRA_VNDK_VERSIONS。無論當前供應商模塊有哪些,都安裝 VNDK 快照模塊。這將安裝 PRODUCT_EXTRA_VNDK_VERSIONS 中列出的預編譯 VNDK 快照,而不會在編譯時將其與任何其他模塊相關聯。

設置 BOARD_VNDK_VERSION

BOARD_VNDK_VERSION 顯示的是當前供應商模塊需要編譯的 VNDK 版本。如果 BOARD_VNDK_VERSION 在 /prebuilts/vndk 目錄中有可用的 VNDK 快照版本,則系統會安裝 BOARD_VNDK_VERSION 中指明的 VNDK 快照。如果目錄中的 VNDK 快照不可用,則會出現編譯錯誤。

定義 BOARD_VNDK_VERSION 也會啟用要安裝的 VNDK 模塊。供應商模塊會在編譯時與 BOARD_VNDK_VERSION 中定義的 VNDK 快照版本相關聯(此操作不會在系統源代碼中編譯當前的 VNDK 模塊)。從代碼庫中下載完整的源代碼樹時,系統源代碼和供應商源代碼均基於相同的 Android 版本。

注意:供應商模塊使用的是系統源代碼樹的當前 VNDK 版本,因此,您必須將  BOARD_VNDK_VERSION 設置為  current

設置 PRODUCT_EXTRA_VNDK_VERSIONS

PRODUCT_EXTRA_VNDK_VERSIONS 列出了要安裝的其他 VNDK 版本。正常情況下,當前的供應商分區只需一個 VNDK 快照就足夠了。不過,在某些情況下,您可能需要在一個系統映像中提供多個快照。例如,常規系統映像 (GSI) 具有多個快照,以通過一個系統映像支持多個供應商版本。設置 PRODUCT_EXTRA_VNDK_VERSIONS 后,除了 BOARD_VNDK_VERSION 中的 VNDK 版本之外,您還可以安裝 VNDK 快照模塊。

如果 PRODUCT_EXTRA_VNDK_VERSIONS 具有特定的版本列表,則編譯系統會在 prebuilts/vndk 目錄中查找版本列表的預編譯快照。如果編譯系統找到所有列出的快照,便會將這些快照文件安裝到每個 out/target/product/<board>/system/lib[64]/vndk[-sp]-${VER} 中。缺少某些版本會導致出現編譯錯誤。

VNDK 模塊將不會在編譯時與供應商模塊相關聯,但在運行時可能會使用該模塊(如果供應商分區中的供應商模塊需要某個已安裝的 VNDK 版本)。PRODUCT_EXTRA_VNDK_VERSIONS 僅在指定了 BOARD_VNDK_VERSION 的情況下才有效。例如,要將 O MR1 VNDK 快照安裝到 system.img 中,請運行以下命令:

$ m -j PRODUCT_EXTRA_VNDK_VERSIONS=27
 

PLATFORM_VNDK_VERSION

PLATFORM_VNDK_VERSION 在系統源代碼中指定了當前 VNDK 模塊的 VNDK 版本。系統會通過以下方式自動設置該值:

  • 在版本發布之前,將 PLATFORM_VNDK_VERSION 設置為 PLATFORM_VERSION_CODENAME
  • 在發布時,將 PLATFORM_SDK_VERSION 復制到 PLATFORM_VNDK_VERSION 中。

發布 Android 版本后,當前的 VNDK 庫會被安裝到 /system/lib[64]/vndk-$SDK_VER 和 /system/lib[64]/vndk-sp-$SDK_VER,其中 $SDK_VER 是存儲在 PLATFORM_VNDK_VERSION 中的版本。

命名空間配置

供應商模塊使用 /etc/ld.config.${VER}.txt(其中 ${VER} 是從 ro.vndk.version 屬性中獲得的)中的命名空間配置來搜索所需的共享庫。命名空間配置中包含帶有版本編號的 VNDK 目錄,該目錄使用以下語法:

  • /system/${LIB}/vndk-%VNDK_VER%
  • /system/${LIB}/vndk-sp-%VNDK_VER%

%VNDK_VER% 在編譯時會被替換為 PLATFORM_VNDK_VERSION,這樣一來,系統映像便能夠為每個 VNDK 版本提供多個快照。

如果將 BOARD_VNDK_VERSION 設置為 current,則 PLATFORM_VNDK_VERSION 將存儲在 ro.vndk.version中;否則,BOARD_VNDK_VERSION 將存儲在 ro.vndk.version 中。PLATFORM_VNDK_VERSION 在 Android 版本發布時會被設置為 SDK 版本;在發布之前,由字母和數字組成的 Android 代碼名稱會用於 PLATFORM_VNDK_VERSION

VNDK 版本設置摘要

下表總結了 VNDK 版本設置。

供應商
版本
開發板
版本
SDK
版本
平台
版本
版本
屬性
安裝目錄
當前的 VNDK 模塊 current 之前 <CODE_NAME> <CODE_NAME> /system/lib[64]/vndk[-sp]-<CODE_NAME>
之后 <SDK_ver> <SDK_ver> /system/lib[64]/vndk[-sp]-<SDK_ver>
預編譯的快照模塊 <VNDK_ver>
(用於快照)
之前或之后 <CODE_NAME>
或 <SDK_ver>
<VNDK_ver> /system/lib[64]/vndk[-sp]-<VNDK_ver>
  • 開發板版本 (BOARD_VNDK_VERSION):供應商模塊需要編譯的 VNDK 版本。如果供應商模塊可與當前系統模塊相關聯,則將其設置為 current
  • 平台版本 (PLATFORM_VNDK_VERSION):當前系統模塊正在編譯的 VNDK 版本(僅在 BOARD_VNDK_VERSION為當前版本時編譯)。
  • 版本屬性 (ro.vndk.version):一種屬性,用於指定 vendor.img 中的二進制文件和庫需要運行的 VNDK 版本。該屬性存儲在 /vendor/default.prop 下的 vendor.img 中。

鏈接器命名空間

動態鏈接器解決了 Treble VNDK 設計中的兩個難題:

  • 將 SP-HAL 共享庫及其依賴項(包括 VNDK-SP 庫)加載到框架進程中。這種情況下應該有一些防止出現符號沖突的機制。
  • dlopen() 和 android_dlopen_ext() 可能會引入一些在編譯時不可見的運行時依賴項,這些依賴項使用靜態分析很難檢測到。

這兩個難題可以通過鏈接器命名空間機制來解決。鏈接器命名空間機制由動態鏈接器提供,可以隔離不同鏈接器命名空間中的共享庫,以確保具有相同庫名稱和不同符號的庫不會發生沖突。

另一方面,鏈接器命名空間機制可提供相應的靈活性,從而將由一個鏈接器命名空間導出的某些共享庫用於另一個鏈接器命名空間。這些導出的共享庫可能會成為對其他程序公開的應用編程接口,同時在其鏈接器命名空間中隱藏實現細節。

例如,/system/lib[64]/libcutils.so 和 /system/lib[64]/vndk-sp-${VER}/libcutils.so 是兩個共享庫。這兩個庫可能有不同的符號。它們會加載到不同的鏈接器命名空間中,以便框架模塊可以依賴於 /system/lib[64]/libcutils.so,而 SP-HAL 共享庫則可以依賴於 /system/lib[64]/vndk-sp-${VER}/libcutils.so

另一方面,/system/lib[64]/libc.so 是由一個鏈接器命名空間導出而后又被導入到許多鏈接器命名空間中的公共庫。/system/lib[64]/libc.so 的依賴項(例如 libnetd_client.so)將被加載到 /system/lib[64]/libc.so 所在的命名空間中。其他命名空間將無法訪問這些依賴項。這種機制會在提供公共接口的同時封裝實現細節。

工作原理

動態鏈接器負責加載 DT_NEEDED 條目中指定的共享庫,或由 dlopen() 或 android_dlopen_ext() 的參數指定的共享庫。在這兩種情況下,動態鏈接器都會找出調用程序所在的鏈接器命名空間,並嘗試將相關依賴項加載到同一個鏈接器命名空間中。如果動態鏈接器無法將共享庫加載到指定的鏈接器命名空間中,它會向關聯的鏈接器命名空間索取導出的共享庫。

配置文件格式

配置文件格式取決於 INI 文件格式。典型的配置文件如下所示:

dir.system = /system/bin
dir.system = /system/xbin
dir.vendor = /vendor/bin

[system]
additional.namespaces = sphal,vndk

namespace.default.isolated = true
namespace.default.search.paths = /system/${LIB}
namespace.default.permitted.paths = /system/${LIB}/hw
namespace.default.asan.search.paths = /data/asan/system/${LIB}:/system/${LIB}
namespace.default.asan.permitted.paths = /data/asan/system/${LIB}/hw:/system/${LIB}/hw

namespace.sphal.isolated = true
namespace.sphal.visible = true
namespace.sphal.search.paths = /odm/${LIB}:/vendor/${LIB}
namespace.sphal.permitted.paths = /odm/${LIB}:/vendor/${LIB}
namespace.sphal.asan.search.paths  = /data/asan/odm/${LIB}:/odm/${LIB}
namespace.sphal.asan.search.paths += /data/asan/vendor/${LIB}:/vendor/${LIB}
namespace.sphal.asan.permitted.paths  = /data/asan/odm/${LIB}:/odm/${LIB}
namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB}:/vendor/${LIB}
namespace.sphal.links = default,vndk
namespace.sphal.link.default.shared_libs = libc.so:libm.so
namespace.sphal.link.vndk.shared_libs = libbase.so:libcutils.so

namespace.vndk.isolated = true
namespace.vndk.search.paths = /system/${LIB}/vndk-sp-29
namespace.vndk.permitted.paths = /system/${LIB}/vndk-sp-29
namespace.vndk.links = default
namespace.vndk.link.default.shared_libs = libc.so:libm.so

[vendor]
namespace.default.isolated = false
namespace.default.search.paths = /vendor/${LIB}:/system/${LIB}
 

配置文件包含以下內容:

  • 多個“目錄-區段”映射屬性(位於動態鏈接器的開頭),用於選擇有效的區段。
  • 多個鏈接器命名空間配置區段:
    • 每個區段都包含多個命名空間(圓形頂點)以及各命名空間之間的多個回退鏈接(圖形弧)。
    • 每個命名空間都具有自己的隔離、搜索路徑、允許的路徑以及可見性設置。

下表詳細介紹了各屬性的含義。

“目錄-區段”映射屬性

屬性 說明 示例

dir.name

指向 [name] 區段所應用到的目錄的路徑。

每個屬性都會將目錄下的可執行文件映射到鏈接器命名空間配置區段。可能會有 2 個(或 2 個以上)屬性具有相同的 name,卻指向不同的目錄。

dir.system = /system/bin
dir.system = /system/xbin
dir.vendor = /vendor/bin

這表示在 [system] 區段中指定的配置適用於從 /system/bin 或 /system/xbin 加載的可執行文件。

在 [vendor] 區段中指定的配置適用於從 /vendor/bin 加載的可執行文件。

關系屬性

屬性 說明 示例
additional.namespaces

相應區段的其他命名空間的逗號分隔列表(default 命名空間除外)。

additional.namespaces = sphal,vndk

這表示 [system] 配置中有 3 個命名空間(defaultsphal 和 vndk)。

namespace.name.links

回退命名空間的逗號分隔列表。

如果在當前命名空間中找不到共享庫,則動態鏈接器會嘗試從回退命名空間加載共享庫。在列表開頭指定的命名空間優先級較高。

注意:回退命名空間沒有傳遞特性。例如,命名空間 A 鏈接到命名空間 B,命名空間 B 鏈接到命名空間 C。如果動態鏈接器在命名空間 A 中找不到相應的庫,則它僅會搜索命名空間 B,而不會搜索命名空間 C。

namespace.sphal.links = default,vndk

如果某個共享庫或可執行文件請求另一個共享庫,而后者無法加載到 sphal 命名空間,則動態鏈接器會嘗試從 default 命名空間加載此共享庫。

然后,如果此共享庫也無法從 default 命名空間加載,則動態鏈接器會嘗試從 vndk 命名空間加載此共享庫。

最后,如果所有嘗試都失敗,則動態鏈接器會返回一個錯誤。

namespace.name.link.other.shared_libs

用冒號分隔的共享庫列表(如果在 other 命名空間中找不到這些共享庫,則可以在 name 命名空間中搜索)。

此屬性無法與 namespace.name.link.other.allow_all_shared_libs 一起使用。

注意:此屬性與 public.libraries.txt 文件在底層實現上是相同的。這兩種機制都通過使用庫名稱過濾器指定鏈接的方式來控制導入的共享庫。

不同之處在於,ld.config.txt 由動態鏈接器進行加載,並且所有命名空間都是在程序啟動時創建的。相反,libnativeloader 會在 Zygote 進程針對某個應用進行分岔和專門化操作時創建鏈接器命名空間。應用原生庫的命名空間具有一個僅允許使用在 public.libraries.txt 中指定的庫名稱的鏈接。

namespace.sphal.link.default.shared_libs = libc.so:libm.so

這表示回退鏈接僅接受 libc.so或 libm.so 作為請求的庫名稱。如果請求的庫名稱不是 libc.so,也不是 libm.so,則動態鏈接器會忽略從 sphal 到 default 命名空間的回退鏈接。

namespace.name.link.other.allow_all_shared_libs

一個布爾值,用於指示是否能在 other 命名空間中搜索到所有共享庫(當無法在 name 命名空間中找到這些共享庫時)。

此屬性無法與 namespace.name.link.other.shared_libs 一起使用。

namespace.vndk.link.sphal.allow_all_shared_libs = true

這表示所有庫名稱都可以遍歷從 vndk 到 sphal 命名空間的回退鏈接。

命名空間屬性

屬性 說明 示例
namespace.name.isolated

一個布爾值,用於指示動態鏈接器是否應該檢查共享庫在什么位置。

如果 isolated 為 true,則系統只能加載某個 search.paths 目錄(不包含子目錄)中的共享庫或某個 permitted.paths 目錄(包含子目錄)下的共享庫。

如果 isolated 為 false,則動態鏈接器不會檢查共享庫的路徑。

namespace.sphal.isolated = true

這表示只有 search.paths 中或 permitted.paths 下的共享庫才能加載到 sphal命名空間。

namespace.name.search.paths

以冒號分隔的目錄列表,用於搜索共享庫。

如果函數調用 dlopen() 或 DT_NEEDED 條目時未指定完整路徑,則在 search.paths 中指定的目錄將附加到請求的庫名稱前面。在列表開頭指定的目錄優先級較高。

如果 isolated 為 true,則系統可加載某個 search.paths 目錄(不包含子目錄)中的共享庫(無論 permitted.paths 屬性為何)。

例如,如果 search.paths 為 /system/${LIB},並且 permitted.paths 為空,則 /system/${LIB}/libc.so 可以加載,但 /system/${LIB}/vndk/libutils.so 無法加載。

注意:${LIB} 是內置占位符。對於 32 位進程,此占位符將替換為 lib;對於 64 位進程,此占位符將替換為 lib64

namespace.default.search.paths = /system/${LIB}

這表示動態鏈接器會在 /system/${LIB} 中搜索共享庫。

namespace.name.asan.search.paths

以冒號分隔的目錄列表,用於在啟用 Address Sanitizer后搜索共享庫。

在啟用 Address Sanitizer 后,系統會忽略 namespace.name.search.paths

namespace.default.asan.search.paths = /data/asan/system/${LIB}:/system/${LIB}

這表示在啟用 Address Sanitizer 后,動態鏈接器會先搜索 /data/asan/system/${LIB},然后再搜索 /system/${LIB}

namespace.name.permitted.paths

以冒號分隔的目錄列表(包含子目錄),當 isolated為 true 時,動態鏈接器可在其中加載共享庫(除了 search.paths 以外)。

permitted.paths 的子目錄下的共享庫也可以加載。例如,如果 permitted.paths 為 /system/${LIB},則 /system/${LIB}/libc.so和 /system/${LIB}/vndk/libutils.so 均可加載。

如果 isolated 為 false,則系統會忽略 permitted.paths 並發出相應警告。

注意:在動態鏈接器搜索共享庫時,permitted.paths 不會附加到請求的庫名稱前面。其主要目的是讓程序員能夠通過指定共享庫的完整路徑將共享庫加載到隔離的命名空間。

namespace.default.permitted.paths = /system/${LIB}/hw

這表示 /system/${LIB}/hw 下的共享庫可以加載到隔離的 default 命名空間。

例如,如果沒有 permitted.paths,則 libaudiohal.so 無法將 /system/${LIB}/hw/audio.a2dp.default.so加載到 default 命名空間。

namespace.name.asan.permitted.paths

以冒號分隔的目錄列表,在啟用 Address Sanitizer 后,動態鏈接器可在其中加載共享庫。

在啟用 Address Sanitizer 后,系統會忽略 namespace.name.permitted.paths

namespace.default.asan.permitted.paths = /data/asan/system/${LIB}/hw:/system/${LIB}/hw

這表示在啟用 Address Sanitizer后,/data/asan/system/${LIB}/hw 或 /system/${LIB}/hw 下的共享庫可以加載到隔離的 default 命名空間。

namespace.name.visible

一個布爾值,用於指示程序(不包括 libc)是否可以包含帶有 android_get_exported_namespace()的鏈接器命名空間句柄,以及通過將此句柄傳遞到 android_dlopen_ext() 打開鏈接器命名空間中的共享庫。

如果 visible 為 true,則 android_get_exported_namespace() 在命名空間存在時始終返回此句柄。

如果 visible 為 false(默認值),則無論命名空間是否存在,android_get_exported_namespace()始終返回 NULL。僅當符合以下條件時,共享庫才能加載到此命名空間:(1) 具有指向此命名空間的回退鏈接的其他鏈接器命名空間請求這些共享庫,或者 (2) 此命名空間中的其他共享庫或可執行文件請求這些共享庫。

namespace.sphal.visible = true

這表示 android_get_exported_namespace("sphal")可以返回有效的鏈接器命名空間句柄。

鏈接器命名空間隔離

${android-src}/system/core/rootdir/etc 中有 3 個配置文件。系統會根據 BoardConfig.mk 中 PRODUCT_TREBLE_LINKER_NAMESPACESBOARD_VNDK_VERSION 和 BOARD_VNDK_RUNTIME_DISABLE 的值選擇不同的配置:

PRODUCT_TREBLE_
LINKER_NAMESPACES
BOARD_VNDK_
VERSION
BOARD_VNDK_
RUNTIME_DISABLE
選擇的配置 VTS 要求
true current empty ld.config.txt 搭載 Android P 的設備的必要配置。
true ld.config.vndk_lite.txt 搭載 Android 8.x 的設備的必要配置。
empty any
false any any ld.config.legacy.txt 適用於不支持 Treble 的設備

${android-src}/system/core/rootdir/etc/ld.config.vndk_lite.txt 會隔離 SP-HAL 和 VNDK-SP 共享庫。在 Android 8.0 及更高版本中,當 PRODUCT_TREBLE_LINKER_NAMESPACES 為 true 時,該配置必須是動態鏈接器的配置文件。

${android-src}/system/core/rootdir/etc/ld.config.txt 也會隔離 SP-HAL 和 VNDK-SP 共享庫。此外,ld.config.txt 還會提供全面的動態鏈接器隔離。它可確保系統分區中的模塊不依賴於供應商分區中的共享庫,反之亦然。

在 Android 8.1 中,ld.config.txt 是默認配置文件,強烈建議您啟用全面的動態鏈接器隔離。但是,如果在 Android 8.1 中需要清理的依賴項太多,您可以將 BOARD_VNDK_RUNTIME_DISABLE 添加到 BoardConfig.mk 中:

BOARD_VNDK_RUNTIME_DISABLE := true
 

如果 BOARD_VNDK_RUNTIME_DISABLE 為 true,則會安裝 ${android-src}/system/core/rootdir/etc/ld.config.vndk_lite.txt

ld.config.txt

ld.config.txt 會隔離系統分區和供應商分區之間的共享庫依賴項。下文概述了該配置文件與上一小節中提到的 ld.config.txt 相比有哪些不同:

  • 框架進程

    • 創建了四個命名空間(defaultvndksphal 和 rs)。
    • 系統會隔離所有命名空間。
    • 將系統共享庫加載到 default 命名空間中。
    • 將 SP-HAL 加載到 sphal 命名空間中。
    • 將 VNDK-SP 共享庫加載到 vndk 命名空間中。
  • 供應商進程

    • 創建了三個命名空間(defaultvndk 和 system)。
    • 系統會隔離 default 命名空間。
    • 將供應商共享庫加載到 default 命名空間中。
    • 將 VNDK 和 VNDK-SP 共享庫加載到 vndk 命名空間中。
    • 將 LL-NDK 及其依賴項加載到 system 命名空間中。

鏈接器命名空間之間的關系如下圖所示:

ld.config.txt 中描繪的鏈接器命名空間圖表 圖 1. 鏈接器命名空間隔離 ( ld.config.txt)

在上圖中,LL-NDK 和 VNDK-SP 代表以下共享庫:

  • LL-NDK
    • libEGL.so
    • libGLESv1_CM.so
    • libGLESv2.so
    • libGLESv3.so
    • libandroid_net.so
    • libc.so
    • libdl.so
    • liblog.so
    • libm.so
    • libnativewindow.so
    • libneuralnetworks.so
    • libsync.so
    • libvndksupport.so
    • libvulkan.so
  • VNDK-SP
    • android.hardware.graphics.common@1.0.so
    • android.hardware.graphics.mapper@2.0.so
    • android.hardware.renderscript@1.0.so
    • android.hidl.memory@1.0.so
    • libRSCpuRef.so
    • libRSDriver.so
    • libRS_internal.so
    • libbase.so
    • libbcinfo.so
    • libc++.so
    • libcutils.so
    • libhardware.so
    • libhidlbase.so
    • libhidlmemory.so
    • libhidltransport.so
    • libhwbinder.so
    • libion.so
    • libutils.so
    • libz.so

下表列出了框架進程的命名空間配置(摘自 ld.config.txt 中的 [system] 部分):

命名空間 屬性
default search.paths /system/${LIB}
/product/${LIB}
permitted.paths /system/${LIB}/drm
/system/${LIB}/extractors
/system/${LIB}/hw
/product/${LIB}
/system/framework
/system/app
/system/priv-app
/vendor/app
/vendor/priv-app
/odm/app
/odm/priv-app
/oem/app
/product/framework
/product/app
/product/priv-app
/data
/mnt/expand
isolated true
sphal search.paths /odm/${LIB}
/vendor/${LIB}
permitted.paths /odm/${LIB}
/vendor/${LIB}
isolated true
visible true
links default,vndk,rs
link.default.shared_libs LL-NDK
link.vndk.shared_libs VNDK-SP
link.rs.shared_libs libRS_internal.so
vndk(適用於 VNDK-SP) search.paths /odm/${LIB}/vndk-sp
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp-${VER}
permitted.paths /odm/${LIB}/hw
/odm/${LIB}/egl
/vendor/${LIB}/hw
/vendor/${LIB}/egl
/system/${LIB}/vndk-sp-${VER}/hw
isolated true
visible true
links defaultsphal
link.default.shared_libs LL-NDK
link.default.allow_all_shared_libs true
rs(適用於 Renderscript) search.paths /odm/${LIB}/vndk-sp
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp-${VER}
/odm/${LIB}
/vendor/${LIB}
permitted.paths /odm/${LIB}
/vendor/${LIB}
/data(適用於已編譯的 RS 內核)
isolated true
visible true
links default,vndk
link.default.shared_libs LL-NDK
libmediandk.so
libft2.so
link.vndk.shared_libs VNDK-SP

下表列出了供應商進程的命名空間配置(摘自 ld.config.txt 中的 [vendor] 部分):

命名空間 屬性
default search.paths /odm/${LIB}
/vendor/${LIB}
permitted.paths /odm
/vendor
isolated true
visible true
links systemvndk
link.system.shared_libs LL-NDK
link.vndk.shared_libs VNDKVNDK-SP(供應商可用)
vndk search.paths /odm/${LIB}/vndk
/odm/${LIB}/vndk-sp
/vendor/${LIB}/vndk
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-${VER}
/system/${LIB}/vndk-sp-${VER}
isolated true
links systemdefault
link.system.shared_libs LL-NDK
link.default.allow_all_shared_libs true
system search.paths /system/${LIB}
isolated false

更多詳情請見 ${android-src}/system/core/rootdir/etc/ld.config.txt

ld.config.vndk_lite.txt

從 Android 8.0 開始,動態鏈接器將配置為隔離 SP-HAL 和 VNDK-SP 共享庫,以使其符號不會與其他框架共享庫發生沖突。鏈接器命名空間之間的關系如下所示:

ld.config.vndk_lite.txt 中描繪的鏈接器命名空間圖表 圖 2. 鏈接器命名空間隔離 ( ld.config.vndk_lite.txt)

LL-NDK 和 VNDK-SP 代表以下共享庫:

  • LL-NDK
    • libEGL.so
    • libGLESv1_CM.so
    • libGLESv2.so
    • libc.so
    • libdl.so
    • liblog.so
    • libm.so
    • libnativewindow.so
    • libstdc++.so(不在 ld.config.txt 中)
    • libsync.so
    • libvndksupport.so
    • libz.so(已移到 ld.config.txt 中的 VNDK-SP)
  • VNDK-SP
    • android.hardware.graphics.common@1.0.so
    • android.hardware.graphics.mapper@2.0.so
    • android.hardware.renderscript@1.0.so
    • android.hidl.memory@1.0.so
    • libbase.so
    • libc++.so
    • libcutils.so
    • libhardware.so
    • libhidlbase.so
    • libhidlmemory.so
    • libhidltransport.so
    • libhwbinder.so
    • libion.so
    • libutils.so

下表列出了框架進程的命名空間配置(摘自 ld.config.vndk_lite.txt 中的 [system] 部分):

命名空間 屬性
default search.paths /system/${LIB}
/odm/${LIB}
/vendor/${LIB}
/product/${LIB}
isolated false
sphal search.paths /odm/${LIB}
/vendor/${LIB}
permitted.paths /odm/${LIB}
/vendor/${LIB}
isolated true
visible true
links default,vndk,rs
link.default.shared_libs LL-NDK
link.vndk.shared_libs VNDK-SP
link.rs.shared_libs libRS_internal.so
vndk(適用於 VNDK-SP) search.paths /odm/${LIB}/vndk-sp
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp-${VER}
permitted.paths /odm/${LIB}/hw
/odm/${LIB}/egl
/vendor/${LIB}/hw
/vendor/${LIB}/egl
/system/${LIB}/vndk-sp-${VER}/hw
isolated true
visible true
links default
link.default.shared_libs LL-NDK
rs(適用於 Renderscript) search.paths /odm/${LIB}/vndk-sp
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp-${VER}
/odm/${LIB}
/vendor/${LIB}
permitted.paths /odm/${LIB}
/vendor/${LIB}
/data(適用於已編譯的 RS 內核)
isolated true
visible true
links default,vndk
link.default.shared_libs LL-NDK
libmediandk.so
libft2.so
link.vndk.shared_libs VNDK-SP

下表列出了供應商進程的命名空間配置(摘自 ld.config.vndk_lite.txt 中的 [vendor] 部分):

命名空間 屬性
default search.paths /odm/${LIB}
/odm/${LIB}/vndk
/odm/${LIB}/vndk-sp
/vendor/${LIB}
/vendor/${LIB}/vndk
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-${VER}
/system/${LIB}/vndk-sp-${VER}
/system/${LIB}(已棄用)
/product/${LIB}(已棄用)
isolated false

更多詳情請見 ${android-src}/system/core/rootdir/etc/ld.config.vndk_lite.txt

文檔歷史記錄

Android P 變更

  • 在 Android P 中,vndk 鏈接器命名空間已添加到供應商進程,而且 VNDK 共享庫已與默認鏈接器命名空間隔離開。

  • 將 PRODUCT_FULL_TREBLE 替換為更具體的 PRODUCT_TREBLE_LINKER_NAMESPACES

  • Android P 更改了以下動態鏈接器配置文件的名稱:

    Android 8.x Android P 說明
    ld.config.txt.in ld.config.txt 對於具有運行時鏈接器命名空間隔離的設備
    ld.config.txt ld.config.vndk_lite.txt 對於具有 VNDK-SP 鏈接器命名空間隔離的設備
    ld.config.legacy.txt ld.config.legacy.txt 對於搭載 Android 7.x 及更早版本的舊版設備
  • 移除 android.hardware.graphics.allocator@2.0.so

  • 添加了 product 和 odm 分區。

目錄、規則和 sepolicy

本頁面將介紹運行 Android 8.0 或更高版本系統的設備的目錄布局,以及 VNDK 規則和關聯的 sepolicy。

目錄布局

退化目錄布局由以下目錄組成:

  • /system/lib[64] 包含所有框架共享庫,具體包括 LL-NDK、VNDK 和框架專用庫(包括 LL-NDK-Private 和一些與 VNDK-SP 中的庫同名的庫)。
  • /system/lib[64]/vndk-sp 包含適用於 Same-Process HAL 的 VNDK-SP 庫。
  • /vendor/lib[64] 包含供應商擴展的 VNDK 庫(DXUA 庫或 DXUX VNDK 庫)、Same-Process HAL 實現,以及其他供應商共享庫。
  • /vendor/lib[64]/vndk-sp 可能包含供應商擴展的 VNDK-SP 庫。

供應商模塊從 /system/lib[64] 中加載 VNDK 庫。

VNDK 規則

本部分提供了完整的 VNDK 規則列表。

    • 框架進程不得從供應商分區中加載非 SP-HAL 共享庫(此規則從 Android 8.1 開始嚴格地強制實施)。
    • 供應商進程不得從系統分區中加載非 LL-NDK 庫、非 VNDK-SP 庫和非 VNDK 庫(Android O 中並未嚴格地強制實施此規則,但未來版本中會這么做)。
注意
    :要想從未來版本(比 Android 8.0 更高的版本)僅針對框架的 OTA 中受益,就不得在搭載 Android 8.0 出廠的設備中違反此規則。
  • 已安裝的 VNDK 庫必須是由 Google 定義的合格 VNDK 庫的子集。
  • SP-HAL 和 SP-HAL-Dep 的外部依賴項必須僅限於 LL-NDK 庫或由 Google 定義的 VNDK-SP 庫。
    • SP-HAL 共享庫的依賴項必須僅限於 LL-NDK 庫、由 Google 定義的 VNDK-SP 庫、其他 SP-HAL 庫和/或可標記為 SP-HAL-Dep 庫的其他供應商共享庫。
    • 只有當供應商共享庫不是 AOSP 庫,且其依賴項僅限於 LL-NDK 庫、由 Google 定義的 VNDK-SP 庫、SP-HAL 庫和/或其他 SP-HAL-Dep 庫時,才可標記為 SP-HAL-Dep 庫。
  • VNDK-SP 必須保持獨立。在 Android 8.0 中,系統以一種特殊方式處理 libRS_internal.so,但在未來版本中,其處理方式會被重新考慮。
  • 不得通過非 HIDL 接口(包括但不限於 Binder、套接字、共享內存、文件等)進行框架-供應商通信。
  • 系統分區必須足夠大,以便容納所有符合條件的 VNDK 庫的兩個副本,以及不符合條件的框架共享庫的一個副本。

sepolicy

本部分中介紹的框架進程對應於 sepolicy 中的 coredomain,而供應商進程對應於 non-coredomain。例如,/dev/binder 只能在 coredomain 中被訪問,而 /dev/vndbinder 只能在非 coredomain 中被訪問。

類似政策會限制對系統分區和供應商分區上的共享庫的訪問。下表列出了訪問不同類別的共享庫時所需的權限:

類別 分區 是否可從
coredomain 訪問
是否可從
非 coredomain 訪問
LL-NDK 系統
LL-NDK-Private 系統
VNDK-SP/VNDK-SP-Private 系統
VNDK-SP-Ext 供應商
VNDK 系統
VNDK-Ext 供應商
FWK-ONLY 系統
FWK-ONLY-RS 系統
SP-HAL 供應商
SP-HAL-Dep 供應商
VND-ONLY 供應商

LL-NDK-Private 和 VNDK-SP-Private 必須從這兩個域中都可訪問,因為非 coredomain 會間接訪問這些庫。同樣,SP-HAL-Dep 必須可從 coredomain 訪問,因為 SP-HAL 依賴該域。

same_process_hal_file 標簽

供應商分區中包含下面幾個庫。確保這些庫既可以從 coredomain 訪問,又可以從非 coredomain 訪問。

  • VNDK-SP-Ext,位於 /vendor/lib[64]/vndk-sp
  • SP-HAL,位於 /vendor/lib[64] 或 /vendor/lib[64]/hw
  • SP-HAL-Dep,位於 /vendor/lib[64] 或 /vendor/lib[64]/hw

將這些文件明確標記為 same_process_hal_file。因為在默認情況下,從 coredomain 無法訪問 vendor 分區中的任何內容。請向供應商特定的 file_contexts 文件中添加與以下命令行類似的命令行:

/vendor/lib(64)?/hw/libMySpHal\.so        u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/vndk-sp/libBase\.so      u:object_r:same_process_hal_file:s0
/vendor/lib(64)?/libBaseInternal\.so      u:object_r:same_process_hal_file:s0


Renderscript

RenderScript 是用於在 Android 上以高性能運行計算密集型任務的框架。RenderScript 專為數據並行計算而設計,不過串行工作負載也可以從中受益。RenderScript 運行時可以並行安排設備上可用的多個處理器(如多核 CPU 和 GPU)上的工作負載,使開發者能夠專注於表達算法而不是調度工作。RenderScript 對於專注於圖像處理、計算攝影或計算機視覺的應用來說尤其有用。

運行 Android 8.0 及更高版本的設備使用以下 RenderScript 框架和供應商 HAL:

圖 1. 與內部庫相關聯的供應商代碼

與 Android 7.x 及更低版本中的 RenderScript 之間的區別包括:

  • 一個進程中有兩組 RenderScript 內部庫的實例。一組用於 CPU 備用路徑,直接來源於 /system/lib;另一組用於 GPU 路徑,來源於 /system/lib/vndk-sp
  • /system/lib 中的 RS 內部庫是作為平台的一部分構建的,會隨着 system.img 的升級而更新。不過,/system/lib/vndk-sp 中的庫是面向供應商構建的,不會隨着 system.img 的升級而更新(雖然可以針對安全修復程序進行更新,但其 ABI 仍然保持不變)。
  • 供應商代碼(RS HAL、RS 驅動程序和 bcc plugin)與位於 /system/lib/vndk-sp 的 RenderScript 內部庫相關聯。它們無法與 /system/lib 中的庫相關聯,因為該目錄中的庫是面向平台構建的,可能與供應商代碼不兼容(即,符號可能會被移除)。如此一來可能會導致僅針對框架的 OTA 無法實現。

有關詳情,請參閱 developer.android.com 上的 Renderscript

設計

以下部分詳細介紹了 Android 8.0 及更高版本中的 RenderScript 設計。

供應商可使用的 RenderScript 庫

本部分列出了向供應商代碼開放且可與之關聯的 RenderScript 庫(稱為供應商 NDK,適用於 Same-Process HAL 或 VNDK-SP)。此外,本部分還詳細介紹了雖然與 RenderScript 無關但也已向供應商代碼提供的其他庫。

雖然以下庫的列表可能會因 Android 版本而異,但對於特定的 Android 版本來說是不變的;有關可用庫的最新列表,請參閱 /system/etc/ld.config.txt

注意:任何供應商代碼都無法使用下面未列出的庫;也就是說,供應商的  bcc plugin 無法使用  libLLVM.so,因為以下列表中不包含該庫。
RenderScript 庫 非 RenderScript 庫
  • android.hardware.graphics.renderscript@1.0.so
  • libRS_internal.so
  • libRSCpuRef.so
  • libblas.so
  • libbcinfo.so
  • libcompiler_rt.so
  • libRSDriver.so
  • libc.so
  • libm.so
  • libdl.so
  • libstdc++.so
  • liblog.so
  • libnativewindow.so
  • libsync.so
  • libvndksupport.so
  • libbase.so
  • libc++.so
  • libcutils.so
  • libutils.so
  • libhardware.so
  • libhidlbase.so
  • libhidltransport.so
  • libhwbinder.so
  • liblzma.so
  • libz.so
  • libEGL.so
  • libGLESv1_CM.so
  • libGLESv2.so

鏈接器命名空間配置

系統會在運行時使用鏈接器命名空間,強制實施關聯限制,阻止供應商代碼使用 VNDK-SP 中未包含的庫(有關詳情,請參閱 VNDK 設計演示文稿)。

在運行 Android 8.0 或更高版本的設備上,除 RenderScript 之外的所有 Same-Process HAL (SP-HA) 都會在鏈接器命名空間 sphal 中加載。RenderScript 將被加載到 RenderScript 專用的命名空間 rs 中,該位置對 RenderScript 庫的限制稍微寬松些。由於 RS 實現需要加載編譯后的位碼,因此系統會將 /data/*/*.so 添加到 rs 命名空間的路徑中(不允許其他 SP-HAL 從該數據分區加載庫)。

此外,rs 命名空間所允許的庫要比其他命名空間提供的庫多。libmediandk.so 和 libft2.so 將可用於 rs 命名空間,因為 libRS_internal.so 有一個對這些庫的內部依賴項。

圖 2. 鏈接器的命名空間配置

加載驅動程序

CPU 備用路徑

根據在創建 RS 上下文時是否存在 RS_CONTEXT_LOW_LATENCY 位,可以選擇 CPU 或 GPU 路徑。選擇 CPU 路徑時,系統會直接從默認鏈接器命名空間(提供了 RS 庫的平台版本)對 libRS_internal.so(RS 框架的主要實現)執行 dlopen 處理。

采用 CPU 備用路徑時,系統根本不會使用來自供應商的 RS HAL 實現,而是通過空的 mVendorDriverName 創建一個 RsContext 對象。系統會對 libRSDriver.so 執行 dlopen 處理(默認情況下),且驅動程序庫會從 default 名稱空間加載,因為調用程序 (libRS_internal.so) 也會在 default 命名空間中加載。

圖 4. CPU 備用路徑

GPU 路徑

對於 GPU 路徑來說,系統會通過不同的方式加載 libRS_internal.so。首先,libRS.so 使用 android.hardware.renderscript@1.0.so(及其底層的 libhidltransport.so)將 android.hardware.renderscript@1.0-impl.so(一種 RS HAL 的供應商實現)加載到一個不同的鏈接器命名空間(名稱為 sphal)。然后,RS HAL 在另一個名稱為 rs 的鏈接器命名空間中對 libRS_internal.so 執行 dlopen 處理。

供應商可以通過設置編譯時標記 OVERRIDE_RS_DRIVER 來提供自己的 RS 驅動程序,該標記嵌入在 RS HAL 實現 (hardware/interfaces/renderscript/1.0/default/Context.cpp) 中。然后,系統會在 GPU 路徑的 RS 上下文中對該驅動程序名稱執行 dlopen 處理。

RsContext 對象的創建被委派給 RS HAL 實現。HAL 使用 rsContextCreateVendor() 函數(並將驅動程序的名稱用作參數)來回調 RS 框架。然后,RS 框架會在 RsContext 進行初始化時加載指定的驅動程序。在這種情況下,驅動程序庫會加載到 rs 命名空間中,因為 RsContext 對象是在 rs 命名空間內創建的,而且 /vendor/lib 位於該命名空間的搜索路徑中。

圖 5. GPU 備用路徑

從 default 命名空間轉換為 sphal 命名空間時,libhidltransport.so 使用 android_load_sphal_library() 函數來明確指示動態鏈接器從 sphal 命名空間加載 -impl.so 庫。

從 sphal 命名空間轉換為 rs 命名空間時,加載由 /system/etc/ld.config.txt 中的以下行間接完成:

namespace.sphal.link.rs.shared_libs = libRS_internal.so
 

此行指定了以下規則:如果無法從 sphal 命名空間找到/加載目標庫(這種情況一直會出現,因為 sphal 命名空間不會搜索 libRS_internal.so 所在的 /system/lib/vndk-sp),動態鏈接器應該從 rs 命名空間加載 libRS_internal.so。借助此配置,對 libRS_internal.so 進行簡單的 dlopen() 調用就足以實現命名空間轉換。

加載 bcc 插件

bcc plugin 是由供應商提供的加載到 bcc 編譯器中的庫。由於 bcc 是 /system/bin 目錄中的系統進程,因此 bcc plugin 可以被視為 SP-HAL(即,可以直接加載到系統進程中而無需 Binder 化的供應商 HAL)。作為 SP-HAL,bcc-plugin 庫具有以下特點:

  • 無法與框架專用庫(如 libLLVM.so)相關聯。
  • 只能與面向供應商的 VNDK-SP 庫相關聯。

此限制是通過使用 android_sphal_load_library() 函數將 bcc plugin 加載到 sphal 命名空間來強制實施的。在之前的 Android 版本中,插件名稱是使用 -load 選項指定的,而庫是由 libLLVM.so 使用簡單的 dlopen() 加載的。在 Android 8.0 及更高版本中,該名稱在 -plugin 選項中指定,而庫則直接由 bcc 本身加載。此選項可使開放源代碼 LLVM 項目支持非 Android 專用路徑。

圖 6. 加載 bcc 插件 - Android 7.x 及更低版本

圖 7. 加載 bcc 插件 - Android 8.0 及更高版本

ld.mc 的搜索路徑

在執行 ld.mc 時,系統會將某些 RS 運行時庫作為輸入提供給鏈接器。來自應用的 RS 位碼會與運行時庫相關聯,當轉換后的位碼被加載到某個應用進程中時,會再次與運行時庫動態關聯。

運行時庫包括:

  • libcompiler_rt.so
  • libm.so
  • libc.so
  • RS 驅動程序(libRSDriver.so 或 OVERRIDE_RS_DRIVER

在將編譯后的位碼加載到應用進程中時,請提供與 ld.mc 所使用的完全相同的庫。否則,編譯后的位碼可能無法找到它被關聯時可供使用的那個符號。

為此,RS 框架在執行 ld.mc 時會針對運行時庫使用不同的搜索路徑,具體取決於 RS 框架本身是從 /system/lib 中還是 /system/lib/vndk-sp 中加載的。通過讀取 RS 框架庫的任意符號的地址,並使用 dladdr() 獲取映射到該地址的文件路徑,可以確定 RS 框架的加載位置。

SELinux 政策

由於 Android 8.0 及更高版本中的 SELinux 政策發生了變化,您在 neverallows 分區中標記額外的文件時必須遵循特定規則(通過 vendor 強制實施):

  • vendor_file 必須是 vendor 分區中所有文件的默認標簽。平台政策要求使用此標簽來訪問直通式 HAL 實現。
  • 通過供應商 SEPolicy 在 vendor 分區中添加的所有新 exec_types 均必須具有 vendor_file_type 屬性。這一規則將通過 neverallows 強制實施。
  • 為了避免與將來的平台/框架更新發生沖突,請避免在 vendor 分區中標記除 exec_types 之外的文件。
  • AOSP 標識的 Same-Process HAL 的所有庫依賴項均必須標記為 same_process_hal_file

要詳細了解 SELinux 政策,請參閱 Android 中的安全增強型 Linux

位碼的 ABI 兼容性

如果沒有添加新的 API(意味着無 HAL 版本遞增),RS 框架將繼續使用現有的 GPU (HAL 1.0) 驅動程序。

對於不會影響位碼的 HAL 小更改 (HAL 1.1),RS 框架應該回退到 CPU 以支持這些新添加的 API,並在其他地方繼續使用 GPU (HAL 1.0) 驅動程序。

對於會影響位碼編譯/關聯的 HAL 大更改 (HAL 2.0),RS 框架應選擇不加載供應商提供的 GPU 驅動程序,而是使用 CPU 或 Vulkan 路徑以實現加速。

RenderScript 位碼的使用發生在以下三個階段:

階段 詳細信息
編譯
  • bcc 的輸入位碼 (.bc) 的格式必須是 LLVM 3.2,且 bcc 必須向后兼容現有的(舊版)應用。
  • 不過,.bc 中的元數據可能會發生變化(可能會有新的運行時函數,例如分配設置器和獲取器、數學函數等)。部分運行時函數位於 libclcore.bc 中,部分位於 LibRSDriver 或供應商同類驅動程序中。
  • 對於新運行時函數或重大元數據更改,必須遞增位碼 API 級別。HAL 版本也必須遞增,否則供應商驅動程序將無法使用它。
  • 供應商可能有自己的編譯器,不過針對 bcc 的總結/要求也適用於這些編譯器。
鏈接
  • 編譯后的 .o 將與供應商驅動程序相關聯,例如 libRSDriver_foo.so 和 libcompiler_rt.so。CPU 路徑將與 libRSDriver.so 相關聯。
  • 如果 .o 需要來自 libRSDriver_foo 的新運行時 API,則供應商驅動程序必須進行更新,以便為其提供支持。
  • 某些供應商可能有自己的鏈接器,不過適用於 ld.mc 的參數也適用於這些鏈接器。
加載
  • libRSCpuRef 會加載共享對象。如果此接口發生更改,則需要遞增 HAL 版本。
  • 供應商可以依賴 libRSCpuRef 加載共享對象,也可以實現自己的對象。

除了 HAL 之外,運行時 API 和導出的符號也是接口。從 Android 7.0 (API 24) 開始,這兩種接口均未發生更改,目前也沒有在 Android 8.0 及更高版本中對其做出更改的計划。但是,如果接口發生更改,HAL 版本也會進行遞增。

供應商實現

Android 8.0 及更高版本需要對 GPU 驅動程序做出一些更改,以便 GPU 驅動程序能夠正常運行。

驅動程序模塊

  • 驅動程序模塊不得依賴此列表中未包含的任何系統庫。
  • 驅動程序必須提供自己的 android.hardware.renderscript@1.0-impl_{NAME},或者將默認實現 android.hardware.renderscript@1.0-impl 聲明為其依賴項。
  • CPU 實現 libRSDriver.so 就是關於如何移除非 VNDK-SP 依賴項的一個很好的例子。

位碼編譯器

您可以通過以下兩種方式為供應商驅動程序編譯 RenderScript 位碼:

  1. 在 /vendor/bin/ 中調用供應商專用的 RenderScript 編譯器(GPU 編譯的首選方法)。與其他驅動程序模塊類似,供應商編譯器二進制文件不能依賴面向供應商的 RenderScript 庫列表中未包含的任何系統庫。
  2. 使用供應商提供的 bcc plugin 調用系統 bcc:/system/bin/bcc;此插件不能依賴面向供應商的 RenderScript 庫列表中未包含的任何系統庫。

如果供應商 bcc plugin 需要干預 CPU 編譯,並且它對 libLLVM.so 的依賴無法輕松解除,那么供應商應將 bcc(以及包括 libLLVM.so 和 libbcc.so 在內的所有非 LL-NDK 依賴項)復制到 /vendor 分區。

此外,供應商還需要做出以下更改:

圖 8. 供應商驅動程序更改
  1. 將 libclcore.bc 復制到 /vendor 分區。這樣可以確保 libclcore.bclibLLVM.so 和 libbcc.so 保持同步。
  2. 在 RS HAL 實現中設置 RsdCpuScriptImpl::BCC_EXE_PATH 來更改 bcc 可執行文件的路徑。
注意:對  /vendor/bin/* 進程的限制並未完全實現。從理論上講,可以只將  bcc 復制到  /vendor/bin/ 而不復制其依賴項,但並不建議您這樣做。

SELinux 政策

SELinux 政策會影響驅動程序和編譯器可執行文件。所有驅動程序模塊必須在設備的 file_contexts 中標記為 same_process_hal_file。例如:

/vendor/lib(64)?/libRSDriver_EXAMPLE\.so     u:object_r:same_process_hal_file:s0
 

編譯器可執行文件必須能夠由應用進程調用,bcc 的供應商副本 (/vendor/bin/bcc) 也是如此。例如:

device/vendor_foo/device_bar/sepolicy/file_contexts:
/vendor/bin/bcc                    u:object_r:same_process_hal_file:s0
 

舊版設備

舊版設備是指滿足以下條件的設備:

  1. PRODUCT_SHIPPING_API_LEVEL 低於 26。
  2. PRODUCT_FULL_TREBLE_OVERRIDE 未定義。

將舊版設備的系統升級到 Android 8.0 及更高版本時,不會強制執行這些限制,這意味着驅動程序可以繼續與 /system/lib[64] 中的庫相關聯。不過,由於與 OVERRIDE_RS_DRIVER 相關的架構變更,您必須將 android.hardware.renderscript@1.0-impl 安裝到 /vendor 分區;如果無法做到這一點,RenderScript 運行時會被強制回退到 CPU 路徑。

 

ABI 穩定性

應用二進制接口 (ABI) 穩定性是進行僅針對框架的更新的前提條件,因為供應商模塊可能依賴於系統分區中的供應商原生開發套件 (VNDK) 共享庫。新編譯的 VNDK 共享庫必須與之前發布的 VNDK 共享庫保持 ABI 兼容性,以便供應商模塊可以與這些庫協同工作,而無需重新編譯,也不會出現運行時錯誤。

為了確保實現 ABI 兼容性,Android 9 中添加了一個標頭 ABI 檢查工具,下文會對該工具進行介紹。

關於 VNDK 和 ABI 合規性

VNDK 是供應商模塊可以關聯到的一組受限庫,用於實現僅針對框架的更新。ABI 合規性是指較新版本的共享庫能夠按預期與動態關聯到它的模塊協同工作(即像較舊版本的共享庫那樣正常工作)。

關於導出的符號

導出的符號(也稱為全局符號)是指滿足以下所有條件的符號:

  • 通過共享庫的公開標頭導出。
  • 顯示在與共享庫對應的 .so 文件的 .dynsym 表中。
  • 具有 WEAK 或 GLOBAL 綁定。
  • 可見性為 DEFAULT 或 PROTECTED。
  • 區塊索引不是 UNDEFINED。
  • 類型為 FUNC 或 OBJECT。

共享庫的公開頭文件是指通過以下屬性提供給其他庫/二進制文件使用的頭文件:export_include_dirsexport_header_lib_headersexport_static_lib_headersexport_shared_lib_headers 和 export_generated_headers 屬性(位於與共享庫對應的模塊的 Android.bp 定義中)。

關於可到達類型

可到達類型是指可通過導出的符號直接或間接到達並且是通過公開標頭導出的任何 C/C++ 內置類型或用戶定義的類型。例如,libfoo.so 具有函數 Foo,該函數是一個導出的符合,可在 .dynsym 表中找到。libfoo.so 庫包含以下內容:

foo_exported.h foo.private.h

typedef struct foo_private foo_private_t;

typedef struct foo {
  int m1;
  int *m2;
  foo_private_t *mPfoo;
} foo_t;

typedef struct bar {
  foo_t mfoo;
} bar_t;

bool Foo(int id, bar_t *bar_ptr);
 

typedef struct foo_private {
  int m1;
  float mbar;
} foo_private_t;
 
Android.bp

cc_library {
  name : libfoo,
  vendor_available: true,
  vndk {
    enabled : true,
  }
  srcs : ["src/*.cpp"],
  export_include_dirs : [
    "include"
  ],
}
 
.dynsym 表
Num Value Size Type Bind Vis Ndx Name
1 0 0 FUNC GLOB DEF UND dlerror@libc
2 1ce0 20 FUNC GLOB DEF 12 Foo

以 Foo 為例,直接/間接可到達類型包括:

類型 說明
bool Foo 的返回值類型。
int 第一個 Foo 參數的類型。
bar_t * 第二個 Foo 參數的類型。bar_t 是經由 bar_t * 通過 foo_exported.h 導出的。 

bar_t 包含類型 foo_t(通過 foo_exported.h 導出)的一個成員 mfoo,這會導致導出更多類型:
  • int : 是 m1 的類型。
  • int * : 是 m2 的類型。
  • foo_private_t * : 是 mPfoo 的類型。

不過,foo_private_t 不是可到達類型,因為它不是通過 foo_exported.h 導出的。(foot_private_t * 不透明,因此系統允許對 foo_private_t 進行更改)。

對於可通過基類指定符和模板參數到達的類型,也可給出類似解釋。

確保 ABI 合規性

對於在對應的 Android.bp 文件中標有 vendor_available: true 和 vndk.enabled: true 的庫,必須確保其 ABI 合規性。例如:

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}
 

對於可通過導出的函數直接或間接到達的數據類型,對庫進行以下更改會破壞 ABI 合規性:

數據類型 說明
結構和類
  • 更改類類型或結構類型的大小。
  • 基類
    • 添加或移除基類。
    • 添加或移除虛擬繼承的基類。
    • 更改基類的順序。
  • 成員函數
    • 移除成員函數*。
    • 將參數添加到成員函數或從中移除參數。
    • 更改成員函數的參數類型或返回類型*。
    • 更改虛表布局。
  • 數據成員
    • 移除靜態數據成員。
    • 添加或移除非靜態數據成員。
    • 更改數據成員的類型。
    • 將偏移量更改為非靜態數據成員**。
    • 更改數據成員的 constvolatile 和/或 restricted 限定符***。
    • 對數據成員的訪問權限說明符進行降級***。
  • 更改模板參數。
聯合
  • 添加或移除數據成員。
  • 更改聯合類型的大小。
  • 更改數據成員的類型。
  • 更改數據成員的順序。
枚舉
  • 更改基礎類型。
  • 更改枚舉器的名稱。
  • 更改枚舉器的值。
全局符號
  • 移除通過公開頭文件導出的符號。
  • 對於類型 FUNC 的全局符號
    • 添加或移除參數。
    • 更改參數類型。
    • 更改返回類型。
    • 對訪問權限說明符進行降級***。
  • 對於類型 OBJECT 的全局符號
    • 更改相應的 C/C++ 類型。
    • 對訪問權限說明符進行降級***。

* 不得更改或移除公共和私有成員函數,因為公共內聯函數可以引用私有成員函數。私有成員函數的符號引用可保存在調用程序二進制文件中。從共享庫更改或移除私有成員函數會導致二進制文件向后不兼容。

** 不得更改公共或私有數據成員的偏移量,因為內聯函數可以在其函數主體中引用這些數據成員。更改數據成員偏移量會導致二進制文件向后不兼容。

*** 雖然這些操作不會更改類型的內存布局,但它們之間存在語義差異,可能導致庫無法按預期正常運行。

使用 ABI 合規性工具

編譯 VNDK 庫時,系統會將其 ABI 與所編譯 VNDK 的版本對應的 ABI 參考進行比較。參考 ABI 轉儲位於以下位置:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
 

例如,在為 VNDK 的 API 級別 27 編譯 libfoo 時,系統會將 libfoo 的推斷 ABI 與其參考進行比較,該參考位於以下位置:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump
 

ABI 損壞錯誤

當 ABI 損壞時,編譯日志會顯示警告,其中包含警告類型以及 abi-diff 報告所在的路徑。例如,如果 libbinder的 ABI 有不兼容的更改,則編譯系統會拋出錯誤,並顯示類似以下的消息:

*****************************************************
error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES
Please check compatibility report at:
out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff
******************************************************
---- Please update abi references by running
platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ----
 

編譯 VNDK 庫時進行的 ABI 檢查

編譯 VNDK 庫時:

  1. header-abi-dumper 會處理為了編譯 VNDK 庫(庫本身的源文件以及通過靜態傳遞依賴項沿用的源文件)而編譯的源文件,以生成與各個源文件對應的 .sdump 文件。 
    創建 sdump圖 1. 創建 .sdump 文件
  2. 然后,header-abi-linker 會處理 .sdump 文件(使用提供給它的版本腳本或與共享庫對應的 .so 文件),以生成 .lsdump 文件,該文件用於記錄與共享庫對應的所有 ABI 信息。 
    創建 lsdump圖 2. 創建 .lsdump 文件
  3. header-abi-diff 會將 .lsdump 文件與參考 .lsdump 文件進行比較,以生成差異報告,該報告中會簡要說明兩個庫的 ABI 之間存在的差異。 
    創建 abi diff圖 3. 創建差異報告

header-abi-dumper

header-abi-dumper 工具會解析 C/C++ 源文件,並將從該源文件推斷出的 ABI 轉儲到一個中間文件。編譯系統會對所有已編譯的源文件運行 header-abi-dumper,同時還會建立一個庫,其中包含來自傳遞依賴項的源文件。

目前,.sdump 文件采用 Protobuf TextFormatted 格式,我們無法保證該格式在未來版本中仍保持穩定。因此,.sdump 文件格式化應被視為編譯系統的實現細節。

例如,libfoo.so 具有以下源文件 foo.cpp

#include <stdio.h>
#include <foo_exported.h>

bool Foo(int id, bar_t *bar_ptr) {
    if (id > 0 && bar_ptr->mfoo.m1 > 0) {
        return true;
    }
    return false;
}
 

您可以使用 header-abi-dumper 生成中間 .sdump 文件,該文件代表源文件使用以下命令提供的 ABI:

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++
 

該命令指示 header-abi-dumper 解析 foo.cpp 並發出 ABI 信息(顯示在 exported 目錄內的公開頭文件中)。下面是 header-abi-dumper 生成的 foo.sdump 中的一部分(並非完整表示):

record_types {
  type_info {
    name: "foo"
    size: 12
    alignment: 4
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 32
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-5"
    field_offset: 64
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 12
    alignment: 4
    referenced_type: "type-6"

pointer_types {
  type_info {
    name: "bar *"
    size: 4
    alignment: 4
    referenced_type: "type-6"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "bar *"
    self_type: "type-8"
  }
}
builtin_types {
  type_info {
    name: "int"
    size: 4
    alignment: 4
    referenced_type: "type-2"
    source_file: ""
    linker_set_key: "int"
    self_type: "type-2"
  }
  is_unsigned: false
  is_integral: true
}
functions {
  return_type: "type-7"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-8"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}
 

foo.sdump 包含源文件 foo.cpp 提供的 ABI 信息,例如:

  • record_types:指通過公開標頭提供的結構、聯合或類。每個記錄類型都包含其字段、大小、訪問權限指定符、所在標頭文件等相關信息。
  • pointer_types:指通過公開標頭提供的記錄/函數直接/間接引用的指針類型,以及指針指向的類型(通過 type_info 中的 referenced_type 字段)。對於限定類型、內置 C/C++ 類型、數組類型以及左值和右值參考類型(有關類型的此類記錄信息允許遞歸差異),系統會在 .sdump 文件中記錄類似信息。
  • functions:表示通過公開標頭提供的函數。它們還包含函數的重整名稱、返回值類型、參數類型、訪問權限指定符等相關信息。
提示:要獲取  header-abi-dumper 工具方面的幫助,請運行  header-abi-dumper --help

header-abi-linker

header-abi-linker 工具會將 header-abi-dumper 生成的中間文件作為輸入,然后關聯以下文件:

輸入
  • header-abi-dumper 生成的中間文件
  • 版本腳本/映射文件(可選)
  • 共享庫的 .so 文件
輸出 用於記錄共享庫 ABI 的文件(例如,libfoo.so.lsdump 表示 libfoo 的 ABI)。

該工具會將收到的所有中間文件中的類型圖合並在一起,並會將不同轉換單元之間的單一定義(完全限定名稱相同的不同轉換單元中由用戶定義的類型可能在語義上有所不同)差異考慮在內。然后,該工具會解析版本腳本或解析共享庫的 .dynsym 表(.so 文件),以創建導出符號列表。

例如,當 libfoo 將 bar.cpp 文件(用於提供 C 函數 bar)添加到其編譯時,系統可能會調用 header-abi-linker,以創建 libfoo 的完整關聯 ABI 轉儲,如下所示:

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current
 

libfoo.so.lsdump 中的命令輸出示例:

record_types {
  type_info {
    name: "foo"
    size: 24
    alignment: 8
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 64
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-4"
    field_offset: 128
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 24
    alignment: 8
...
builtin_types {
  type_info {
    name: "void"
    size: 0
    alignment: 0
    referenced_type: "type-6"
    source_file: ""
    linker_set_key: "void"
    self_type: "type-6"
  }
  is_unsigned: false
  is_integral: false
}
functions {
  return_type: "type-19"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-20"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}
functions {
  return_type: "type-6"
  function_name: "FooBad"
  source_file: "foo/include/foo_exported_bad.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
parameters {
    referenced_type: "type-7"
    default_arg: false
  }
  linker_set_key: "_Z6FooBadiP3foo"
  access: public_access
}
elf_functions {
  name: "_Z3FooiP3bar"
}
elf_functions {
  name: "_Z6FooBadiP3foo"
}
 

header-abi-linker 工具將執行以下操作:

  • 關聯收到的 .sdump 文件(foo.sdump 和 bar.sdump),濾除位於 exported 目錄的標頭中不存在的 ABI 信息。
  • 解析 libfoo.so,然后通過其 .dynsym 表收集通過庫導出的符號的相關信息。
  • 添加 _Z3FooiP3bar 和 Bar

libfoo.so.lsdump 是最終生成的 libfoo.so ABI 轉儲。

提示:要獲取  header-abi-linker 工具方面的幫助,請運行  header-abi-linker --help

header-abi-diff

header-abi-diff 工具會將代表兩個庫的 ABI 的兩個 .lsdump 文件進行比較,並生成差異報告,其中會說明這兩個 ABI 之間存在的差異。

輸入
  • 表示舊共享庫的 ABI 的 .lsdump 文件。
  • 表示新共享庫的 ABI 的 .lsdump 文件。
輸出 差異報告,其中會說明在比較兩個共享庫提供的 ABI 之后發現的差異。

ABI 差異文件會盡可能詳細且便於讀懂。格式在未來版本中可能會發生變化。例如,您有兩個版本的 libfoolibfoo_old.so 和 libfoo_new.so。在 libfoo_new.so 中的 bar_t 內,您將 mfoo 的類型從 foo_t 更改為 foo_t *。由於 bar_t 是直接可到達類型,因此這應該由 header-abi-diff 標記為會破壞 ABI 的更改。

要運行 header-abi-diff,請執行以下命令:

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo
 

libfoo.so.abidiff 中的命令輸出示例:

lib_name: "libfoo"
arch: "arm64"
record_type_diffs {
  name: "bar"
  type_stack: "Foo-> bar *->bar "
  type_info_diff {
    old_type_info {
      size: 24
      alignment: 8
    }
    new_type_info {
      size: 8
      alignment: 8
    }
  }
  fields_diff {
    old_field {
      referenced_type: "foo"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
    new_field {
      referenced_type: "foo *"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
  }
}
 

libfoo.so.abidiff 包含一個報告,其中會注明 libfoo 中所有會破壞 ABI 的更改。record_type_diffs 消息表示記錄發生了更改,並會列出不兼容的更改,其中包括:

  • 記錄大小從 24 個字節更改為 8 個字節。
  • mfoo 的字段類型從 foo 更改為 foo *(去除了所有類型定義符)。

type_stack 字段用於指示 header-abi-diff 如何到達已更改的類型 (bar)。該字段可作如下解釋:Foo 是一個導出的函數,接受 bar * 作為參數,該參數指向已導出且發生變化的 bar

提示:要獲取  header-abi-diff 工具方面的幫助,請運行  header-abi-diff --help。您也可以參閱  development/vndk/tools/header-checker/README.md

強制執行 ABI/API

要強制執行 VNDK 和 LLNDK 共享庫的 ABI/API,必須將 ABI 參考簽入到 ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ 中。要創建這些參考,請運行以下命令:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
 

創建參考后,如果對源代碼所做的任何更改導致 VNDK 或 LLNDK 庫中出現不兼容的 ABI/API 更改,則這些更改現在會導致編譯錯誤。

要更新特定 VNDK 核心庫的 ABI 參考,請運行以下命令:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
 

例如,要更新 libbinder ABI 參考,請運行以下命令:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder
 

要更新特定 LLNDK 庫的 ABI 參考,請運行以下命令:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
 

例如,要更新 libm ABI 參考,請運行以下命令:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk
 
 

預編譯 ABI 使用情況檢查工具

Android 共享庫會不時改進。使預編譯二進制文件保持最新狀態需要投入大量的努力。在 Android 9 或更早版本中,依賴於已移除的庫或 ABI 的預編譯二進制文件僅在運行時無法成功關聯。開發者必須跟蹤日志以查找過時的預編譯二進制文件。Android 10 中引入了基於符號的 ABI 使用情況檢查工具。該檢查工具可以在編譯時檢測過時的預編譯二進制文件,以便共享庫開發者了解哪些預編譯二進制文件可能會因更改而遭到破壞,以及哪些預編譯二進制文件必須重新編譯。

基於符號的 ABI 使用情況檢查工具

基於符號的 ABI 使用情況檢查工具可模擬主機上的 Android 動態鏈接器。該檢查工具會將預編譯的二進制文件與預編譯的二進制文件的依賴項關聯起來,並檢查是否所有未定義符號均已解析。

首先,檢查工具會檢查預編譯二進制文件的目標架構。如果預編譯的二進制文件不以 ARM、AArch64、x86 或 x86-64 架構為目標,則檢查工具將跳過該預編譯二進制文件。

其次,必須在 LOCAL_SHARED_LIBRARIES 或 shared_libs 中列出預編譯二進制文件的依賴項。編譯系統會將模塊名稱解析為共享庫的匹配變體(即 core 與 vendor)。

第三,檢查工具會將 DT_NEEDED 條目與 LOCAL_SHARED_LIBRARIES 或 shared_libs 進行比較。尤其是,該檢查工具會從每個共享庫中提取 DT_SONAME 條目,並將這些 DT_SONAME 與預編譯二進制文件中記錄的 DT_NEEDED 條目進行比較。如果存在不匹配,則系統會發出錯誤消息。

第四,該檢查工具會解析預編譯二進制文件中未定義的符號。這些未定義的符號必須在一個依賴項中進行定義,並且符號綁定必須為 GLOBAL 或 WEAK。如果無法解析某個未定義的符號,則平台會發出錯誤消息。

預編譯模塊屬性

必須在以下一項中指定預編譯二進制文件的依賴項:

  • Android.bp:shared_libs: ["libc", "libdl", "libm"],
  • Android.mk:LOCAL_SHARED_LIBRARIES := libc libdl libm

如果預編譯二進制文件具有一些無法解析的未定義符號,請指定以下內容之一:

  • Android.bp:allow_undefined_symbols: true,
  • Android.mk:LOCAL_ALLOW_UNDEFINED_SYMBOLS := true

要使預編譯二進制文件跳過 ELF 文件檢查,請指定以下內容之一:

  • Android.bp:check_elf_files: false,
  • Android.mk:LOCAL_CHECK_ELF_FILES := false

運行檢查工具

要運行檢查工具,請將環境變量 CHECK_ELF_FILES 設置為 true,並運行 make check-elf-files

CHECK_ELF_FILES=true make check-elf-files
 

要默認啟用檢查工具,請將 PRODUCT_CHECK_ELF_FILES 添加到 BoardConfig.mk

PRODUCT_CHECK_ELF_FILES := true
 

在 Android 的編譯過程中會對預編譯自動進行檢查:

make
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM