Estabilidad del ITB

La estabilidad de la interfaz binaria de la aplicación (ABI) es un requisito previo de las actualizaciones solo del 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 de VNDK recién creadas deben ser compatibles con ABI con las bibliotecas compartidas de VNDK lanzadas anteriormente para que los módulos del proveedor puedan trabajar con esas bibliotecas sin volver a compilar y sin errores de tiempo de ejecución. Entre las versiones de Android, las bibliotecas de VNDK se pueden cambiar y no hay garantías de ABI.

Para ayudar a garantizar la compatibilidad con ABI, Android 9 incluye un verificador de encabezado ABI, 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 se pueden vincular los módulos de los proveedores 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 con 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á SIN DEFINIR.
  • 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 de C/C++ o definido por el usuario al que se puede 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.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 cuanto a Foo , los tipos accesibles directos/indirectos incluyen:

Tipo Descripción
bool Tipo de retorno de Foo .
int Tipo del primer parámetro Foo .
bar_t * Tipo de segundo parámetro Foo. Por medio 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:
  • 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 foo_private_t porque no se exporta a través de foo_exported.h . ( foo_private_t * es opaco, por lo tanto, se permiten los cambios realizados en foo_private_t ).

Se puede dar una explicación similar para los tipos a los que se puede acceder a través de especificadores de clase base y también de 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 accesibles directa o indirectamente por una función exportada, los siguientes cambios en una biblioteca se clasifican como ruptura de ABI:

Tipo de datos Descripción
Estructuras y Clases
  • Cambie el tamaño del tipo de clase o el tipo de estructura.
  • Clases base
    • Agregar o eliminar clases base.
    • Agregue o elimine clases base heredadas virtualmente.
    • Cambiar el orden de las clases base.
  • Funciones de los miembros
    • Eliminar funciones miembro*.
    • Agregue o elimine argumentos de las funciones miembro.
    • Cambie los tipos de argumentos o los tipos de retorno de las funciones miembro*.
    • Cambiar el diseño de la mesa virtual.
  • Miembros de datos
    • Eliminar miembros de datos estáticos.
    • Agregue o elimine miembros de datos no estáticos.
    • Cambie los tipos de miembros de datos.
    • Cambie los desplazamientos a miembros de datos no estáticos**.
    • Cambie los calificadores const , volatile y/o restricted de los miembros de datos***.
    • Rebaje los especificadores de acceso de los miembros de datos***.
  • Cambie los argumentos de la plantilla.
sindicatos
  • Agregar o eliminar miembros de datos.
  • Cambie el tamaño del tipo de unión.
  • Cambie los tipos de miembros de datos.
enumeraciones
  • Cambie el tipo subyacente.
  • Cambiar los nombres de los enumeradores.
  • Cambiar los valores de los enumeradores.
Símbolos globales
  • Elimina los símbolos exportados por los encabezados públicos.
  • Para símbolos globales de tipo FUNC
    • Agregar o eliminar argumentos.
    • Cambiar los tipos de argumento.
    • Cambiar el tipo de devolución.
    • Degradar el especificador de acceso***.
  • Para símbolos globales de tipo OBJECT
    • Cambie el tipo de C/C++ correspondiente.
    • Degradar el especificador de acceso***.

* Las funciones de miembros públicas y privadas no deben cambiarse ni eliminarse porque las funciones en línea públicas pueden hacer referencia a funciones de miembros privadas. Las referencias de símbolos a funciones de miembros privados se pueden mantener en binarios de llamantes. Cambiar o eliminar funciones de miembros privados de bibliotecas compartidas puede generar archivos binarios incompatibles con versiones anteriores.

** Las compensaciones para miembros de datos públicos o privados no deben cambiarse porque las funciones en línea pueden hacer referencia a estos miembros de datos en el cuerpo de su función. Cambiar los desplazamientos de los miembros de datos puede generar archivos 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 hacer que las bibliotecas no funcionen como se esperaba.

Uso de las herramientas de cumplimiento de ABI

Cuando se compila una biblioteca VNDK, la ABI de la biblioteca se compara con la referencia ABI correspondiente para la versión del VNDK que se está compilando. Los volcados 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 de 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 de ABI

En 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 verificaciones ABI de la biblioteca VNDK

Cuando se crea una biblioteca VNDK:

  1. header-abi-dumper procesa los archivos fuente compilados para crear 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 correspondan a cada fuente.
    sdump creation
    Figura 1. Creando los archivos .sdump
  2. 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.
    lsdump creation
    Figura 2. Creación del archivo .lsdump
  3. header-abi-diff compara el archivo .lsdump con un archivo .lsdump de referencia para generar un informe de diferencias que describe las diferencias en los ABI de las dos bibliotecas.
    abi diff creation
    Figura 3. Creación del informe de diferencias

encabezado-abi-dumper

La herramienta header-abi-dumper analiza un archivo fuente C/C++ y vuelca el ABI inferido de ese archivo fuente en un archivo intermedio. El sistema de compilación ejecuta header-abi-dumper en todos los archivos de origen compilados al mismo tiempo que crea una biblioteca que incluye los archivos de origen de las dependencias transitivas.

Entradas
  • Archivo fuente AC/C++
  • Directorios de inclusión exportados
  • Indicadores del compilador
Producción 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, por lo que no se garantiza que sea estable en versiones futuras. Como tal, 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;
}

Puede 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 después de -- y emita la información de 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, el especificador de acceso, el archivo de encabezado en el que está definido y otros atributos.
  • pointer_types . Haga referencia a los tipos de puntero a los que hacen referencia directa o indirectamente los registros o funciones exportados en los encabezados públicos, junto con el tipo al que apunta el puntero (a través del campo referenced_type en type_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 la diferenciación recursiva.
  • functions Representa funciones exportadas por encabezados públicos. También tienen información sobre el nombre alterado de la función, el tipo de devolución, los tipos de parámetros, el especificador de acceso y otros atributos.

encabezado-abi-enlazador

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

Entradas
  • Archivos intermedios producidos por header-abi-dumper
  • Script de versión/archivo de mapa (opcional)
  • archivo .so de la biblioteca compartida
  • Directorios de inclusión exportados
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 (los 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 hacer una lista de los símbolos exportados.

Por ejemplo, libfoo consta de foo.cpp y bar.cpp . header-abi-linker podría invocarse 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 de 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 y bar.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 el ABI de dos bibliotecas y genera un informe de diferencias que indica las diferencias entre los dos ABI.

Entradas
  • Archivo .lsdump que representa la ABI de una biblioteca compartida antigua.
  • Archivo .lsdump que representa la ABI de una nueva biblioteca compartida.
Producción Un informe de diferencias que indica las diferencias en las ABI que ofrecen las dos bibliotecas compartidas comparadas.

El archivo ABI diff está en formato de texto protobuf . El formato está sujeto a cambios en versiones futuras.

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, debe marcarse como un cambio de ruptura de ABI por 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 de 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
    }
  }
}

El 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 a 8 bytes.
  • El tipo de campo de mfoo cambia de foo a foo * (todas las definiciones de tipo se eliminan).

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.

Aplicación de ABI/API

Para hacer cumplir la ABI/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, 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 de ABI/API incompatible en una biblioteca VNDK ahora genera 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