链接器命名空间

动态链接器解决了 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 个命名空间(defaultsphalvndk)。

namespace.name.links

回退命名空间的英文逗号分隔列表。

如果在当前命名空间中找不到共享库,动态链接器会尝试从回退命名空间加载共享库。在列表开头指定的命名空间优先级较高。

namespace.sphal.links = default,vndk

如果某个共享库或可执行文件请求另一个共享库,而后者无法加载到 sphal 命名空间,动态链接器就会尝试从 default 命名空间加载此共享库。

然后,如果此共享库也无法从 default 命名空间加载,动态链接器就会尝试从 vndk 命名空间加载此共享库。

最后,如果所有尝试都失败,动态链接器就会返回一个错误。

namespace.name.link.other.shared_libs

用英文冒号分隔的共享库列表(如果在 name 命名空间中找不到这些共享库,可以在 other 命名空间中搜索)。

此属性无法与 namespace.name.link.other.allow_all_shared_libs 一起使用。

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

这表示回退链接仅接受 libc.solibm.so 作为请求的库名称。如果请求的库名称既不是 libc.so 也不是 libm.so,动态链接器会忽略从 sphaldefault 命名空间的回退链接。

namespace.name.link.other.allow_all_shared_libs

一个布尔值,用于指示当无法在 name 命名空间中找到共享库时,能否在 other 命名空间中搜索到所有这些共享库。

此属性无法与 namespace.name.link.other.shared_libs 一起使用。

namespace.vndk.link.sphal.allow_all_shared_libs = true

这表示所有库名称都可以遍历从 vndksphal 命名空间的回退链接。

命名空间属性

属性 说明 示例
namespace.name.isolated

一个布尔值,用于指示动态链接器是否应该检查共享库在什么位置。

如果 isolatedtrue,系统只能加载某个 search.paths 目录(不包含子目录)中的共享库或某个 permitted.paths 目录(包含子目录)下的共享库。

如果 isolatedfalse(默认值),动态链接器不会检查共享库的路径。

namespace.sphal.isolated = true

这表示只有 search.paths 中或 permitted.paths 下的共享库才能加载到 sphal 命名空间。

namespace.name.search.paths

以英文冒号分隔的目录列表,用于搜索共享库。

如果函数调用 dlopen()DT_NEEDED 条目时未指定完整路径,在 search.paths 中指定的目录将附加到请求的库名称前面。在列表开头指定的目录优先级较高。

如果 isolatedtrue,系统可加载某个 search.paths 目录(不包含子目录)中的共享库(无论 permitted.paths 为何属性)。

例如,如果 search.paths/system/${LIB}permitted.paths 为空,可以加载 /system/${LIB}/libc.so,但无法加载 /system/${LIB}/vndk/libutils.so

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

这表示动态链接器会在 /system/${LIB} 中搜索共享库。

namespace.name.asan.search.paths

以英文冒号分隔的目录列表,用于在启用 AddressSanitizer (ASan) 后搜索共享库。

在启用 ASan 后,系统会忽略 namespace.name.search.paths

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

这表示在启用 ASan 后,动态链接器会先搜索 /data/asan/system/${LIB},然后再搜索 /system/${LIB}

namespace.name.permitted.paths

以英文冒号分隔的目录列表(包含子目录),当 isolatedtrue 时,动态链接器可在其中加载共享库(以及 search.paths)。

permitted.paths 的子目录下的共享库也可以加载。例如,如果 permitted.paths/system/${LIB}/system/${LIB}/libc.so/system/${LIB}/vndk/libutils.so 均可加载。

如果 isolatedfalse,系统会忽略 permitted.paths 并发出相应警告。

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

这表示 /system/${LIB}/hw 下的共享库可以加载到隔离的 default 命名空间。

例如,如果没有 permitted.pathslibaudiohal.so 就无法将 /system/${LIB}/hw/audio.a2dp.default.so 加载到 default 命名空间。

namespace.name.asan.permitted.paths

以英文冒号分隔的目录列表,在启用 ASan 后,动态链接器可在其中加载共享库。

在启用 ASan 后,系统会忽略 namespace.name.permitted.paths

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

这表示在启用 ASan 后,/data/asan/system/${LIB}/hw/system/${LIB}/hw 下的共享库可以加载到隔离的 default 命名空间。

namespace.name.visible

一个布尔值,用于指示程序(不包括 libc)能否包含带有 android_get_exported_namespace() 的链接器命名空间句柄,以及能否通过将此句柄传递到 android_dlopen_ext() 来打开链接器命名空间中的共享库。

如果 visibletrueandroid_get_exported_namespace() 在命名空间存在时始终返回此句柄。

如果 visiblefalse(默认值),无论命名空间是否存在,android_get_exported_namespace() 始终返回 NULL。仅当符合以下条件时,共享库才能加载到此命名空间:(1) 具有指向此命名空间的回退链接的其他链接器命名空间请求这些共享库,或者 (2) 此命名空间中的其他共享库或可执行文件请求这些共享库。

namespace.sphal.visible = true

这表示 android_get_exported_namespace("sphal") 可以返回有效的链接器命名空间句柄。

链接器命名空间创建

在 Android 11 中,系统会在运行时在 /linkerconfig 下创建链接器配置,而不是在 ${android-src}/system/core/rootdir/etc 中使用纯文本文件来创建链接器配置。配置是在启动时根据运行时环境生成的,其中包括以下内容:

  • 设备是否支持 VNDK
  • vendor 分区的目标 VNDK 版本
  • product 分区的 VNDK 版本
  • 已安装的 APEX 模块

链接器配置是通过解析链接器命名空间之间的依赖关系来创建的。例如,如果 APEX 模块上有包括依赖项更新在内的任何更新,就会生成链接器配置,以反映这些变化。如需详细了解如何创建链接器配置,请参阅 ${android-src}/system/linkerconfig

链接器命名空间隔离

有 3 类配置文件。系统会根据 BoardConfig.mkPRODUCT_TREBLE_LINKER_NAMESPACESBOARD_VNDK_VERSION 的值在启动时生成相应的配置。

PRODUCT_TREBLE_
LINKER_NAMESPACES
BOARD_VNDK_
VERSION
选择的配置 VTS 要求
true current VNDK 搭载 Android 9 或更高版本的设备的必要配置
空白 VNDK Lite 搭载 Android 8.x 的设备的必要配置
false 空白 Legacy 适用于不支持 Treble 的设备

VNDK Lite 配置会隔离 SP-HAL 和 VNDK-SP 共享库。在 Android 8.0 中,当 PRODUCT_TREBLE_LINKER_NAMESPACEStrue 时,该配置必须是动态链接器的配置文件。

VNDK 配置还会隔离 SP-HAL 和 VNDK-SP 共享库。此外,此配置还会提供全面的动态链接器隔离。它可确保 system 分区中的模块不依赖于 vendor 分区中的共享库,反之亦然。

在 Android 8.1 或更高版本中,VNDK 配置是默认配置,强烈建议您将 BOARD_VNDK_VERSION 设置为 current,以启用全面的动态链接器隔离。

VNDK 配置

VNDK 配置会隔离 system 分区和 vendor 分区之间的共享库依赖项。下文概述了该配置文件与上一子部分中提到的配置相比有哪些不同:

  • 框架进程

    • 创建了 defaultvndksphalrs 命名空间。
    • 系统会隔离所有命名空间。
    • 将系统共享库加载到 default 命名空间中。
    • 将 SP-HAL 加载到 sphal 命名空间中。
    • 将 VNDK-SP 共享库加载到 vndk 命名空间中。
  • 供应商进程

    • 创建了 defaultvndksystem 命名空间。
    • 隔离 default 命名空间。
    • 将供应商共享库加载到 default 命名空间中。
    • 将 VNDK 和 VNDK-SP 共享库加载到 vndk 命名空间中。
    • 将 LL-NDK 及其依赖项加载到 system 命名空间中。

链接器命名空间之间的关系如下所示:

VNDK 配置中描绘的链接器命名空间图
图 1. 链接器命名空间隔离(VNDK 配置)

在上图中,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

您可以查看设备的 /linkerconfig/ld.config.txt,了解更多详情。

VNDK Lite 配置

从 Android 8.0 开始,动态链接器将配置为隔离 SP-HAL 和 VNDK-SP 共享库,以使其符号不会与其他框架共享库发生冲突。链接器命名空间之间的关系如下所示。

VNDK Lite 配置中描绘的链接器命名空间图
图 2. 链接器命名空间隔离(VNDK Lite 配置)

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(不在配置中)
    • libsync.so
    • libvndksupport.so
    • libz.so(已移到配置中的 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

下表列出了框架进程的命名空间配置(摘自 VNDK Lite 配置中的 [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

下表列出了供应商进程的命名空间配置(摘自 VNDK Lite 配置中的 [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

如需了解更多详情,请参阅 /linkerconfig/ld.config.txt

文档历史记录

Android 11 变更

  • 在 Android 11 中,静态 ld.config.*.txt 文件会从代码库中移除,而 LinkerConfig 会在运行时生成它们。

Android 9 变更

  • 在 Android 9 中,vndk 链接器命名空间已添加到供应商进程,而且 VNDK 共享库已与默认链接器命名空间隔离开。
  • PRODUCT_FULL_TREBLE 替换为更具体的 PRODUCT_TREBLE_LINKER_NAMESPACES
  • Android 9 更改了以下动态链接器配置文件的名称。
    Android 8.x Android 9 说明
    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
  • 添加了 productodm 分区。