Estabilidad de la ABI

La estabilidad de la interfaz binaria de la aplicación (ABI) es un requisito previo actualizaciones exclusivas del framework, ya que los módulos de proveedores pueden depender del proveedor de código nativo Bibliotecas compartidas del kit de desarrollo (VNDK) que residen en la partición del sistema. En una versión de Android, las bibliotecas compartidas del VNDK recién compiladas deben Es compatible con ABI en bibliotecas compartidas para el VNDK, que se lanzaron anteriormente, por lo que los módulos de proveedores puede trabajar con esas bibliotecas sin recompilación y sin errores de tiempo de ejecución. Entre las versiones de Android, se pueden cambiar las bibliotecas de VNDK y no hay ABI. garantías.

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 restrictivo de bibliotecas a las que se pueden vincular los módulos de proveedores. que permiten actualizaciones solo del framework. El cumplimiento de las ABI se refiere al capacidad de una versión más reciente de una biblioteca compartida para funcionar como se espera con una que esté vinculado de forma dinámica a él (es decir, que funcione como una versión anterior del biblioteca).

Acerca de los símbolos exportados

Un símbolo exportado (también conocido como símbolo global) hace referencia a lo siguiente: un símbolo que cumpla con los siguientes requisitos:

  • 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 DÉBIL o GLOBAL.
  • La visibilidad es DEFAULT o PROTECTED.
  • El índice de la sección no está SIN DEFINIR.
  • El tipo es FUNC u OBJECT.

Los encabezados públicos de una biblioteca compartida se definen como los encabezados que están disponibles para otras bibliotecas o objetos binarios export_include_dirs, export_header_lib_headers export_static_lib_headers, export_shared_lib_headers y Atributos export_generated_headers en Android.bp del módulo correspondiente a la biblioteca compartida.

Acerca de los tipos accesibles

Un tipo accesible es cualquier tipo C/C++ integrado o definido por el usuario que se se puede acceder de forma directa o indirecta mediante un símbolo exportado Y exportado a través de encabezados públicos. Por ejemplo, libfoo.so tiene una función Foo, que es un símbolo exportado que se encuentra en el Tabla .dynsym. La biblioteca libfoo.so incluye lo siguiente: 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 Tipo de datos que se muestra de Foo.
int Es el tipo del primer parámetro Foo.
bar_t * Tipo del segundo parámetro Foo. Por bar_t *, bar_t se exporta a través de foo_exported.h.

bar_t contiene un miembro mfoo, del tipo foo_t, que se exporta a través de foo_exported.h, lo que da como resultado la exportación de más tipos:
  • int : es el tipo de m1.
  • int * : es el tipo de m2.
  • foo_private_t * : es el tipo de mPfoo.

Sin embargo, NO se puede acceder a foo_private_t porque no es se exportan a través de foo_exported.h. (foo_private_t *) es opaca, 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 mediante la clase básica. especificadores y parámetros de plantilla.

Cómo garantizar el cumplimiento de ABI

Se debe garantizar el cumplimiento con la ABI para las bibliotecas marcadas vendor_available: true y vndk.enabled: true en la 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 puede acceder una función exportada directa o indirectamente, Los siguientes cambios en una biblioteca se clasifican como roturas de ABI:

Tipo de datos Descripción
Estructuras y clases
  • Cambia el tamaño del tipo de clase o el tipo de struct.
  • Clases básicas
    • Agrega o quita clases base.
    • Agrega o quita clases base heredadas de forma virtual.
    • Cambiar el orden de las clases básicas
  • Funciones de los miembros
    • Quita las funciones de miembros.*
    • Agrega o quita argumentos de las funciones de los miembros.
    • Cambia los tipos de argumento o los tipos que se muestran de miembro funciones.*
    • Cambiar el diseño de la tabla virtual
  • Miembros de datos
    • Quita miembros de datos estáticos.
    • Agrega o quita miembros de datos no estáticos.
    • Cambia los tipos de miembros de datos.
    • Cambia las compensaciones en miembros de datos no estáticos**.
    • Cambia const, volatile o restricted calificadores de miembros de datos***.
    • Cambiar a una versión inferior los especificadores de acceso de los miembros de datos***
  • Cambia los argumentos de la plantilla.
Uniones
  • Agregar o quitar miembros de datos
  • Cambia el tamaño del tipo de unión.
  • Cambia los tipos de miembros de datos.
Enumeraciones
  • Cambiar el tipo subyacente
  • Cambia los nombres de los enumeradores.
  • Cambia los valores de los enumeradores.
Símbolos globales
  • Quita los símbolos exportados por encabezados públicos.
  • Para los símbolos globales del tipo FUNC
    • Agrega o quita argumentos.
    • Cambia los tipos de argumento.
    • Cambia el tipo de datos que se muestra.
    • Cambiar el especificador de acceso a una versión inferior***
  • Para símbolos globales de tipo OBJECT
    • Cambia el tipo de C/C++ correspondiente.
    • Cambiar el especificador de acceso a una versión inferior***

* Las funciones de miembro públicas y privadas deben no se pueden cambiar ni quitar porque las funciones públicas intercaladas pueden hacer referencia funciones de miembro privado. Las referencias de símbolos para las funciones de miembros privados se mantienen en los objetos binarios del llamador. Cambia o quita las funciones privadas de los miembros de las bibliotecas compartidas puede generar objetos binarios incompatibles con versiones anteriores.

** Las compensaciones para los miembros de datos públicos o privados no deben porque las funciones intercaladas pueden referirse a estos miembros de datos en sus cuerpo de la función. Cambiar los desplazamientos de los miembros de datos puede generar objetos binarios incompatibles con versiones anteriores.

*** Aunque estos valores no cambian el diseño de la memoria del tipo, existen diferencias semánticas que podrían llevar a que las bibliotecas no funcionen como se espera.

Usa herramientas de cumplimiento de ABI

Cuando se compila una biblioteca de VNDK, se compara su ABI con el referencia de ABI correspondiente a la versión del VNDK que se está compilando. Referencias Los volcados de ABI se encuentran en:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based

Por ejemplo, cuando compilas 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 falla de la ABI

Cuando se producen fallas de ABI, el registro de compilación muestra advertencias con el tipo de advertencia y una la 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 mensaje de error 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 ----

Cómo compilar verificaciones de ABI de la biblioteca del VNDK

Cuando se compila una biblioteca de VNDK:

  1. header-abi-dumper procesa los archivos de origen compilados en compilar la biblioteca VNDK (los archivos de origen propios de la biblioteca, además de los archivos de origen) a través de dependencias transitivas estáticas), para producir Archivos .sdump que corresponden a cada fuente.
    creación de sdump
    Figura 1: Cómo crear .sdump archivos
  2. Luego, header-abi-linker procesa la .sdump. archivos (mediante una secuencia de comandos de versión que se le proporcionó o el comando .so correspondiente a la biblioteca compartida) para producir un .lsdump que registra toda la información de ABI correspondiente a la biblioteca compartida.
    Creación de lsdump
    Figura 2: Cómo crear .lsdump archivo
  3. header-abi-diff compara el .lsdump Archivo con un archivo .lsdump de referencia para producir un informe de diferencias que describe las diferencias en las ABI de ambas bibliotecas.
    creación de diferencias de abi
    Figura 3: Crea el informe de diferencias

volador de encabezado abi

La herramienta header-abi-dumper analiza un archivo de origen C/C++ y lo vuelca. la ABI que se infiere de ese archivo de origen a un archivo intermedio. La compilación el sistema ejecuta header-abi-dumper en todos los archivos de origen compilados mientras y también compilar una biblioteca que incluya los archivos de origen de la dependencias.

Entradas
  • Un archivo fuente C/C++
  • Directorios de inclusión exportados
  • Marcas del compilador
Salida Es un archivo que describe la ABI del archivo de origen (por ejemplo, foo.sdump representa la ABI de foo.cpp).

Actualmente, los archivos .sdump están en formato JSON, y no es así y garantizar la estabilidad en versiones futuras. Por lo tanto, .sdump el formato de archivo debe considerarse un detalle de la 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 nivel intermedio archivo .sdump que representa la ABI que presenta el archivo fuente usando:

$ 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 después de -- emiten la información de ABI exportada por los encabezados públicos en el exported. Lo 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 de ABI exportada por el archivo de origen. foo.cpp y los encabezados públicos, por ejemplo,

  • record_types Hacer referencia a structs, uniones o clases definidas en los encabezados públicos. Cada tipo de registro tiene información sobre sus campos, sus tamaño, especificador de acceso, el archivo de encabezado en el que está definido y otros atributos.
  • pointer_types Cómo hacer referencia a los tipos de puntero de forma directa o indirecta a los que se hace referencia en los registros o funciones exportados en los encabezados públicos, junto con con el tipo al que apunta el puntero (mediante la referenced_type en type_info). Se registra información similar en el Archivo .sdump para tipos calificados, tipos C/C++ integrados, array tipos de referencia, y tipos de referencia lvalue y rvalue. Esa información permite diffing recursivo.
  • functions Representa funciones exportadas por encabezados públicos. También tienen información sobre el nombre alterado de la función, el tipo de datos que se muestra los tipos de parámetros, el especificador de acceso y otros atributos.

Vinculador de encabezados abi

La herramienta header-abi-linker toma los archivos intermedios producidos de header-abi-dumper como entrada y, luego, vincula esos archivos:

Entradas
  • Archivos intermedios producidos por header-abi-dumper
  • Secuencia de comandos de la versión/archivo de mapa (opcional)
  • .so archivo de la biblioteca compartida
  • Directorios de inclusión exportados
Salida Es 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 proporcionaron. considerar una definición (tipos definidos por el usuario en diferentes unidades de traducción con el mismo nombre completamente calificado, podrían diferentes) entre las unidades de traducción. Luego, la herramienta analiza una secuencia de comandos de la 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 header-abi-linker podría invocarse para Crea el volcado completo de ABI vinculado 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 realiza lo siguiente:

  • Vincula los archivos .sdump proporcionados (foo.sdump y bar.sdump), filtrando la información de ABI que no está presente en los encabezados que se encuentran en el directorio: exported.
  • Analiza libfoo.so y recopila información sobre los símbolos. que la biblioteca exporta 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

encabezado-abi-diferencia

La herramienta header-abi-diff compara dos archivos .lsdump que representa la ABI de dos bibliotecas y produce un informe de diferencias que indica la entre las dos ABI.

Entradas
  • Archivo .lsdump que representa la ABI de una antigua red compartida biblioteca.
  • Archivo .lsdump que representa la ABI de una nueva biblioteca compartida.
Salida Un informe de diferencias que indica las diferencias en las ABI que ofrecen ambos bibliotecas compartidas.

El archivo de diferencias de ABI se encuentra Formato de texto 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 accesible, este debe marcarse como un cambio rotundo de ABI 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 todas las fallas de ABI. cambios en libfoo. El mensaje record_type_diffs indica que un registro cambió y enumera los cambios incompatibles, incluyen:

  • El tamaño del registro que cambia de 24 bytes a 8 bytes.
  • El tipo de campo mfoo cambia de foo a foo * (se quitan todos los typedefs).

El campo type_stack indica cómo header-abi-diff alcanzó el tipo que cambió (bar). Este campo puede ser se interpreta como Foo es una función exportada que incorpora bar * como parámetro, que apunta a bar, que era exportarse y cambiarse.

Cómo aplicar ABI y API

Para aplicar la ABI y la API de las bibliotecas compartidas del VNDK, las referencias de ABI deben se anunciará 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 realizado en el código fuente que resulte en un cambio incompatible de ABI/API en una biblioteca 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