La estabilidad de la interfaz binaria de aplicaciones (ABI) es un requisito previo para las actualizaciones de solo el marco porque 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 VNDK recién creadas deben ser compatibles con ABI con las bibliotecas compartidas VNDK publicadas anteriormente para que los módulos del proveedor puedan funcionar con esas bibliotecas sin tener que volver a compilarlas y sin errores de tiempo de ejecución. Entre versiones de Android, las bibliotecas VNDK se pueden cambiar y no hay garantías de ABI.
Para ayudar a garantizar la compatibilidad con ABI, Android 9 incluye un verificador ABI de encabezado, como se describe en las siguientes secciones.
Acerca del cumplimiento de VNDK y ABI
El VNDK es un conjunto restrictivo de bibliotecas a las que los módulos de proveedores pueden vincularse y que permiten actualizaciones solo del marco. El cumplimiento de ABI se refiere a la capacidad de una versión más nueva de una biblioteca compartida para funcionar como se espera con un módulo que está vinculado dinámicamente a ella (es decir, funciona como lo haría una versión anterior de la biblioteca).
Acerca de los símbolos exportados
Un símbolo exportado (también conocido como símbolo global ) se refiere a un símbolo que cumple todo lo siguiente:
- Exportado por los encabezados públicos de una biblioteca compartida.
- Aparece en la tabla
.dynsym
del archivo.so
correspondiente a la biblioteca compartida. - Tiene enlace DÉBIL o GLOBAL.
- La visibilidad es PREDETERMINADA o PROTEGIDA.
- El índice de la sección no está INDEFINIDO.
- El tipo es FUNC u OBJECT.
Los encabezados públicos de una biblioteca compartida se definen como los encabezados disponibles para otras bibliotecas/binarios a través de los 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 de C/C++ al que se pueda acceder directa o indirectamente 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 libfoo.so
incluye lo siguiente:
foo_exportado.h | foo.privado.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 cuanto a Foo
, los tipos accesibles directo/indirecto incluyen:
Tipo | Descripción |
---|---|
bool | Tipo de retorno de Foo . |
int | Tipo de primer parámetro Foo . |
bar_t * | Tipo de 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 da como resultado que se exporten más tipos:
Sin embargo, NO se puede acceder foo_private_t porque no se exporta a través de foo_exported.h . ( foo_private_t * es opaco, por lo tanto se permiten cambios realizados en foo_private_t ). |
También se puede dar una explicación similar para los tipos accesibles a través de especificadores de clase base y parámetros de plantilla.
Garantizar el cumplimiento de ABI
Se debe garantizar el cumplimiento de ABI para las bibliotecas marcadas 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, } }
Para los tipos de datos a los que se puede acceder directa o indirectamente mediante una función exportada, los siguientes cambios en una biblioteca se clasifican como ruptura de ABI:
Tipo de datos | Descripción |
---|---|
Estructuras y clases |
|
Sindicatos |
|
Enumeraciones |
|
Símbolos globales |
|
* Las funciones de los miembros públicos y privados no se deben cambiar ni eliminar porque las funciones públicas en línea pueden hacer referencia a funciones de los miembros privados. Las referencias de símbolos a funciones de miembros privados se pueden mantener en los archivos binarios de la persona que llama. Cambiar o eliminar funciones de miembros privados de bibliotecas compartidas puede generar archivos binarios incompatibles con versiones anteriores.
** Las compensaciones de los miembros de datos públicos o privados no se deben cambiar porque las funciones en línea pueden hacer referencia a estos miembros de datos en el cuerpo de su función. Cambiar las compensaciones de los miembros de datos puede dar como resultado archivos binarios incompatibles con versiones anteriores.
*** Si bien esto no cambia el diseño de la memoria del tipo, existen diferencias semánticas que podrían provocar que las bibliotecas no funcionen como se esperaba.
Uso de herramientas de cumplimiento de ABI
Cuando se crea una biblioteca VNDK, el ABI de la biblioteca se compara con la referencia ABI correspondiente para la versión del VNDK que se está creando. Los volcados de ABI de referencia se encuentran en:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Por ejemplo, al compilar libfoo
para x86 en el nivel API 27, el ABI inferido 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 rotura ABI
En caso de roturas de ABI, el registro de compilación muestra advertencias con el tipo de advertencia y una ruta 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 comprobaciones ABI de la biblioteca VNDK
Cuando se construye una biblioteca VNDK:
-
header-abi-dumper
procesa los archivos fuente compilados para construir la biblioteca VNDK (los archivos fuente propios de la biblioteca, así como los archivos fuente heredados a través de dependencias transitivas estáticas), para producir archivos.sdump
que corresponden a cada fuente.Figura 1. Creando los archivos .sdump
-
header-abi-linker
luego procesa los archivos.sdump
(usando un script de versión proporcionado o el archivo.so
correspondiente a la biblioteca compartida) para producir un archivo.lsdump
que registra toda la información ABI correspondiente a la biblioteca compartida.Figura 2. Creando el archivo .lsdump
-
header-abi-diff
compara el archivo.lsdump
con un archivo.lsdump
de referencia para producir un informe de diferencias que describe las diferencias en las ABI de las dos bibliotecas.Figura 3. Creación del informe de diferencias
encabezado-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 crea una biblioteca que incluye los archivos fuente de las dependencias transitivas.
Entradas |
|
---|---|
Producción | 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, por lo que no se garantiza que sean estables en futuras versiones. Como tal, el formato del 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 el ABI presentado por el archivo fuente usando:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Este comando le dice a header-abi-dumper
que analice foo.cpp
con los indicadores del compilador siguientes --
y emita la información ABI que exportan los encabezados públicos en el directorio exported
. El siguiente es 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 ABI exportada por el archivo fuente foo.cpp
y los encabezados públicos, por ejemplo,
-
record_types
. Consulte estructuras, uniones o clases definidas en los encabezados públicos. Cada tipo de registro tiene información sobre sus campos, su tamaño, especificador de acceso, el archivo de encabezado en el que está definido y otros atributos. -
pointer_types
. Consulte los tipos de puntero a los que hacen referencia directa/indirectamente los registros/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 tipos calificados, tipos C/C++ integrados, tipos de matriz y tipos de referencia lvalue y rvalue. Esta información permite diferencias recursivas. -
functions
. Representa funciones exportadas por encabezados públicos. También tienen información sobre el nombre alterado de la función, el tipo de retorno, los tipos de parámetros, el especificador de acceso y otros atributos.
encabezado-abi-linker
La herramienta header-abi-linker
toma los archivos intermedios producidos por header-abi-dumper
como entrada y luego vincula esos archivos:
Entradas |
|
---|---|
Producción | Un archivo que describe la ABI de una biblioteca compartida (por ejemplo, libfoo.so.lsdump representa la ABI de libfoo ). |
La herramienta fusiona los gráficos de tipos en todos los archivos intermedios que se le proporcionan, teniendo en cuenta las diferencias de una definición (tipos definidos por el usuario en diferentes unidades de traducción con el mismo nombre completo, pueden ser semánticamente diferentes) entre las unidades de traducción. Luego, la herramienta analiza un script 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 header-abi-linker
para crear el volcado 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 salida 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
), filtrando la información ABI que no está presente en los encabezados que residen en el directorio:exported
. - Analiza
libfoo.so
y recopila información sobre los símbolos exportados por la biblioteca a través de su tabla.dynsym
. - Agrega
_Z3FooiP3bar
y_Z6FooBadiP3foo
.
libfoo.so.lsdump
es el volcado ABI final generado de libfoo.so
.
encabezado-abi-diff
La herramienta header-abi-diff
compara dos archivos .lsdump
que representan la ABI de dos bibliotecas y genera un informe de diferencias que indica las diferencias entre las dos ABI.
Entradas |
|
---|---|
Producción | Un informe de diferencias que indica las diferencias en las ABI ofrecidas por las dos bibliotecas compartidas comparadas. |
El archivo ABI diff está en formato de texto protobuf . El formato está sujeto a cambios en futuras versiones.
Por ejemplo, tiene dos versiones de libfoo
: libfoo_old.so
y libfoo_new.so
. En libfoo_new.so
, en bar_t
, cambia el tipo de mfoo
de foo_t
a foo_t *
. Dado que bar_t
es un tipo accesible, esto debe marcarse como un cambio importante de ABI mediante header-abi-diff
.
Para ejecutar header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Ejemplo de salida 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 importantes de ABI en libfoo
. El mensaje record_type_diffs
indica que un registro ha cambiado y enumera los cambios incompatibles, que incluyen:
- El tamaño del registro cambia de
24
bytes a8
bytes. - El tipo de campo de
mfoo
cambia defoo
afoo *
(se eliminan todos los tipos de definición).
El campo type_stack
indica cómo header-abi-diff
alcanzó el tipo que cambió ( bar
). Este campo puede interpretarse como que Foo
es una función exportada que toma bar *
como parámetro, que apunta a bar
, que fue exportado y modificado.
Hacer cumplir ABI/API
Para hacer cumplir la ABI/API de las bibliotecas compartidas de VNDK, las referencias de ABI deben registrarse en ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
. Para crear estas referencias, ejecute el siguiente comando:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Después de crear las referencias, cualquier cambio realizado en el código fuente que resulte en un cambio ABI/API incompatible en una biblioteca VNDK ahora resulta en un error de compilación.
Para actualizar las referencias de ABI para bibliotecas específicas, ejecute 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 ABI libbinder
, ejecute:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder