La estabilidad de la interfaz binaria de la aplicación (ABI) es un requisito previo de las actualizaciones solo de 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. Dentro de una versión de Android, las bibliotecas compartidas de VNDK compiladas recientemente deben ser compatibles con la ABI de las bibliotecas compartidas de VNDK lanzadas anteriormente para que los módulos de proveedores puedan funcionar con esas bibliotecas sin volver a compilarlas 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 garantizar la compatibilidad con la ABI, Android 9 incluye un verificador de ABI de encabezado, como se describe en las siguientes secciones.
Información acerca del cumplimiento de VNDK y ABI
El VNDK es un conjunto restrictivo de bibliotecas a las que los módulos del proveedor pueden vincularse y que habilitan actualizaciones solo de 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 con 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 a través de los atributos export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
y export_generated_headers
en las definiciones Android.bp
del módulo correspondiente a la biblioteca compartida.
Información 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
|
En Foo
, los tipos de alcance directo o indirecto 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 los cambios realizados en foo_private_t ).
|
Se puede dar una explicación similar para los tipos accesibles a través de los especificadores de clase base y los 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 una función exportada puede acceder de forma directa o indirecta, los siguientes cambios en una biblioteca se clasifican como que generan una ruptura 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 offsets de los miembros de datos puede generar objetos binarios incompatibles con versiones anteriores.
*** Si bien estos no cambian el diseño de 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
En los casos de 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 estáticas transitivas) 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 proporciona o el archivo.so
que corresponde a la biblioteca compartida) para producir un archivo.lsdump
que registra toda la información de 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: Cómo crear el informe de diferencias
header-abi-dumper
La herramienta header-abi-dumper
analiza un archivo fuente C/C++ y vuelca la ABI inferida de ese archivo fuente en un archivo intermedio. El sistema de compilación ejecuta header-abi-dumper
en todos los archivos fuente compilados y, al mismo tiempo, compila una biblioteca que incluye los archivos fuente 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, que no se garantiza que sea estable 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 fuente foo.cpp
y los encabezados públicos, por ejemplo:
record_types
. Consulta las estructuras, 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
que se le proporcionan (foo.sdump
ybar.sdump
) y filtra la información de ABI que no está presente en los encabezados que residen 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 de ABI generado final 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 las dos bibliotecas compartidas en comparación. |
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 *
. Dado que bar_t
es un tipo accesible, header-abi-diff
debe marcarlo como un cambio rotundo de ABI.
Para ejecutar header-abi-diff
, sigue estos pasos:
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