应用程序二进制接口 (ABI) 稳定性是仅框架更新的先决条件,因为供应商模块可能依赖于位于系统分区的供应商原生开发套件 (VNDK) 共享库。在 Android 版本中,新构建的 VNDK 共享库必须与先前发布的 VNDK 共享库 ABI 兼容,以便供应商模块可以使用这些库,而无需重新编译且不会出现运行时错误。在 Android 版本之间,VNDK 库可以更改,并且不保证 ABI 兼容性。
为了帮助确保 ABI 兼容性,Android 9 包含了一个标头 ABI 检查器,如下节所述。
关于 VNDK 和 ABI 合规性
VNDK 是供应商模块可以链接到的一组受限库,它支持仅框架更新。ABI 合规性 指的是新版本的共享库能够与其动态链接的模块按预期工作(即,像旧版本的库一样工作)的能力。
关于导出的符号
导出的符号(也称为全局符号)是指满足以下所有条件的符号
- 由共享库的公共标头导出。
- 出现在与共享库对应的
.so
文件的.dynsym
表中。 - 具有 WEAK 或 GLOBAL 绑定。
- 可见性为 DEFAULT 或 PROTECTED。
- 段索引不是 UNDEFINED。
- 类型为 FUNC 或 OBJECT。
共享库的公共标头定义为可通过 export_include_dirs
、export_header_lib_headers
、export_static_lib_headers
、export_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 : [ "exported" ], } |
.dynsym 表 | |||||||
---|---|---|---|---|---|---|---|
编号
|
值
|
大小
|
类型
|
绑定
|
可见性
|
索引
|
名称
|
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 的成员 mfoo ,它通过 foo_exported.h 导出,这导致更多类型被导出
但是, foo_private_t 是不可访问的,因为它没有通过 foo_exported.h 导出。(foo_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 破坏性更改
数据类型 | 描述 |
---|---|
结构体和类 |
|
联合体 |
|
枚举 |
|
全局符号 |
|
* 公有和私有成员函数都不得更改或删除,因为公有内联函数可以引用私有成员函数。对私有成员函数的符号引用可以保留在调用方二进制文件中。从共享库中更改或删除私有成员函数可能会导致向后不兼容的二进制文件。
** 公有或私有数据成员的偏移量都不得更改,因为内联函数可以在其函数体中引用这些数据成员。更改数据成员偏移量可能会导致向后不兼容的二进制文件。
*** 虽然这些不会更改类型的内存布局,但存在语义差异,可能会导致库无法按预期运行。
使用 ABI 合规性工具
构建 VNDK 库时,会将该库的 ABI 与正在构建的 VNDK 版本的相应 ABI 参考进行比较。参考 ABI 转储位于
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
例如,在 API 级别 27 为 x86 构建 libfoo
时,会将 libfoo
的推断 ABI 与其在以下位置的参考进行比较
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/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 库时
header-abi-dumper
处理编译用于构建 VNDK 库的源文件(库自身的源文件以及通过静态传递依赖项继承的源文件),以生成与每个源文件对应的.sdump
文件。
图 1. 创建 .sdump
文件- 然后,
header-abi-linker
处理.sdump
文件(使用提供给它的版本脚本或与共享库对应的.so
文件),以生成记录与共享库对应的所有 ABI 信息的.lsdump
文件。
图 2. 创建 .lsdump
文件 header-abi-diff
将.lsdump
文件与参考.lsdump
文件进行比较,以生成一份差异报告,其中概述了两个库的 ABI 中的差异。
图 3. 创建差异报告
header-abi-dumper
header-abi-dumper
工具解析 C/C++ 源文件,并将从该源文件推断出的 ABI 转储到中间文件中。构建系统在构建包含来自传递依赖项的源文件的库的同时,对所有已编译的源文件运行 header-abi-dumper
。
输入 |
|
---|---|
输出 | 描述源文件的 ABI 的文件(例如,foo.sdump 表示 foo.cpp 的 ABI)。 |
目前,.sdump
文件是 JSON 格式,这不能保证在未来的版本中保持稳定。因此,.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 -- -I exported -x c++
此命令告知 header-abi-dumper
使用 --
后面的编译器标志解析 foo.cpp
,并发出由 exported
目录中的公共标头导出的 ABI 信息。以下是由 header-abi-dumper
生成的 foo.sdump
{ "array_types" : [], "builtin_types" : [ { "alignment" : 4, "is_integral" : true, "linker_set_key" : "_ZTIi", "name" : "int", "referenced_type" : "_ZTIi", "self_type" : "_ZTIi", "size" : 4 } ], "elf_functions" : [], "elf_objects" : [], "enum_types" : [], "function_types" : [], "functions" : [ { "function_name" : "FooBad", "linker_set_key" : "_Z6FooBadiP3foo", "parameters" : [ { "referenced_type" : "_ZTIi" }, { "referenced_type" : "_ZTIP3foo" } ], "return_type" : "_ZTI3bar", "source_file" : "exported/foo_exported.h" } ], "global_vars" : [], "lvalue_reference_types" : [], "pointer_types" : [ { "alignment" : 8, "linker_set_key" : "_ZTIP11foo_private", "name" : "foo_private *", "referenced_type" : "_ZTI11foo_private", "self_type" : "_ZTIP11foo_private", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIP3foo", "name" : "foo *", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTIP3foo", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIPi", "name" : "int *", "referenced_type" : "_ZTIi", "self_type" : "_ZTIPi", "size" : 8, "source_file" : "exported/foo_exported.h" } ], "qualified_types" : [], "record_types" : [ { "alignment" : 8, "fields" : [ { "field_name" : "mfoo", "referenced_type" : "_ZTI3foo" } ], "linker_set_key" : "_ZTI3bar", "name" : "bar", "referenced_type" : "_ZTI3bar", "self_type" : "_ZTI3bar", "size" : 24, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "fields" : [ { "field_name" : "m1", "referenced_type" : "_ZTIi" }, { "field_name" : "m2", "field_offset" : 64, "referenced_type" : "_ZTIPi" }, { "field_name" : "mPfoo", "field_offset" : 128, "referenced_type" : "_ZTIP11foo_private" } ], "linker_set_key" : "_ZTI3foo", "name" : "foo", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTI3foo", "size" : 24, "source_file" : "exported/foo_exported.h" } ], "rvalue_reference_types" : [] }
foo.sdump
包含由源文件 foo.cpp
和公共标头导出的 ABI 信息,例如,
record_types
。引用公共标头中定义的结构体、联合体或类。每个记录类型都包含有关其字段、大小、访问说明符、定义它的头文件和其他属性的信息。pointer_types
。引用公共标头中导出的记录/函数直接/间接引用的指针类型,以及指针指向的类型(通过type_info
中的referenced_type
字段)。.sdump
文件中记录了限定类型、内置 C/C++ 类型、数组类型以及左值和右值引用类型的类似信息。此类信息允许递归差异比较。functions
。表示由公共标头导出的函数。它们还包含有关函数的 mangled 名称、返回类型、参数类型、访问说明符和其他属性的信息。
header-abi-linker
header-abi-linker
工具将 header-abi-dumper
生成的中间文件作为输入,然后链接这些文件
输入 |
|
---|---|
输出 | 描述共享库的 ABI 的文件(例如,libfoo.so.lsdump 表示 libfoo 的 ABI)。 |
该工具合并提供给它的所有中间文件中的类型图,同时考虑跨转换单元的单一定义(在具有相同完全限定名称的不同转换单元中的用户定义类型,在语义上可能不同)差异。然后,该工具解析版本脚本或共享库(.so
文件)的 .dynsym
表,以生成导出符号的列表。
例如,libfoo
由 foo.cpp
和 bar.cpp
组成。可以调用 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
中的示例命令输出
{ "array_types" : [], "builtin_types" : [ { "alignment" : 1, "is_integral" : true, "is_unsigned" : true, "linker_set_key" : "_ZTIb", "name" : "bool", "referenced_type" : "_ZTIb", "self_type" : "_ZTIb", "size" : 1 }, { "alignment" : 4, "is_integral" : true, "linker_set_key" : "_ZTIi", "name" : "int", "referenced_type" : "_ZTIi", "self_type" : "_ZTIi", "size" : 4 } ], "elf_functions" : [ { "name" : "_Z3FooiP3bar" }, { "name" : "_Z6FooBadiP3foo" } ], "elf_objects" : [], "enum_types" : [], "function_types" : [], "functions" : [ { "function_name" : "Foo", "linker_set_key" : "_Z3FooiP3bar", "parameters" : [ { "referenced_type" : "_ZTIi" }, { "referenced_type" : "_ZTIP3bar" } ], "return_type" : "_ZTIb", "source_file" : "exported/foo_exported.h" }, { "function_name" : "FooBad", "linker_set_key" : "_Z6FooBadiP3foo", "parameters" : [ { "referenced_type" : "_ZTIi" }, { "referenced_type" : "_ZTIP3foo" } ], "return_type" : "_ZTI3bar", "source_file" : "exported/foo_exported.h" } ], "global_vars" : [], "lvalue_reference_types" : [], "pointer_types" : [ { "alignment" : 8, "linker_set_key" : "_ZTIP11foo_private", "name" : "foo_private *", "referenced_type" : "_ZTI11foo_private", "self_type" : "_ZTIP11foo_private", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIP3bar", "name" : "bar *", "referenced_type" : "_ZTI3bar", "self_type" : "_ZTIP3bar", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIP3foo", "name" : "foo *", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTIP3foo", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIPi", "name" : "int *", "referenced_type" : "_ZTIi", "self_type" : "_ZTIPi", "size" : 8, "source_file" : "exported/foo_exported.h" } ], "qualified_types" : [], "record_types" : [ { "alignment" : 8, "fields" : [ { "field_name" : "mfoo", "referenced_type" : "_ZTI3foo" } ], "linker_set_key" : "_ZTI3bar", "name" : "bar", "referenced_type" : "_ZTI3bar", "self_type" : "_ZTI3bar", "size" : 24, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "fields" : [ { "field_name" : "m1", "referenced_type" : "_ZTIi" }, { "field_name" : "m2", "field_offset" : 64, "referenced_type" : "_ZTIPi" }, { "field_name" : "mPfoo", "field_offset" : 128, "referenced_type" : "_ZTIP11foo_private" } ], "linker_set_key" : "_ZTI3foo", "name" : "foo", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTI3foo", "size" : 24, "source_file" : "exported/foo_exported.h" } ], "rvalue_reference_types" : [] }
header-abi-linker
工具
- 链接提供给它的
.sdump
文件(foo.sdump
和bar.sdump
),过滤掉目录exported
中标头中不存在的 ABI 信息。 - 解析
libfoo.so
,并收集有关库通过其.dynsym
表导出的符号的信息。 - 添加
_Z3FooiP3bar
和_Z6FooBadiP3foo
。
libfoo.so.lsdump
是 libfoo.so
的最终生成的 ABI 转储。
header-abi-diff
header-abi-diff
工具比较两个表示两个库的 ABI 的 .lsdump
文件,并生成一份差异报告,说明两个 ABI 之间的差异。
输入 |
|
---|---|
输出 | 一份差异报告,说明比较的两个共享库提供的 ABI 中的差异。 |
ABI 差异文件采用 protobuf 文本格式。该格式可能会在未来的版本中更改。
例如,您有两个版本的 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
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 *
(所有 typedef 都被剥离)。
type_stack
字段指示 header-abi-diff
如何访问已更改的类型 (bar
)。此字段可以解释为 Foo
是一个导出函数,它将 bar *
作为参数传入,该参数指向 bar
,后者被导出并已更改。
强制执行 ABI 和 API
为了强制执行 VNDK 共享库的 ABI 和 API,必须将 ABI 参考签入到 ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
。要创建这些参考,请运行以下命令
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
创建参考后,对源代码进行的任何导致 VNDK 库中出现不兼容的 ABI/API 更改的更改现在都会导致构建错误。
要更新特定库的 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