链接器命名空间

动态链接器解决了 Treble VNDK 设计中的两个难题:

  • 将 SP-HAL 共享库及其依赖项(包括 VNDK-SP 库)加载到框架进程中。应该有一些防止出现符号冲突的机制。
  • dlopen()android_dlopen_ext() 可能会引入一些在编译时不可见的运行时依赖关系,这些依赖关系使用静态分析很难检测到。

这两个难题可以通过链接器命名空间机制来解决。链接器命名空间机制由动态链接器提供,可以隔离不同链接器命名空间中的共享库,以确保具有相同库名称和不同符号的库不会发生冲突。

另一方面,链接器命名空间机制可提供相应的灵活性,从而将由一个链接器命名空间导出的某些共享库用于另一个链接器命名空间。这些导出的共享库可能会成为对其他程序公开的应用编程接口,同时在其链接器命名空间中隐藏实现细节。

例如,/system/lib[64]/libcutils.so/system/lib[64]/vndk-sp/libutils.so 是两个共享库。这两个库可能有不同的符号。它们将加载到不同的链接器命名空间中,以便框架模块可以依赖于 /system/lib[64]/libcutils.so,SP-HAL 共享库可以依赖于 /system/lib[64]/vndk-sp/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.vendor = /vendor/bin

[system]
additional.namespaces = sphal

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

namespace.sphal.isolated = true
namespace.sphal.visible = true
namespace.sphal.search.paths = /vendor/${LIB}
namespace.sphal.permitted.paths = /vendor/${LIB}
namespace.sphal.links = default
namespace.sphal.link.default.shared_libs = libc.so:libm.so

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

首先,ld.config.txt 的开头有几个 dir.${section} 属性:

dir.${section} = /path/to/bin/directory

这些属性决定了将应用于相应进程的一系列规则。例如,如果主可执行文件位于 /system/bin 中,则会应用 [system] 中的规则。同样,如果主可执行文件位于 /vendor/bin 中,则会应用 [vendor] 中的规则。

其次,除了 default 链接器命名空间外,addition.namespaces 还为每个部分指定了将由动态链接器创建的额外链接器命名空间(用英文逗号分隔):

additional.namespaces = namespace1,namespace2,namespace3

在上述示例中,动态链接器为 /system/bin 中的可执行文件创建了两个链接器命名空间(defaultsphal)。

再次,对于每个链接器命名空间,可以配置以下属性:

namespace.${name}.search.paths = /path1/${LIB}:/path2/${LIB}
namespace.${name}.permitted.paths = /path1:/path2
namespace.${name}.isolated = true|false
namespace.${name}.links = namespace1,namespace2
namespace.${name}.link.${other}.shared_libs = lib1.so:lib2.so
namespace.${name}.visible = true|false

namespace.${name}.search.paths 表示将附加到库名称前面的目录。各目录之间用英文冒号隔开。 ${LIB} 是一个特殊的占位符。如果相应进程正在运行 32 位可执行文件,则 ${LIB} 将被替换为 lib。同样,如果相应进程正在运行 64 位可执行文件,则 ${LIB} 将被替换为 lib64

在上述示例中,如果 /system/bin 中的 64 位可执行文件与 libexample.so 相关联,则动态链接器会首先搜索 /system/lib64/libexample.so。如果找不到 /system/lib64/libexample.so,则动态链接器会搜索 /vendor/lib64/libexample.so

如果 namespace.${name}.isolatedtrue,则动态链接器仅会加载 namespace.${name}.search.paths 中指定目录下的共享库,或 namespace.${name}.permitted.paths 中指定目录下的共享库。

在上述示例中,在 sphal 链接器命名空间中加载的共享库将无法关联到 /system/lib[64] 中的共享库,因为 namespace.sphal.isolatedtrue 并且 /system/lib[64] 既不在 namespace.sphal.permitted.paths 中也不在 namespace.sphal.search.paths 中。

namespace.${name}.links 指定了 ${name} 链接器命名空间关联到的链接器命名空间列表(以英文逗号分隔)。

在上述示例中,namespace.sphal.links 指定 sphal 链接器命名空间关联到 default 链接器命名空间。

namespace.${name}.link.${other}.shared_libs 会关联两个链接器命名空间,并指定可能会利用后备链接的共享库名称(用英文冒号分隔)。如果某个共享库无法加载到 ${name} 链接器命名空间中,并且其名称位于 namespace.${name}.link.${other}.shared_libs 中,则动态链接器会尝试从 ${other} 链接器命名空间导入该库。

在上述示例中,namespace.sphal.link.default.shared_libs 指定 libc.solibm.so 可以由 default 链接器命名空间导出。如果在 sphal 链接器命名空间中加载的共享库关联到 libc.so,并且动态链接器在 /vendor/lib[64] 中找不到 libc.so,则动态链接器会遍历后备链接,并查找由 default 链接器命名空间导出的 libc.so

如果 namespace.${name}.visibletrue,该程序将能够获取链接器命名空间句柄,该句柄随后可传递到 android_dlopen_ext()

在上述示例中,namespace.sphal.visibletrue,以便 android_load_sphal_library() 可以明确要求动态链接器加载 sphal 链接器命名空间中的共享库。

链接器命名空间隔离

android-src/system/core/rootdir/etc 中有三种配置。系统会根据 BoardConfig.mkPRODUCT_FULL_TREBLEBOARD_VNDK_VERSIONBOARD_VNDK_RUNTIME_DISABLE 的值选择不同的配置:

PRODUCT_FULL_TREBLE BOARD_VNDK_VERSION / BOARD_VNDK_RUNTIME_DISABLE 选择的配置
false any ld.config.legacy.txt
true current 和 empty ld.config.txt.in
empty 或 true ld.config.txt

android-src/system/core/rootdir/etc/ld.config.txt 会隔离 SP-HAL 和 VNDK-SP 共享库。在 Android 8.0 及更高版本中,当 PRODUCT_FULL_TREBLEtrue 时,该配置必须是动态链接器配置。

android-src/system/core/rootdir/etc/ld.config.txt.in 也会隔离 SP-HAL 和 VNDK-SP 共享库。此外,ld.config.txt.in 还提供全面的动态链接器隔离。它可确保系统分区中的模块不依赖于供应商分区中的共享库,反之亦然。

在 Android 8.1 中,ld.config.txt.in 是默认配置,并且我们强烈建议启用全面的动态链接器隔离。但是,如果在 Android 8.1 中需要清理的依赖项太多,您可以将 BOARD_VNDK_RUNTIME_DISABLE 添加到 BoardConfig.mk 中:

BOARD_VNDK_RUNTIME_DISABLE := true

如果 BOARD_VNDK_RUNTIME_DISABLEtrue,则会安装 android-src/system/core/rootdir/etc/ld.config.txt

ld.config.txt

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

ld.config.txt 中描绘的链接器命名空间图表
图 2. 链接器命名空间隔离 (ld.config.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.in 中)
    • libsync.so
    • libvndksupport.so
    • libz.so(已移到 ld.config.txt.in 中的 VNDK-SP)
  • VNDK-SP
    • android.hardware.graphics.common@1.0.so
    • android.hardware.graphics.allocator@2.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.txt 中的 [system] 部分):

命名空间 属性
default search.paths /system/${LIB}
/vendor/${LIB}
permitted.paths /system/${LIB}
/vendor/${LIB}
isolated false
sphal search.paths /vendor/${LIB}/egl
/vendor/${LIB}/hw
/vendor/${LIB}
permitted.paths /vendor/${LIB}
/system/${LIB}/vndk-sp/hw (Android 8.1)
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 /vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp
/vendor/${LIB}
permitted.paths /vendor/${LIB}/egl
/vendor/${LIB}/hw
isolated true
visible true
links default
link.default.shared_libs LL-NDK
rs(适用于 Renderscript) search.paths /vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp
/vendor/${LIB}
permitted.paths /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 /vendor/${LIB}
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp
/system/${LIB}(已弃用)
isolated false

更多详情可以在 android-src/system/core/rootdir/etc/ld.config.txt 中找到。

ld.config.txt.in

ld.config.txt.in 会隔离系统分区和供应商分区之间的共享库依赖关系。下文概述了该配置文件与上一小节中提到的 ld.config.txt 相比有哪些不同:

  • 框架进程
    • 系统会隔离 default 命名空间。只有当共享库位于搜索路径中指定的目录下或允许的路径中指定的目录下时,才能将其加载到 default 命名空间中。
    • default 命名空间的允许路径已更改为有限集(/vendor/lib[64]/system/lib[64]/vndk/system/lib[64]/vndk-sp 已被排除)。
  • 供应商进程
    • 系统会创建两个命名空间(defaultsystem)。
    • 系统会隔离 default 命名空间。只有当共享库位于搜索路径中指定的目录下或允许的路径中指定的目录下时,才能将其加载到默认命名空间中。
    • default 命名空间的允许路径是 /vendor/system/lib[64]/vndk/system/lib[64]/vndk-sp
    • default 命名空间与 system 命名空间相关联。default 命名空间可以关联到在 system 命名空间中加载的 LL-NDK 库。

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

ld.config.txt.in 中描绘的链接器命名空间图表
图 2. 链接器命名空间隔离 (ld.config.txt.in)。

在上图中,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
    • libsync.so
    • libvndksupport.so
  • VNDK-SP
    • android.hardware.graphics.common@1.0.so
    • android.hardware.graphics.allocator@2.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.in 中的 [system] 部分):

命名空间 属性
default search.paths /system/${LIB}
permitted.paths /system/${LIB}/drm
/system/${LIB}/hw
/system/framework
/system/app
/system/priv-app
/vendor/app
/vendor/framework
/oem/app
/data
/mnt/expand
isolated true
sphal search.paths /vendor/${LIB}/egl
/vendor/${LIB}/hw
/vendor/${LIB}
permitted.paths /vendor/${LIB}
/system/${LIB}/vndk-sp/hw
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 /vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp
permitted.paths /vendor/${LIB}/egl
/vendor/${LIB}/hw
isolated true
visible true
links default
link.default.shared_libs LL-NDK
rs(适用于 Renderscript) search.paths /vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp
/vendor/${LIB}
permitted.paths /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.in 中的 [vendor] 部分):

命名空间 属性
default search.paths /vendor/${LIB}/hw
/vendor/${LIB}/egl
/vendor/${LIB}
/vendor/${LIB}/vndk
/system/${LIB}/vndk
/vendor/${LIB}/vndk-sp
/system/${LIB}/vndk-sp
permitted.paths /vendor
/system/${LIB}/vndk
/system/${LIB}/vndk-sp
isolated true
links system
link.system.shared_libs LL-NDK
system search.paths /system/${LIB}
permitted.paths /system/${LIB}

更多详情可以在 android-src/system/core/rootdir/etc/ld.config.txt.in 中找到。