La estabilidad de la interfaz binaria de la aplicación (ABI) es un requisito previo de las actualizaciones del framework, ya que los módulos del proveedor pueden depender de las bibliotecas compartidas del kit de desarrollo nativo del proveedor (VNDK) que residen en la partición del sistema. En una versión de Android, las bibliotecas compartidas del VNDK recién compiladas deben ser compatibles con ABI para las bibliotecas compartidas del VNDK lanzadas anteriormente, de modo que los módulos de los proveedores puedan funcionar con esas bibliotecas sin recompilaciones y sin errores de tiempo de ejecución. Entre las versiones de Android, se pueden cambiar las bibliotecas de VNDK y no hay garantías de ABI.
Para ayudar a garantizar la compatibilidad con ABI, Android 9 incluye un verificador de ABI de encabezado, como se describe en las siguientes secciones.
Información acerca del cumplimiento del VNDK y la ABI
El VNDK es un conjunto restringido de bibliotecas a las que se pueden vincular los módulos de proveedores y que permiten actualizaciones exclusivas del framework. La cumplimiento de la ABI hace referencia a la capacidad de una versión más reciente de una biblioteca compartida para funcionar como se espera con un módulo que está vinculado de forma dinámica a ella (es decir, funciona como lo haría una versión anterior de la biblioteca).
Información acerca de los símbolos exportados
Un símbolo exportado (también conocido como símbolo global) hace referencia a un símbolo que satisface todas las siguientes condiciones:
- Se exportan mediante los encabezados públicos de una biblioteca compartida.
- Aparece en la tabla
.dynsym
del archivo.so
correspondiente a la biblioteca compartida. - Tiene vinculación WEAK o GLOBAL.
- La visibilidad es DEFAULT o PROTECTED.
- El índice de la sección no es UNDEFINED.
- El tipo es FUNC o OBJECT.
Los encabezados públicos de una biblioteca compartida se definen como los encabezados disponibles para otras bibliotecas o objetos binarios mediante los atributos export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
y export_generated_headers
en las definiciones de Android.bp
del módulo correspondiente a la biblioteca compartida.
Acerca de los tipos accesibles
Un tipo accesible es cualquier tipo integrado o definido por el usuario en C/C++ al que se puede acceder de forma directa o indirecta a través de un símbolo exportado Y exportado a través de encabezados públicos. Por ejemplo, libfoo.so
tiene la función Foo
, que es un símbolo exportado que se encuentra en la tabla .dynsym
. La biblioteca de libfoo.so
incluye lo siguiente:
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" ], } |
Tabla .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
|
Si observas Foo
, los tipos a los que se puede acceder de forma directa o indirecta incluyen los siguientes:
Tipo | Descripción |
---|---|
bool
|
Es el tipo de datos que se muestra en Foo .
|
int
|
Es el tipo del primer parámetro Foo .
|
bar_t *
|
Es el tipo del segundo parámetro Foo. A través de bar_t * ,
bar_t se exporta a través de foo_exported.h .
bar_t contiene un miembro mfoo , de tipo
foo_t , que se exporta a través de foo_exported.h ,
lo que genera que se exporten más tipos:
Sin embargo, NO se puede acceder a foo_private_t porque no se exporta a través de foo_exported.h . (foo_private_t * es opaco, por lo que se permiten cambios en foo_private_t ).
|
Se puede brindar una explicación similar para los tipos a los que se puede acceder a través de especificadores de clase base y parámetros de plantilla.
Cómo garantizar el cumplimiento de la ABI
Se debe garantizar el cumplimiento de la ABI para las bibliotecas marcadas como vendor_available: true
y vndk.enabled: true
en los archivos Android.bp
correspondientes. Por ejemplo:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
En el caso de los tipos de datos a los que puede acceder una función exportada de forma directa o indirecta, los siguientes cambios en una biblioteca se clasifican como rompedores de ABI:
Tipo de datos | Descripción |
---|---|
Estructuras y clases |
|
Sindicatos |
|
Enumeraciones |
|
Símbolos globales |
|
* No se deben cambiar ni quitar las funciones miembro públicas ni privadas, ya que las funciones intercaladas públicas pueden hacer referencia a funciones miembro privadas. Las referencias de símbolos a funciones de miembros privadas se pueden mantener en objetos binarios del llamador. Cambiar o quitar funciones de miembros privados de bibliotecas compartidas puede generar objetos binarios que no sean compatibles con versiones anteriores.
** No se deben cambiar los desplazamientos a miembros de datos públicos o privados, ya que las funciones intercaladas pueden hacer referencia a estos miembros de datos en su cuerpo. Cambiar los desplazamientos de los miembros de datos puede generar objetos binarios incompatibles con versiones anteriores.
*** Si bien estos no cambian el diseño de la memoria del tipo, existen diferencias semánticas que podrían provocar que las bibliotecas no funcionen como se espera.
Usa herramientas de cumplimiento de ABI
Cuando se compila una biblioteca del VNDK, la ABI de la biblioteca se compara con la referencia de ABI correspondiente para la versión del VNDK que se compila. Los volcados de ABI de referencia se encuentran en las siguientes ubicaciones:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Por ejemplo, cuando se compila libfoo
para x86 en el nivel de API 27, la ABI inferida de libfoo
se compara con su referencia en:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Error de ruptura de ABI
Cuando se producen fallas de ABI, el registro de compilación muestra advertencias con el tipo de advertencia y una ruta de acceso al informe abi-diff. Por ejemplo, si la ABI de libbinder
tiene un cambio incompatible, el sistema de compilación arroja un error con un mensaje similar al siguiente:
***************************************************** 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 ----
Compilación de verificaciones de ABI de la biblioteca de VNDK
Cuando se compila una biblioteca de VNDK, sucede lo siguiente:
header-abi-dumper
procesa los archivos de origen compilados para compilar la biblioteca del VNDK (los propios archivos de origen de la biblioteca, así como los archivos de origen heredados a través de dependencias transitivas estáticas) para producir archivos.sdump
que corresponden a cada fuente.
Figura 1: Creación de los archivos .sdump
- Luego,
header-abi-linker
procesa los archivos.sdump
(con una secuencia de comandos de versión que se le proporcionó o el archivo.so
correspondiente a la biblioteca compartida) para producir un archivo.lsdump
que registra toda la información de la ABI correspondiente a la biblioteca compartida.
Figura 2: Crea el archivo .lsdump
header-abi-diff
compara el archivo.lsdump
con un archivo.lsdump
de referencia para generar un informe de diferencias que describe las diferencias en las ABI de las dos bibliotecas.
Figura 3: Crea el informe de diferencias
header-abi-dumper
La herramienta header-abi-dumper
analiza un archivo de origen C/C++ y vuelca la ABI inferida de ese archivo de origen en un archivo intermedio. El sistema de compilación ejecuta header-abi-dumper
en todos los archivos de origen compilados y, al mismo tiempo, compila una biblioteca que incluye los archivos de origen de las dependencias transitivas.
Entradas |
|
---|---|
Salida | Es un archivo que describe la ABI del archivo fuente (por ejemplo, foo.sdump representa la ABI de foo.cpp ).
|
Actualmente, los archivos .sdump
están en formato JSON, lo que no garantiza que sean estables en versiones futuras. Por lo tanto, el formato de archivo .sdump
debe considerarse un detalle de implementación del sistema de compilación.
Por ejemplo, libfoo.so
tiene el siguiente archivo fuente 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; }
Puedes usar header-abi-dumper
para generar un archivo .sdump
intermedio que represente la ABI que presenta el archivo fuente con lo siguiente:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Este comando le indica a header-abi-dumper
que analice foo.cpp
con las marcas del compilador que siguen a --
y emita la información de ABI que exportan los encabezados públicos en el directorio exported
. A continuación, se muestra el foo.sdump
generado por 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
contiene información de ABI exportada por el archivo de origen foo.cpp
y los encabezados públicos, por ejemplo,
record_types
: Hace referencia a structs, uniones o clases definidas en los encabezados públicos. Cada tipo de registro tiene información sobre sus campos, su tamaño, el especificador de acceso, el archivo de encabezado en el que se define y otros atributos.pointer_types
: Consulta los tipos de punteros a los que hacen referencia directa o indirectamente los registros o las funciones exportados en los encabezados públicos, junto con el tipo al que apunta el puntero (a través del camporeferenced_type
entype_info
). Se registra información similar en el archivo.sdump
para los tipos calificados, los tipos integrados de C/C++, los tipos de array y los tipos de referencia de valor izquierdo y valor derecho. Esa información permite la comparación recursiva.functions
: Representa las funciones exportadas por encabezados públicos. También tienen información sobre el nombre truncado de la función, el tipo de datos que se muestra, los tipos de parámetros, el especificador de acceso y otros atributos.
header-abi-linker
La herramienta header-abi-linker
toma los archivos intermedios que produce header-abi-dumper
como entrada y, luego, vincula esos archivos:
Entradas |
|
---|---|
Salida | Un archivo que describe la ABI de una biblioteca compartida (por ejemplo, libfoo.so.lsdump representa la ABI de libfoo ).
|
La herramienta combina los gráficos de tipos en todos los archivos intermedios que se le proporcionan, teniendo en cuenta las diferencias entre las unidades de traducción de una definición (tipos definidos por el usuario en diferentes unidades de traducción con el mismo nombre completamente calificado que pueden ser semánticamente diferentes). Luego, la herramienta analiza una secuencia de comandos de versión o la tabla .dynsym
de la biblioteca compartida (archivo .so
) para crear una lista de los símbolos exportados.
Por ejemplo, libfoo
consta de foo.cpp
y bar.cpp
. Se podría invocar a header-abi-linker
para crear el volcado de ABI vinculado completo de libfoo
de la siguiente manera:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Ejemplo de resultado del comando en 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" : [] }
La herramienta header-abi-linker
:
- Vincula los archivos
.sdump
proporcionados (foo.sdump
ybar.sdump
), filtrando la información de ABI que no está presente en los encabezados que se encuentran en el directorioexported
. - Analiza
libfoo.so
y recopila información sobre los símbolos que exporta la biblioteca a través de su tabla.dynsym
. - Se agregaron
_Z3FooiP3bar
y_Z6FooBadiP3foo
libfoo.so.lsdump
es el volcado final de ABI generado de libfoo.so
.
header-abi-diff
La herramienta header-abi-diff
compara dos archivos .lsdump
que representan la ABI de dos bibliotecas y produce un informe de diferencias que indica las diferencias entre las dos ABI.
Entradas |
|
---|---|
Salida | Un informe de diferencias que indica las diferencias en las ABI que ofrecen ambas bibliotecas compartidas en comparación con ellas. |
El archivo de diferencias de ABI está en formato de texto de protobuf. El formato está sujeto a cambios en versiones futuras.
Por ejemplo, tienes dos versiones de libfoo
: libfoo_old.so
y libfoo_new.so
. En libfoo_new.so
, en bar_t
, cambias el tipo de mfoo
de foo_t
a foo_t *
. Como bar_t
es un tipo alcanzable, debe marcarse como un cambio rotundo de ABI por header-abi-diff
.
Para ejecutar header-abi-diff
, haz lo siguiente:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Ejemplo de resultado del comando en 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
contiene un informe de todos los cambios que generan errores de ABI en libfoo
. El mensaje record_type_diffs
indica que cambió un registro y muestra una lista de los cambios incompatibles, que incluyen lo siguiente:
- El tamaño del registro cambia de
24
bytes a8
bytes. - El tipo de campo de
mfoo
cambia defoo
afoo *
(se quitan todos los typedefs).
El campo type_stack
indica cómo header-abi-diff
llegó al tipo que cambió (bar
). Este campo se puede interpretar como Foo
es una función exportada que toma bar *
como parámetro, que apunta a bar
, que se exportó y cambió.
Aplica la ABI y la API
Para aplicar la ABI y la API de las bibliotecas compartidas de VNDK, las referencias de ABI deben verificarse en ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
.
Para crear estas referencias, ejecuta el siguiente comando:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Después de crear las referencias, cualquier cambio que se realice en el código fuente que genere un cambio de ABI o API incompatible en una biblioteca de VNDK ahora generará un error de compilación.
Para actualizar las referencias de ABI de bibliotecas específicas, ejecuta el siguiente comando:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Por ejemplo, para actualizar las referencias de ABI de libbinder
, ejecuta lo siguiente:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder