一、VNDK概述
VNDK(Vendor Native Development Kit)是一组专门用于vendor实现其HAL的lib库。因为自Android 8.0以来,Google引入了Treble架构,希望对vendor和system分区进行解耦处理,期待实现:framwork进程不加载vendor共享库,vendor进程仅加载vendor共享库(和部分framework共享库),而framework进程和vendor进程之间通过HIDL和hwbinder来通信。
1.1 vendor进程可访问的部分framework共享库
根据特性及使用方法的差异,可将framework共享库大致分为三类:
(1)已知API/ABI稳定的framework共享库 - 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。
对于这些库,在Android.bp都包含llndk_library模块的定义,该定义指定了模块名称和符号文件:
1 | llndk_library { |
编译系统会根据符号文件为vendor模块生成stub共享库,需满足条件:未在以_PRIVATE或_PLATFORM结尾的部分中定义,不含#platform-only标记,并且不含#introduce*标记或者该标记与目标匹配。
1 | #libvndksupport.map.txt |
(2)可以安全复制两次的framework共享库 - Eligible-VNDK,需要满足以下几个条件:
a.不与framework之间进行IPC通信。
b.与ART虚拟机无关。
c.不读写文件格式不稳定的文件或分区。
d.没有特殊的软件许可。
e.code所有者不反对vendor使用。
(3)除上述两种以外的framework共享库 - FWK-Only,通常具有以下几个特点:
a.被视为framework内部实现。
b.不得由vendor访问。
c.不稳定的ABI/API。
d.不会被复制。
1.2 SP-HAL(Same-Pocess HAL)
SP-HAL是一组预先确定的HAL,被加载到framework进程中的vendor共享库。它由链接器命名空间进行隔离,必须仅依赖于LL-NDK和VNDK-SP(部分预定义的Eligible-VNDK库)。无论是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。
SP-HAL不可见的VNDK-SP有:libRSCpuRef.so (RS)、libRSDriver.so (RS)、libbacktrace.so、libblas.so (RS)、libbcinfo.so (RS)、liblzma.so、libunwind.so。
包含渲染脚本(Rendor script)的FWK-ONLY库:libft2.so、libmediandk.so。
1.3 VNDK安全策略
framework进程对应于sepolicy中的coremain,而vendor进程对应于non-coredomain。现将共享库权限整理如下:
类别 | coredomain访问 | 非coredomain访问 | 分区位置 |
---|---|---|---|
LL-NDK | Y | Y | system/lib[64] |
LL-NDK-Private | Y | Y | system/lib[64] |
VNDK-SP/VNDK-SP-Private | Y | Y | system/lib[64] |
VNDK-SP-Ext | Y | Y | vendor/lib[64]/vndk-sp |
VNDK | Y | Y | system/lib[64] |
VNDK-Ext | N | Y | vendor/lib[64] |
FWK-ONLY | Y | N | system/lib[64] |
FWK-ONLY-RS | Y | N | system/lib[64] |
SP-HAL | Y | Y | vendor/lib[64]或vendor/lib[64]/hw |
SP-HAL-Dep | Y | Y | vendor/lib[64]vendor/lib[64]/hw |
VND-ONLY | N | Y | vendor/lib[64] |
为了确保vendor分区中VNDK-SP-Ext、SP-HAL、SP-HAL-Dep库既可以从coredomain访问,也可以从非coredomain访问,需要用same_process_hal_file标签来标记,如file_contexts中所定义:
1 | /vendor/lib(64)?/hw/libMySpHal\.so u:object_r:same_process_hal_file:s0 |
二、VNDK编译
2.1 VNDK使能
针对Android 9.0新设备,会强制ro.vndk.version属性非空。该值用于说明VNDK共享库版本号,存在/vendor/default.prop中。若在makefile中定义的BOARD_VNDK_VERSION不等于current,则将BOARD_VNDK_VERSION定义的值赋给ro.vndk.version。若不相等,则需要先看makefile中PLATFORM_VERSION_CODENAME是否为REL,如果是则采用PLATFORM_SDK_VERSION的值,否则使用PLATFORM_VERSION_CODENAME对应的内容。
若想从Android 8.0之前版本通过OTA升级至Android 9.0,需要在BoardConfig.mk中加入以下内容:
PRODUCT_TREBLE_LINKER_NAMESPACES_OVERRIDE := false
若想从禁用了VNDK运行时增强功能的Android 8.0以后版本通过OTA升级至Android 9.0,需将PRODUCT_USE_VNDK_OVERRIDE := false添加至BoardConfig.mk中,在编译时ro.vndk.lite属性会被设为tru,并会自动添加被添加到/vendor/default.prop文件中。动态链接器将加载/system/etc/ld.config.vndk_lite.txt中的链接器命名空间配置,以隔离SP-HAL和VNDK。
对于first_pai_product属性大于27的版本,不能定义ro.vndk.lite属性,比如Android 9.0的VTS测试结果中将会出现VtsTreblePlatformVersionTest Fail项。
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中。
2.2 VNDK编译配置
编译系统包含多种类型的对象,其中包括库(共享、静态或标头)和二进制文件:
core:位于system.img中,由system使用。vendor、vendor_available、vndk或vndk-sp库不能使用此类库。
1 | cc_binary { |
vendor-only(proprietary):位于vendor.img中,由vendor使用。
1 | cc_binary { |
vendor_available:位于vendor.img中,由vendor使用(可能包含core的重复项)。
1 | cc_binary { |
vndk:位于system.img中,由vendor使用(vendor_available的子集)。
1 | cc_binary { |
vndk-sp:位于system.img中,由system间接使用(core的子集)。
1 | cc_binary { |
llndk:同时由system和vendor使用。
1 | cc_binary { |
库的vendor版本在bp文件中用-D__ANDROID_VNDK__来标记编译。对于vendor模块,在Android.bp中必须将vendor或proprietary设为true,而在Android.mk中需将LOCAL_VENDOR_MODULE或LOCAL_PROPRIETARY_MODULE设为true。将当被标记为vendor_available:true时,该库将编译2次,1次为平台编译,被安装到/system/lib[64]中;1次为vendor编译,被安装到/vendor/lib[64]、/system/lib[64]/vndk或/system/lib[64]/vndk-sp中)。vendor模块可以设置vendor_available,但不得设置 vndk.enabled和vndk.support_system_proces,因为它们由Google定义,即GSI中不包含这些vendor模块。现将三种属性整理如下表:
vendor_available | vndk enabled | vndk support_same_process | 说明 |
---|---|---|---|
true | false | false | 供应商变体为VND-ONLY。共享库将安装到/vendor/lib[64]中。 |
true | false | true | 无效(编译错误) |
true | true | false | 供应商变体为VNDK。共享库将安装到/system/lib[64]/vndk-${VER}中。 |
true | true | true | 供应商变体为VNDK-SP。共享库将安装到/system/lib[64]/vndk-sp-${VER}中。 |
false | false | false | 没有供应商变体。此模块为FWK-ONLY。 |
false | false | true | 无效(编译错误) |
fasle | true | false | 供应商变体为VNDK-Private。共享库将安装到/system/lib[64]/vndk-${VER}中。供应商模块不得直接使用这些变体。 |
false | false | true | 供应商变体为VNDK-SP-Private。共享库将安装到/system/lib[64]/vndk-sp-${VER}中。供应商模块不得直接使用这些变体。 |
如果启用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。
若某个模块依赖于以上目录中的headers,则必须在Android.bp中指定与header_libs、static_libs和shared_libs的依赖关系。或在Android.mk中的中指定LOCAL_HEADER_LIBRARIES、LOCAL_STATIC_LIBRARIES和LOCAL_SHARED_LIBRARIES,否则编译检查时会报错。
2.3 VNDK Definition工具
VNDK Definition tool可以帮助vendor将源码树移植到Android 8.0环境。该工具会先扫描system.img和vendor.img中二进制文件,然后解析依赖项。还可以指定system.img与GSI进行对比,以确定扩展后的lib库。该工具代码路径为:$AOSP/development/vndk/tools/definition-tool/vndk_definition_tool.py。该脚本支持以下参数:
1 | $ python3 ./vndk_definition_tool.py vndk \ |
1 | $ python3 ./vndk_definition_tool.py check-dep \ |
1 | $ python3 ./vndk_definition_tool.py deps \ |
现将脚本支持的参数说明整理如下表:
第1个参数 | 说明 |
---|---|
vndk | 计算VNDK库集 |
deps | 输出二进制文件依赖信息用于debug |
check-dep | 检查eligible共享库依赖 |
第2个参数 | 说明 |
---|---|
–system | 开发项目编译生成的system.img路径。 |
–vendor | 开发项目编译生成的vendor.img路径。 |
–aosp-system | AOSP的system.img,即由Google释放的用于VTS测试的GSI文件。 |
–tag-file | 由Google释放的对framework共享库进行了分类的表格 |
–output-format | 自动生成Android.mk(适用于vndk ) |
–load-extra-deps | 加载外部模块依赖文件(vndk 不适用) |
–revert | 输出依赖详细情况(适用于deps ) |
–module-info | 开发项目编译生成的module-info.json。(适用于check-dep ) |
dlopen.dep的内容范例如下:
1 | #libart.so依赖于libart-compier.so |
check_dep_err.txt的内容范例如下:
1 | #libRS_internal.so对libmediandk.so的违规依赖 |
deps.txt的内容范例如下:
1 | #ld-android.so没有依赖 |
各类库对应目录:
vndk-sp – /system/lib[64]/vndk-sp。
vndk-sp-ext – /vendor/lib[64]/vndk-sp。
extra-vendor-libs – /vendor/lib[64]
在device/$(VENDOR)/$(DEVICE_NAME)目录下创建vndk文件夹,用于存放配置vndk库的Android.mk,其范例如下:
1 | ifneq ($(filter $(DEVICE_NAME),$(TARGET_DEVICE)),) |
然后在项目的makefile中添加下面一条内容,使得vndk/Android.mk参与编译:
1 | PRODUCT_PACKAGES += $(DEVICE_NAME)-vndk |
1 | #查看image类型 |
三、VNDK延伸
3.1 VNDK扩展
根据模块中定义的功能,可将模块分为DA模块和DX模块:
(1)Defining-only-AOSP模块(DA 模块)不会定义AOSP副本中未包含的新功能。
a.一个未经修改的AOSP库就是一个DA模块。
b.如果vendor使用SIMD指令重写libexample.so中的函数(不添加新函数),那么修改后的libexample.so将是一个DA模块。
(2)Defining-Extension模块(DX模块)要么会定义新功能,要么没有AOSP副本。
a.如果vendor向 libexample.so 添加一个test函数以访问某些内部数据,那么修改后的libexample.so将是一个DX库,而这个新增函数将是该库的扩展部分。
b.如果vendor定义了一个名为libexample.so的非AOSP库,那么libexmple.so将是一个DX库。
根据模块所使用的功能,可将模块分为UA模块和UX模块。
(1)Using-only-AOSP(UA模块)仅会在其实现过程中使用AOSP功能。它们不依赖任何非AOSP扩展功能。
a.一个未经修改且完整无缺的AOSP库即是一个UA模块。
b.如果修改后的共享库libexample.so仅依赖于其他AOSP API,那么它将是一个UA模块。
(2)Using-Extension 模块(UX模块)会在其实现过程中依赖某些非 AOSP 功能。
a.如果修改后的libexample.so依赖另一个名为libexample2.so的非AOSP库,那么修改后的libexample.so将是一个UX模块。
b.如果vendor修改后的libexample.so1添加了一个新函数,并且其修改后的libexample2.so使用libexample1.so中的这个新增函数,那么修改后的libexample2.so将是一个UX模块。
3.2 链接器命名空间
链接器命名空间机制由动态链接器提供,可以隔离不同链接器命名空间中的共享库,以确保具有相同库名称和不同符号的库不会发生冲突。链接器命名空间机制可提供相应的灵活性,从而将由一个链接器命名空间导出的某些共享库用于另一个链接器命名空间。这些导出的共享库可能会成为对其他程序公开的应用编程接口,同时在其链接器命名空间中隐藏实现细节。
动态链接器负责加载DT_NEEDED条目中指定的共享库,由dlopen()或android_dlopen_ext()的参数指定的共享库。在这两种情况下,动态链接器都会找出调用程序所在的链接器命名空间,并尝试将相关依赖项加载到同一个链接器命名空间中。如果动态链接器无法将共享库加载到指定的链接器命名空间中,它会向关联的链接器命名空间索取导出的共享库。
ini配置文件范例如下:
1 | dir.system = /system/bin |
各字段含义整理如下表:
属性 | 说明 | 示例 |
---|---|---|
dir.name | 指向[name]区段所应用到的目录的路径。每个属性都会将目录下的可执行文件映射到链接器命名空间配置区段。可能会有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个命名空间(default、sphal和vndk)。 |
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.so或libm.so作为请求的库名称。如果请求的库名称不是libc.so,也不是libm.so,则动态链接器会忽略从sphal到default命名空间的回退链接。 |
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这表示所有库名称都可以遍历从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属性如何设置。 | 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的子目录下的共享库也可以加载。如果isolated为false,则系统会忽略 permitted.paths 并发出相应警告。 | namespace.default.permitted.paths=/system/${LIB}/hw这表示/system/${LIB}/hw下的共享库可以加载到隔离的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_NAMESPACES、BOARD_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 | current | true | ld.config.vndk_lite.txt | 搭载Android8.x的设备的必要配置。 |
true | empty | any | ld.config.vndk_lite.txt | 搭载Android 8.x的设备的必要配置。 |
false | any | any | ld.config.legacy.txt | 适用于不支持Treble的设备 |
${AOSP}/system/core/rootdir/etc/ld.config.vndk_lite.txt会隔离SP-HAL和VNDK-SP共享库。在Android 8.0及更高版本中,当PRODUCT_TREBLE_LINKER_NAMESPACES为true时,该配置必须是动态链接器的配置文件。
${AOSP}/system/core/rootdir/etc/ld.config.txt也会隔离SP-HAL和VNDK-SP共享库。此外,ld.config.txt还会提供全面的动态链接器隔离。它可确保系统分区中的模块不依赖于供应商分区中的共享库,反之亦然。
在Android8.1中,ld.config.txt是默认配置文件,强烈建议您启用全面的动态链接器隔离。但是,如果在Android 8.1中需要清理的依赖项太多,您可以将BOARD_VNDK_RUNTIME_DISABLE添加到BoardConfig.mk中。如果BOARD_VNDK_RUNTIME_DISABLE为true,则会安装 ${AOSP}/system/core/rootdir/etc/ld.config.vndk_lite.txt。
ld.config.txt会隔离系统分区和供应商分区之间的共享库依赖项。下文概述了该配置文件与上一小节中提到的ld.config.txt相比有哪些不同:
framework进程:
a.创建了四个命名空间(default、vndk、sphal和rs)。
b.系统会隔离所有命名空间。
c.将系统共享库加载到default命名空间中。
d.将SP-HAL加载到sphal命名空间中。
e.将VNDK-SP共享库加载到vndk命名空间中。
vendor进程
a.创建了三个命名空间(default、vndk和system)。
b.系统会隔离default命名空间。
c.将供应商共享库加载到default命名空间中。
d.将VNDK和VNDK-SP共享库加载到vndk命名空间中。
e.将LL-NDK及其依赖项加载到system命名空间中。
链接器命名空间之间的关系如下图所示:
在上图中,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} |
default | 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, /oem/app, /odm/priv-app, /oem/app, /product/framework, /product/app, /product/priv-app, /data, /mnt/expand |
default | isolated | true |
sphal | search.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | permitted.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | isolated | true |
sphal | visible | true |
links | default,vndk,rs | |
links | link.default.shared_libs | LL-NDK |
links | link.vndk.shared_libs | VNDK-SP |
links | 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} |
vndk(适用于 VNDK-SP) | permitted.paths | /odm/${LIB}/hw, /odm/${LIB}/egl, /vendor/${LIB}/hw, /vendor/${LIB}/egl, /system/${LIB}/vndk-sp-${VER}/hw |
vndk(适用于 VNDK-SP) | isolated | true |
vndk(适用于 VNDK-SP) | visible | true |
vndk(适用于 VNDK-SP) | links | default、sphal |
vndk(适用于 VNDK-SP) | link.default.shared_libs | LL-NDK |
vndk(适用于 VNDK-SP) | 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} |
rs(适用于 Renderscript) | permitted.paths | /odm/${LIB}, /vendor/${LIB}, /data(适用于已编译的 RS 内核) |
rs(适用于 Renderscript) | isolated | true |
rs(适用于 Renderscript) | visible | true |
rs(适用于 Renderscript) | links | default,vndk |
rs(适用于 Renderscript) | link.default.shared_libs | LL-NDK, libmediandk.so, libft2.so |
rs(适用于 Renderscript) | link.vndk.shared_libs | VNDK-SP |
下表列出了供应商进程的命名空间配置(摘自ld.config.txt中的[vendor]部分):
命名空间 | 属性 | 值 |
---|---|---|
default | search.paths | /odm/${LIB}, /vendor/${LIB} |
default | permitted.paths | /odm, /vendor |
default | isolated | true |
default | visible | true |
default | links | system、vndk |
default | link.system.shared_libs | LL-NDK |
default | link.vndk.shared_libs | VNDK、VNDK-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} |
vndk | isolated | true |
vndk | links | system、default |
vndk | link.system.shared_libs | LL-NDK |
vndk | link.default.allow_all_shared_libs | true |
system | search.paths | /system/${LIB} |
system | isolated | false |
从 Android 8.0 开始,动态链接器将配置为隔离 SP-HAL 和 VNDK-SP 共享库,以使其符号不会与其他框架共享库发生冲突。链接器命名空间之间的关系如下所示:
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} |
default | isolated | false |
sphal | search.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | permitted.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | isolated | true |
sphal | visible | true |
sphal | links | default,vndk,rs |
sphal | link.default.shared_libs | LL-NDK |
sphal | link.vndk.shared_libs | VNDK-SP |
sphal | 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} |
vndk(适用于 VNDK-SP) | permitted.paths | /odm/${LIB}/hw, /odm/${LIB}/egl, /vendor/${LIB}/hw, /vendor/${LIB}/egl, /system/${LIB}/vndk-sp-${VER}/hw |
vndk(适用于 VNDK-SP) | solated | true |
vndk(适用于 VNDK-SP) | visible | true |
vndk(适用于 VNDK-SP) | links | default |
vndk(适用于 VNDK-SP) | 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} |
rs(适用于 Renderscript) | permitted.paths | /odm/${LIB}, /vendor/${LIB}, /data(适用于已编译的 RS 内核) |
rs(适用于 Renderscript) | isolated, | true |
rs(适用于 Renderscript) | visible | true |
rs(适用于 Renderscript) | links | default,vndk |
rs(适用于 Renderscript) | link.default.shared_libs | LL-NDK, libmediandk.so, libft2.so |
rs(适用于 Renderscript) | link.vndk.shared_libs | VNDK-SP |
下表列出了vendor进程的命名空间配置(摘自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}(已弃用) |
default | isolated | false |
3.3 VNDK快照
VNDK快照是一组适用于Android版本的VNDK-core和VNDK-SP库。如果system.img包含vendor.img所需的相应VNDK快照,那么只能升级system分区。VNDK快照设计包括两项操作:(1)从当前系统映像生成预编译的VNDK快照;(2)将这些预编译的库安装到更高Android版本的system分区。
正式的VNDK快照是在Android编译服务器上自动编译而成,并包含在${AOSP}/prebuilts/vndk中。此外,也支持本地编译VNDK快照。
1 | #编译特定project |
VNDK快照包含以下文件:
a.VNDK-core和VNDK-SP共享库的vendor变体。
i.无需LL-NDK 共享库,因为这类库是向后兼容的。
ii.对于64位目标,TARGET_ARCH和TARGET_2ND_ARCH库都将被编译并包含在内。
b.VNDK-core、VNDK-SP、LL-NDK和VNDK-private库的列表位于[vndkcore|vndksp|llndk|vndkprivate].libraries.txt。
c.链接器配置文件为ld.config.txt。
d.许可文件。
e.module_paths.txt。记录所有VNDK库的模块路径;检查GPL项目是否已在指定Android源代码树中发布源代码时,需要用到这种文件。
对于指定VNDK快照ZIP文件android-vndk-{TARGET_ARCH}.zip,系统会根据ABI位数将VNDK预编译库分组到名为arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT} 的单独目录中。
VNDK 快照目录结构:
development/vndk/snapshot/update.py脚本可自动将预编译的VNDK快照添加到源代码树中。此脚本将执行以下任务:
a.在/prebuilts/vndk/v
b.获取VNDK快照编译软件工件并将其解压缩。
c.运行gen_buildfiles.py以自动生成编译文件(Android.mk、Android.bp)。
d.运行check_gpl_license.py以验证根据通用公共许可证(GPL)获得许可的预编译库是否在当前源代码树中发布了源代码。
e.使用git commit提交新的更改。
在指定–local选项的情况下,update.py会从本地$DIST_DIR(而非Android编译服务器中)提取VNDK快照编译工件。但由于本地模式仅用于测试,因此该脚本将跳过 GPL 许可检查和 git commit 步骤。
1 | python update.py <VER> --local |
prebuilts/vndk 的目录结构:
system.img在编译时使用BOARD_VNDK_VERSION、PRODUCT_EXTRA_VNDK_VERSIONS和ro.vndk.version中的信息安装VNDK快照库。可以使用以下选项之一控制从/prebuilts/vndk/v
a.BOARD_VNDK_VERSION。使用快照模块编译当前vendor模块,并仅安装vendor模块所需的快照模块。
b.PRODUCT_EXTRA_VNDK_VERSIONS。无论当前vendor模块有哪些,都安装VNDK快照模块。这将安装PRODUCT_EXTRA_VNDK_VERSIONS中列出的预编译VNDK快照,而不会在编译时将其与任何其他模块相关联。
BOARD_VNDK_VERSION显示的是当前vendor模块需要编译的VNDK版本。如果BOARD_VNDK_VERSION在/prebuilts/vndk目录中有可用的 VNDK快照版本,则系统会安装BOARD_VNDK_VERSION中指明的VNDK快照。如果目录中的VNDK快照不可用,则会出现编译错误。定义 BOARD_VNDK_VERSION也会启用要安装的VNDK模块。vendor模块会在编译时与BOARD_VNDK_VERSION中定义的VNDK快照版本相关联(此操作不会在system源代码中编译当前的VNDK模块)。从代码库中下载完整的源代码树时,system源代码和vendor源代码均基于相同的Android版本。
PRODUCT_EXTRA_VNDK_VERSIONS列出了要安装的其他VNDK版本。正常情况下,当前的vendor分区只需一个VNDK快照就足够了。不过,在某些情况下,您可能需要在一个system.img中提供多个快照。例如,常规系统映像(GSI)具有多个快照,以通过一个system.img支持多个vendor版本。设置PRODUCT_EXTRA_VNDK_VERSIONS后,除了BOARD_VNDK_VERSION中的VNDK版本之外,您还可以安装VNDK快照模块。如果 PRODUCT_EXTRA_VNDK_VERSIONS具有特定的版本列表,则编译系统会在prebuilts/vndk目录中查找版本列表的预编译快照。如果编译系统找到所有列出的快照,便会将这些快照文件安装到每个out/target/product/
VNDK模块将不会在编译时与vendor模块相关联,但在运行时可能会使用该模块(如果vendor分区中的vendor模块需要某个已安装的VNDK版本)。PRODUCT_EXTRA_VNDK_VERSIONS仅在指定了BOARD_VNDK_VERSION的情况下才有效。例如,若要将O MR1 VNDK快照安装到system.img中,需要运行以下命令:
1 | m -j PRODUCT_EXTRA_VNDK_VERSIONS=27 |
PLATFORM_VNDK_VERSION在系统源代码中指定了当前VNDK模块的VNDK版本。系统会通过以下方式自动设置该值:
a.在版本发布之前,将PLATFORM_VNDK_VERSION设置为PLATFORM_VERSION_CODENAME。
b.在发布时,将PLATFORM_SDK_VERSION复制到PLATFORM_VNDK_VERSION中。
c.发布Android版本后,当前的VNDK库会被安装到/system/lib[64]/vndk-$SDK_VER和/system/lib[64]/vndk-sp-$SDK_VER,其中$SDK_VER是存储在PLATFORM_VNDK_VERSION中的版本。
vendor模块使用/etc/ld.config.${VER}.txt(其中${VER}是从ro.vndk.version属性中获得的)中的命名空间配置来搜索所需的共享库。命名空间配置中包含带有版本编号的VNDK目录,该目录使用以下语法:
1 | #%VNDK_VER% 在编译时会被替换为PLATFORM_VNDK_VERSION,这样一来,system.img便能够为每个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版本设置。
供应商版本 | 开发板版本 | SDK版本 | 平台版本 | 版本属性 | 安装目录 |
---|---|---|---|---|---|
当前的VNDK模块 | current | 之前<CODE_NAME> | <CODE_NAME> | /system/lib[64]/vndk[-sp]-<CODE_NAME> | |
当前的VNDK模块 | current | 之后 <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> |
a.开发板版本(BOARD_VNDK_VERSION):vendor模块需要编译的VNDK版本。如果vendor模块可与当前系统模块相关联,则将其设置为 current。
b.平台版本(PLATFORM_VNDK_VERSION):当前系统模块正在编译的VNDK版本(仅在BOARD_VNDK_VERSION为当前版本时编译)。
c.版本属性(ro.vndk.version):一种属性,用于指定vendor.img 中的二进制文件和库需要运行的VNDK版本。该属性存储在/vendor/default.prop下的vendor.img中。
3.4 ABI稳定
vendor模块可能依赖于system分区中的VNDK共享库。新编译的VNDK共享库必须与之前发布的VNDK共享库保持ABI兼容性,以便vendor模块可以与这些库协同工作,而无需重新编译,也不会出现运行时错误。为了确保实现ABI兼容性,Android 9.0中添加了一个ABI header检查工具,接下来会对该工具进行简单介绍。
导出的符号(全局符号)是指满足以下所有条件的符号:
a.通过共享库的公开标头导出。
b.显示在与共享库对应的.so文件的.dynsym表中。
c.具有WEAK或GLOBAL绑定。
d.可见性为DEFAULT或PROTECTED。
e.区块索引不是UNDEFINED。
f.类型为FUNC或OBJECT。
g.共享库的公开头文件是指通过以下属性提供给其他库/二进制文件使用的头文件:export_include_dirs、export_header_lib_headers、export_static_lib_headers、export_shared_lib_headers和 export_generated_headers属性(位于与共享库对应的模块的 Android.bp 定义中)。
可到达类型是指可通过导出的符号直接或间接到达并且是通过公开header导出的任何C/C++内置类型或用户定义的类型。如libfoo.so具有函数Foo,该函数是一个导出的符合,可在.dynsym表中找到。libfoo.so库包含以下内容:
1 | //foo_exported.h |
1 | //foo.private.h |
1 | #android.bp |
.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参数的类型。
ar_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进行更改)。
对于在对应的Android.bp文件中标有vendor_available: true和vndk.enabled: true的库,必须确保其ABI合规性。例如:
1 | #android.bp |
对于可通过导出的函数直接或间接到达的数据类型,对库进行以下更改会破坏ABI合规性:
数据类型 | 说明
:- | :-
结构和类 | 移除非静态数据成员;会导致类/结构体大小发生变化的更改;会导致虚表布局发生变化的更改;添加/移除基类;更改基类的顺序;更改模板参数;会导致数据成员的内存偏移发生变化的更改;更改成员的const-volatile-restricted限定符;对数据成员的访问权限指定符进行降级。
联合 | 添加/移除字段;会导致大小发生变化的更改;更改字段顺序;更改字段类型。
枚举 | 更改成员的值;更改成员的名称;更改基础类型。
全局符号 | 移除通过公开标头导出的符号。对于类型FUNC的全局符号,添加/移除参数;以任何方式更改任何参数的类型;以任何方式更改返回值类型。对访问权限指定符进行降级,对于类型OBJECT的全局符号任何方式更改相应的C/C++类型;对访问权限指定符进行降级。
编译VNDK库时,系统会将其ABI与所编译VNDK的版本对应的ABI参考进行比较。参考ABI位于:${AOSP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based。
当ABI损坏时,编译日志会显示警告,其中包含警告类型以及abi-diff报告所在的路径。如果libbinder的ABI有不兼容的更改,则编译系统会抛出错误,并显示类似以下的消息:
1 | ***************************************************** |
1 | platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder |
编译VNDK库时:header-abi-dumper会处理为了编译VNDK库(库本身的源文件以及通过静态传递依赖项沿用的源文件)而编译的源文件,以生成与各个源文件对应的.sdump文件。
然后header-abi-linker会处理.sdump文件(使用提供给它的版本脚本或与共享库对应的.so 文件),以生成.lsdump文件,该文件用于记录与共享库对应的所有ABI信息。
header-abi-diff会将.lsdump文件与参考.lsdump文件进行比较,以生成差异报告,该报告中会简要说明两个库的ABI之间存在的差异。
header-abi-dumper工具会解析C/C++源文件,并将从该源文件推断出的ABI转储到一个中间文件。编译系统会对所有已编译的源文件运行header-abi-dumper,同时还会建立一个库,其中包含来自传递依赖项的源文件。目前.sdump 文件采用Protobuf TextFormatted格式,无法保证该格式在未来版本中仍保持稳定。因此,.sdump文件格式化应被视为编译系统的实现细节。
例如,libfoo.so具有以下源文件foo.cpp:
1 |
|
可以使用header-abi-dumper生成中间.sdump文件,该文件代表源文件使用以下命令提供的ABI:
1 | $ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++ |
该命令指示header-abi-dumper解析foo.cpp并发出ABI信息。下面是 header-abi-dumper生成的foo.sdump中的一部分:
1 | record_types { |
foo.sdump包含源文件foo.cpp提供的ABI信息,如:
a.record_types:指通过公开header提供的结构、联合或类。每个记录类型都包含其字段、大小、访问权限指定符、所在header文件等相关信息。
b.pointer_types:指通过公开header提供的记录/函数直接/间接引用的指针类型,以及指针指向的类型(通过type_info中的 referenced_type字段)。对于限定类型、内置C/C++类型、数组类型以及左值和右值参考类型,系统会在.sdump文件中记录类似信息。
c.functions:表示通过公开标头提供的函数。它们还包含函数的重整名称、返回值类型、参数类型、访问权限指定符等相关信息。
header-abi-linker工具会将header-abi-dumper生成的中间文件作为输入,然后关联以下文件:
输入:a.header-abi-dumper生成的中间文件。b.版本脚本/映射文件(可选)。c.共享库的.so文件。
输出:用于记录共享库ABI的文件。
该工具会将收到的所有中间文件中的类型图合并在一起,并会将不同转换单元之间的单一定义差异考虑在内。然后,该工具会解析版本脚本或解析共享库的.dynsym表(.so 文件),以创建导出符号列表。
例如,当libfoo将bar.cpp文件添加到其编译时,系统可能会调用header-abi-linker,以创建libfoo的完整关联ABI转储,如下所示:
1 | header-abi-linker -I exported foo.sdump bar.sdump \ |
libfoo.so.lsdump中的命令输出示例:
1 | record_types { |
header-abi-linker工具将执行以下操作:
a.关联收到的.sdump文件(foo.sdump和bar.sdump),滤除位于exported目录的header中不存在的ABI信息。
b.解析libfoo.so,然后通过其.dynsym表收集通过库导出的符号的相关信息。
c.添加_Z3FooiP3bar和Bar。
d.libfoo.so.lsdump是最终生成的 libfoo.so ABI转储。
header-abi-diff工具会将代表两个库的ABI的两个.lsdump文件进行比较,并生成差异报告,其中会说明这两个ABI之间存在的差异。
输入:a.表示旧共享库的ABI的.lsdump文件。b.表示新共享库的ABI的.lsdump文件。
输出:差异报告,其中会说明在比较两个共享库提供的ABI之后发现的差异。
ABI差异文件会尽可能详细且便于读懂。格式在未来版本中可能会发生变化。如有两个版本的libfoo:libfoo_old.so和 libfoo_new.so。在libfoo_new.so中的bar_t内,将mfoo的类型从foo_t更改为foo_t *。由于bar_t是直接可到达类型,因此这应该由header-abi-diff标记为会破坏ABI的更改。
运行header-abi-diff的命令:
1 | header-abi-diff -old libfoo_old.so.lsdump \ |
libfoo.so.abidiff中的命令输出示例:
1 | lib_name: "libfoo" |
libfoo.so.abidiff包含一个报告,其中会注明libfoo中所有会破坏ABI的更改。record_type_diffs消息表示记录发生了更改,并会列出不兼容的更改,其中包括:
a.记录大小从24个字节更改为8个字节。
b.mfoo的字段类型从foo更改为foo (去除了所有类型定义符)。
c.type_stack字段用于指示header-abi-diff如何到达已更改的类型 (bar)。该字段可作如下解释:Foo是一个导出的函数,接受bar 作为参数,该参数指向已导出且发生变化的bar。
要强制执行VNDK和LLNDK共享库的ABI/API,必须将ABI参考添入到${AOSP}/prebuilts/abi-dumps/(v)ndk/ 中。要创建这些参考,需命令:
1 | ${AOSP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py |
创建参考后,如果对源代码所做的任何更改导致VNDK或LLNDK库中出现不兼容的ABI/API更改,则这些更改现在会导致编译错误。要更新特定VNDK核心库的ABI参考,运行命令:
1 | ${AOSP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> |
要更新特定LLNDK库的ABI参考,运行命令:
1 | ${AOSP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk |