Application Binary Interface (ABI) stability is a prerequisite of framework-only updates because vendor modules may depend on the Vendor Native Development Kit (VNDK) shared libraries that reside in the system partition. Within an Android release, newly-built VNDK shared libraries must be ABI-compatible to previously released VNDK shared libraries so vendor modules can work with those libraries without recompilation and without runtime errors. Between Android releases, VNDK libraries can be changed and there are no ABI guarantees.
To help ensure ABI compatibility, Android 9 includes a header ABI checker, as described in the following sections.
About VNDK and ABI compliance
The VNDK is a restrictive set of libraries that vendor modules may link to and which enable framework-only updates. ABI compliance refers to the ability of a newer version of a shared library to work as expected with a module that is dynamically linked to it (i.e. works as an older version of the library would).
About exported symbols
An exported symbol (also known as a global symbol) refers to a symbol that satisfies all of the following:
- Exported by the public headers of a shared library.
- Appears in the
.dynsymtable of the.sofile corresponding to the shared library. - Has WEAK or GLOBAL binding.
- Visibility is DEFAULT or PROTECTED.
- Section index is not UNDEFINED.
- Type is either FUNC or OBJECT.
The public headers of a shared library are defined as the headers
available to other libraries/binaries through the
export_include_dirs, export_header_lib_headers,
export_static_lib_headers,
export_shared_lib_headers, and
export_generated_headers attributes in Android.bp
definitions of the module corresponding to the shared library.
About reachable types
A reachable type is any C/C++ built-in or user-defined type that is
reachable directly or indirectly through an exported symbol AND exported
through public headers. For example, libfoo.so has function
Foo, which is an exported symbol found in the
.dynsym table. The libfoo.so library includes the
following:
| 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 table | |||||||
|---|---|---|---|---|---|---|---|
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
|
Looking at Foo, direct/indirect reachable types include:
| Type | Description |
|---|---|
bool
|
Return type of Foo.
|
int
|
Type of first Foo parameter.
|
bar_t *
|
Type of second Foo parameter. By way of bar_t *,
bar_t is exported through foo_exported.h.
bar_t contains a member mfoo, of type
foo_t, which is exported through foo_exported.h,
which results in more types being exported:
However, foo_private_t is NOT reachable because it isn't
exported through foo_exported.h. (foo_private_t *
is opaque, therefore changes made to foo_private_t are allowed.)
|
A similar explanation can be given for types reachable through base class specifiers and template parameters as well.
Ensure ABI compliance
ABI compliance must be ensured for the libraries marked
vendor_available: true and vndk.enabled: true in the
corresponding Android.bp files. For example:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
For data types reachable directly or indirectly by an exported function, the following changes to a library are classified as ABI-breaking:
| Data type | Description |
|---|---|
| Structures and Classes |
|
| Unions |
|
| Enumerations |
|
| Global Symbols |
|
* Both public and private member functions must not be changed or removed because public inline functions can refer to private member functions. Symbol references to private member functions can be kept in caller binaries. Changing or removing private member functions from shared libraries can result in backward-incompatible binaries.
** The offsets to public or private data members must not be changed because inline functions can refer to these data members in their function body. Changing data member offsets can result in backward-incompatible binaries.
*** While these don't change the memory layout of the type, there are semantic differences that could lead to libraries not functioning as expected.
Use ABI compliance tools
When a VNDK library is built, the library's ABI is compared with the corresponding ABI reference for the version of the VNDK being built. Reference ABI dumps are located in:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
For example, on building libfoo for x86 at API level 27,
libfoo's inferred ABI is compared with its reference at:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
ABI breakage error
On ABI breakages, the build log displays warnings with the warning type and a
path to the abi-diff report. For example, if libbinder's ABI has
an incompatible change, the build system throws an error with a message
similar to the following:
***************************************************** 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 ----
Build VNDK library ABI checks
When a VNDK library is built:
header-abi-dumperprocesses the source files compiled to build the VNDK library (the library's own source files as well as source files inherited through static transitive dependencies), to produce.sdumpfiles that correspond to each source.
Figure 1. Creating the .sdumpfilesheader-abi-linkerthen processes the.sdumpfiles (using either a version script provided to it or the.sofile corresponding to the shared library) to produce a.lsdumpfile that logs all of the ABI information corresponding to the shared library.
Figure 2. Creating the .lsdumpfileheader-abi-diffcompares the.lsdumpfile with a reference.lsdumpfile to produce a diff report that outlines the differences in the ABIs of the two libraries.
Figure 3. Creating the diff report
header-abi-dumper
The header-abi-dumper tool parses a C/C++ source file and dumps
the ABI inferred from that source file into an intermediate file. The build
system runs header-abi-dumper on all compiled source files while
also building a library that includes the source files from transitive
dependencies.
| Inputs |
|
|---|---|
| Output | A file that describes the ABI of the source file (for example,
foo.sdump represents foo.cpp's ABI).
|
Currently .sdump files are in JSON format, which isn't
guaranteed to be stable across future releases. As such, .sdump
file formatting should be considered a build system implementation detail.
For example, libfoo.so has the following source file
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; }
You can use header-abi-dumper to generate an intermediate
.sdump file that represents the ABI presented by the source file
using:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
This command tells header-abi-dumper to parse
foo.cpp with the compiler flags following --, and
emit the ABI information that is exported by the public headers in the
exported directory. The following is
foo.sdump generated by
header-abi-dumper:
{ "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 contains ABI information exported by the source file
foo.cpp and the public headers, for example,
record_types. Refer to structs, unions, or classes defined in the public headers. Each record type has information about its fields, its size, access specifier, the header file it's defined in, and other attributes.pointer_types. Refer to pointer types directly/indirectly referenced by the exported records/functions in the public headers, along with the type the pointer points to (through thereferenced_typefield intype_info). Similar information is logged in the.sdumpfile for qualified types, built-in C/C++ types, array types, and lvalue and rvalue reference types. Such information allows recursive diffing.functions. Represent functions exported by public headers. They also have information about the function's mangled name, the return type, the types of the parameters, the access specifier, and other attributes.
header-abi-linker
The header-abi-linker tool takes the intermediate files produced
by header-abi-dumper as input then links those files:
| Inputs |
|
|---|---|
| Output | A file that describes the ABI of a shared library (for example,
libfoo.so.lsdump represents libfoo's ABI).
|
The tool merges the type graphs in all the intermediate files given to it,
taking into account one-definition (user-defined types in different
translation units with the same fully qualified name, might be semantically
different) differences across translation units. The tool then parses either
a version script or the .dynsym table of the shared library
(.so file) to make a list of the exported symbols.
For example, libfoo consists of foo.cpp and
bar.cpp. header-abi-linker could be invoked to
create the complete linked ABI dump of libfoo as follows:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Example command output in 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" : [] }
The header-abi-linker tool:
- Links the
.sdumpfiles provided to it (foo.sdumpandbar.sdump), filtering out the ABI information not present in the headers residing in the directory:exported. - Parses
libfoo.so, and collects information about the symbols exported by the library through its.dynsymtable. - Adds
_Z3FooiP3barand_Z6FooBadiP3foo.
libfoo.so.lsdump is the final generated ABI dump of
libfoo.so.
header-abi-diff
The header-abi-diff tool compares two .lsdump files
representing the ABI of two libraries and produces a diff report stating the
differences between the two ABIs.
| Inputs |
|
|---|---|
| Output | A diff report stating the differences in the ABIs offered by the two shared libraries compared. |
The ABI diff file is in protobuf text format. The format is subject to change in future releases.
For example, you have two versions of
libfoo: libfoo_old.so and
libfoo_new.so. In libfoo_new.so, in
bar_t, you change the type of mfoo from
foo_t to foo_t *. Since bar_t is a
reachable type, this should be flagged as an ABI breaking change by
header-abi-diff.
To run header-abi-diff:
header-abi-diff -old libfoo_old.so.lsdump \
-new libfoo_new.so.lsdump \
-arch arm64 \
-o libfoo.so.abidiff \
-lib libfoo
Example command output in 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 } } }
The libfoo.so.abidiff contains a report of all ABI breaking
changes in libfoo. The record_type_diffs message
indicates a record has changed and lists the incompatible changes, which
include:
- The size of the record changing from
24bytes to8bytes. - The field type of
mfoochanging fromfootofoo *(all typedefs are stripped off).
The type_stack field indicates how header-abi-diff
reached the type that changed (bar). This field may be
interpreted as Foo is an exported function that takes in
bar * as parameter, that points to bar, which was
exported and changed.
Enforce ABI and API
To enforce the ABI and API of VNDK shared libraries, ABI references must
be checked into ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/.
To create these references, run the following command:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
After creating the references, any change made to the source code that results in an incompatible ABI/API change in a VNDK library now results in a build error.
To update ABI references for specific libraries, run the following command:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
For example, to update libbinder ABI references, run:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder