强制执行产品分区接口

Android 11 取消了 product 分区捆绑,使其独立于 systemvendor 分区。作为这些变更的一部分,您现在可以控制 product 分区对原生和 Java 接口的访问权限(与 vendor 分区的接口强制工作原理类似)。

强制执行原生接口

如需启用原生接口强制执行,请将 PRODUCT_PRODUCT_VNDK_VERSION 设置为 current。(当目标的 Shipping API 级别大于 29 时,版本会自动设置为 current。)强制执行允许:

  • product 分区中的原生模块:
    • 以静态或动态方式链接到 product 分区中包含静态、共享或头文件库的其他模块。
    • 动态链接到 system 分区中的 VNDK 库。
  • product 分区中未捆绑 APK 的 JNI 库链接到 /product/lib/product/lib64 中的库(这是对 NDK 库的补充)。

强制执行不允许链接到 product 分区以外的其他分区。

构建时强制执行 (Android.bp)

在 Android 11 中,除核心和供应商映像变体外,系统模块还可以创建产品映像变体。启用原生接口强制执行后(PRODUCT_PRODUCT_VNDK_VERSION 设置为 current):

  • product 分区中的原生模块位于产品变体中,而不是核心变体中。

  • Android.bp 文件中包含 product_available: true 的模块可用于产品变体。

  • 指定 product_specific: true 的库或二进制文件可以关联到在 Android.bp 文件中指定 product_specific: trueproduct_available: true 的其他库。

  • VNDK 库的 Android.bp 文件中必须包含 product_available: true,以便 product 二进制文件可以链接到 VNDK 库。

下表总结了用于创建映像变体的 Android.bp 属性。

Android.bp 中的属性 已创建的变体
强制执行前 强制执行后
默认(无) 核心
(包含 /system/system_ext/product
核心
(包含 /system/system_ext,但不包含 /product
system_ext_specific: true core core
product_specific: true core 产品
vendor: true 供应商 供应商
vendor_available: true 核心、供应商 核心、供应商
product_available: true 不适用 核心、产品
vendor_available: trueproduct_available: true 不适用 核心、产品、供应商
system_ext_specific: truevendor_available: true 核心、供应商 核心、供应商
product_specific: truevendor_available: true 核心、供应商 产品、供应商

构建时强制执行 (Android.mk)

启用原生接口强制执行后,安装到 product 分区的原生模块将具有一个 native:product 链接类型,该链接类型只能链接到其他 native:productnative:vndk 模块。尝试链接到除上述类型之外的任何模块会导致构建系统生成链接类型检查错误。

运行时强制执行

启用原生接口强制执行后,Bionic 链接器的链接器配置不允许系统进程使用 product 库,这样会为 product 进程创建 product 部分,此类进程无法链接到 product 分区以外的库(但是,此类进程可以链接到 VNDK 库)。尝试违反运行时链接配置会导致进程失败并生成 CANNOT LINK EXECUTABLE 错误消息。

强制执行 Java 接口

如需启用 Java 接口强制执行,请将 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE 设置为 true。(当目标的 Shipping API 级别大于 29 时,该值会自动设置为 true。)启用后,强制执行将允许或禁止以下访问权限:

API /system /system_ext /product /vendor /data
公共 API
@SystemApi
@hide API

vendor 分区一样,product 分区中的应用或 Java 库只能使用公共和系统 API;不允许链接到使用隐藏 API 的库。此限制适用于构建时的链接以及运行时的反射。

构建时强制执行

构建时,Make 和 Soong 将通过检查 platform_apissdk_version 字段来验证 product 分区中的 Java 模块是否不使用隐藏 API。product 分区中应用的 sdk_version 必须填写 currentsystem_current 或 API 的数字版本,platform_apis 字段必须为为空。

运行时强制执行

Android 运行时会验证 product 分区中的应用是否不使用隐藏 API,包括反射。如需了解详情,请参阅针对非 SDK 接口的限制

启用产品接口强制执行

按照本部分中的步骤启用产品接口强制执行。

步骤 任务 必需
1 定义您自己的系统 Makefile,指定 system 分区的软件包,然后在 device.mk 中设置工件路径要求检查(防止非系统模块安装到 system 分区)。
2 清理允许列表。
3 强制执行原生接口并确定运行时链接失败(可以与 Java 强制执行并行运行)。
4 强制执行 Java 接口并验证运行时行为(可以与原生强制执行并行运行)。
5 检查运行时行为。
6 通过产品接口强制执行更新 device.mk

第 1 步:创建 Makefile 并启用工件路径检查

在该步骤中定义 system Makefile。

  1. 创建一个 Makefile,定义 system 分区的软件包。例如,使用以下命令创建一个 oem_system.mk 文件:

    $(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system.mk)
    $(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system.mk)
    
    # Applications
    PRODUCT_PACKAGES += \
        CommonSystemApp1 \
        CommonSystemApp2 \
        CommonSystemApp3 \
    
    # Binaries
    PRODUCT_PACKAGES += \
        CommonSystemBin1 \
        CommonSystemBin2 \
        CommonSystemBin3 \
    
    # Libraries
    PRODUCT_PACKAGES += \
        CommonSystemLib1 \
        CommonSystemLib2 \
        CommonSystemLib3 \
    
    PRODUCT_SYSTEM_NAME := oem_system
    PRODUCT_SYSTEM_BRAND := Android
    PRODUCT_SYSTEM_MANUFACTURER := Android
    PRODUCT_SYSTEM_MODEL := oem_system
    PRODUCT_SYSTEM_DEVICE := generic
    
    # For system-as-root devices, system.img should be mounted at /, so we
    # include ROOT here.
    _my_paths := \
     $(TARGET_COPY_OUT_ROOT)/ \
     $(TARGET_COPY_OUT_SYSTEM)/ \
    
    $(call require-artifacts-in-path, $(_my_paths),)
    
  2. device.mk 文件中,继承 system 分区的通用 Makefile,并启用工件路径要求检查。例如:

    $(call inherit-product, $(SRC_TARGET_DIR)/product/oem_system.mk)
    
    # Enable artifact path requirements checking
    PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := strict
    

工件路径要求简介

PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS 设置为 truestrict 时,构建系统会阻止其他 Makefile 中定义的软件包安装到 require-artifacts-in-path 中定义的路径,阻止当前 Makefile 中定义的软件包将工件安装到 require-artifacts-in-path 中定义的路径之外。

在上例中,将 PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS 设置为 strict 后,oem_system.mk 之外的 Makefile 不能包含安装到 rootsystem 分区的模块。如需包含这些模块,您必须在 oem_system.mk 文件或所含的 Makefile 中定义它们。尝试将模块安装到禁止路径会导致构建中断。如需解决中断问题,请执行以下操作之一:

  • 选项 1:在 oem_system.mk 中包含的 Makefile 中包含系统模块。这样一来,便可以满足工件路径要求(因为模块现在包含在所含的 Makefile 中),从而允许安装到“require-artifacts-in-path”中的路径集。

  • 选项 2:将模块安装到 system_extproduct 分区(不将模块安装到 system 分区)。

  • 选项 3:将模块添加到 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST。这样会列出允许安装的模块。

第 2 步:清空允许列表

在此步骤中,请清空 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST,以便共享 oem_system.mk 的所有设备也可以共享一个 system 映像。如需清空允许列表,请将列表中的所有模块移动到 system_extproduct 分区,或将其添加到 system Make 文件。此步骤是可选的,因为启用产品接口强制执行无需定义通用的 system 映像。但是,清空允许列表有助于使用 system_ext 定义 system 边界。

第 3 步:强制执行原生接口

在此步骤中,您将设置 PRODUCT_PRODUCT_VNDK_VERSION := current,然后查找构建和运行时错误并予以解决。如需检查设备启动和日志并查找和修复运行时链接失败,请执行以下操作:

  1. 设置 PRODUCT_PRODUCT_VNDK_VERSION := current

  2. 构建设备并查找构建错误。您可能会看到缺少产品变体或核心变体的一些构建中断。常见的中断包括:

    • 任何具有 product_specific: truehidl_interface 模块都将不适用于系统模块。如需修复此错误,请将 product_specific: true 替换为 system_ext_specific: true
    • 模块可能缺少产品模块所需的产品变体。如需修复此错误,请通过设置 product_available: true 将该模块提供给 product 分区,或通过设置 product_specific: true 将模块移动到 product 分区。
  3. 解决构建错误并确保设备构建成功。

  4. 刷写映像并在设备启动和日志中查找运行时错误。

    • 如果测试用例日志中的 linker 标签显示 CANNOT LINK EXECUTABLE 消息,那么 Make 文件缺少依赖项(并且在构建时不会被捕获)。
    • 如需从构建系统进行检查,请将所需的库添加到 shared_libs:required: 字段。
  5. 按照上述指南解析缺少的依赖项。

第 4 步:强制执行 Java 接口

在此步骤中,您将设置 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true,然后查找并修复导致的构建错误。查找以下两种类型的错误:

  • 链接类型错误。此错误表示应用链接到具有更广泛的 sdk_version 的 Java 模块。如需解决此错误,您可以扩展应用的 sdk_version 或限制库的 sdk_version。错误示例:

    error: frameworks/base/packages/SystemUI/Android.bp:138:1: module "SystemUI" variant "android_common": compiles against system API, but dependency "telephony-common" is compiling against private API.Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.
    
  • 符号错误。此错误表示某个符号位于隐藏 API 中,因此无法找到。如需修复此错误,请使用可见(非隐藏)API 或查找替代项。错误示例:

    frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java:1051: error: cannot find symbol
                ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
                                               ^
      symbol:   class ProxyAuthenticate
      location: class SipSessionGroup.SipSessionImpl
    

第 5 步:检查运行时行为

在此步骤中,您将验证运行时行为是否符合预期。对于可调试的应用,您可以使用 StrictMode.detectNonSdkApiUsage(在应用使用隐藏 API 时生成日志)监控隐藏的 API 使用情况。或者,您也可以使用 veridex 静态分析工具获取使用类型(链接或反射)、限制级别和调用堆栈。

  • veridex 语法:

    ./art/tools/veridex/appcompat.sh --dex-file={apk file}
    
  • veridex 结果示例:

    #1: Linking greylist-max-o Landroid/animation/AnimationHandler;-><init>()V use(s):
           Lcom/android/systemui/pip/phone/PipMotionHelper;-><init>(Landroid/content/Context;Landroid/app/IActivityManager;Landroid/app/IActivityTaskManager;Lcom/android/systemui/pip/phone/PipMenuActivityController;Lcom/android/internal/policy/PipSnapAlgorithm;Lcom/android/systemui/statusbar/FlingAnimationUtils;)V
    
    #1332: Reflection greylist Landroid/app/Activity;->mMainThread use(s):
           Landroidx/core/app/ActivityRecreator;->getMainThreadField()Ljava/lang/reflect/Field;
    

如需详细了解 veridex 使用情况,请参阅使用 veridex 工具进行测试

第 6 步:更新 device.mk

修复所有构建和运行时失败,并验证运行时行为是否符合预期后,请在 device.mk 中设置以下内容:

  • PRODUCT_PRODUCT_VNDK_VERSION := current
  • PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true