Estabilidad de la ABI

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

Para ayudar a garantizar la compatibilidad de la ABI, Android 9 incluye un verificador de 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 se pueden vincular los módulos del proveedor y que permiten actualizaciones solo del framework. La compatibilidad con la ABI se refiere a la capacidad de una versión más reciente de una biblioteca compartida para funcionar según lo esperado 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).

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 exporta mediante los encabezados públicos de una biblioteca compartida.
  • Aparece en la tabla .dynsym del archivo .so correspondiente a la biblioteca compartida.
  • Tiene una vinculación WEAK o GLOBAL.
  • La visibilidad es DEFAULT o PROTECTED.
  • El índice de 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 archivos 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 de Android.bp del módulo correspondiente a la biblioteca compartida.

Acerca de los tipos de elementos alcanzables

Un tipo accesible es cualquier tipo integrado o definido por el usuario de C/C++ al que se puede acceder de forma directa o indirecta a través de un símbolo exportado Y que se exporta 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 destinos directos o indirectos a los que se puede llegar incluyen los siguientes:

Tipo Descripción
bool Es el tipo de datos que devuelve Foo.
int Es el tipo del primer parámetro de Foo.
bar_t * Es el tipo del segundo parámetro de 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:
  • 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 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).

También 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 parámetros de plantilla.

Garantiza 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 se puede acceder directa o indirectamente a través de una función exportada, los siguientes cambios en una biblioteca se clasifican como incompatibles con la ABI:

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

* No se deben cambiar ni quitar las funciones miembro públicas y privadas, ya que las funciones insertadas públicas pueden hacer referencia a las funciones miembro privadas. Las referencias de símbolos a funciones miembro privadas se pueden conservar en los archivos binarios del llamador. Cambiar o quitar funciones miembro privadas de bibliotecas compartidas puede generar archivos binarios incompatibles con versiones anteriores.

** Los desplazamientos a los miembros de datos públicos o privados no deben cambiarse, ya que las funciones intercaladas pueden hacer referencia a estos miembros de datos en el cuerpo de la función. Cambiar las compensaciones 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 según lo esperado.

Usa herramientas de cumplimiento de la 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 está compilando. 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, 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 interrupción de ABI

En el caso de los errores de ABI, el registro de compilación muestra advertencias con el tipo de advertencia y una ruta de acceso al informe de 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 ----

Compila verificaciones de ABI de la biblioteca del VNDK

Cuando se compila una biblioteca del VNDK, sucede lo siguiente:

  1. header-abi-dumper procesa los archivos fuente compilados para compilar la biblioteca del VNDK (los archivos fuente propios de la biblioteca y los archivos fuente heredados a través de dependencias transitivas estáticas) para producir archivos .sdump que corresponden a cada fuente.
    Creación de sdump
    Figura 1. Crea los archivos .sdump
  2. Luego, header-abi-linker procesa los archivos .sdump (con una secuencia de comandos de versión que se le proporciona o el archivo .so correspondiente a la biblioteca compartida) para generar un archivo .lsdump que registra toda la información de la ABI correspondiente a la biblioteca compartida.
    Creación de lsdump
    Figura 2: Crea el 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 las ABI de las dos bibliotecas.
    Creación de diff de ABI
    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
  • Un archivo fuente C/C++
  • Directorios de inclusión exportados
  • Marcas del compilador
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 se debe considerar 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 el siguiente comando:

$ 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 que 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 la ABI que exporta el archivo fuente foo.cpp y los encabezados públicos, por ejemplo,

  • record_types. Consulta las structs, las uniones o las 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: Se refiere a los tipos de puntero 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 campo referenced_type en type_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 lvalue y rvalue. Esta información permite realizar comparaciones recursivas.
  • functions: Representa las funciones exportadas por los encabezados públicos. También tienen información sobre el nombre codificado de la función, el tipo de devolución, los tipos de los 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
  • Archivos intermedios que produce header-abi-dumper
  • Secuencia de comandos de versión o archivo de mapa (opcional)
  • Archivo .so 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 proporcionan, teniendo en cuenta las diferencias de una sola definición (los tipos definidos por el usuario en diferentes unidades de traducción con el mismo nombre completo pueden ser semánticamente diferentes) en todas las unidades de traducción. 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 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 puede realizar las siguientes acciones:

  • Vincula los archivos .sdump proporcionados (foo.sdump y bar.sdump), y filtra 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 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 genera un informe de diferencias que indica las diferencias entre las dos ABIs.

Entradas
  • Archivo .lsdump que representa la ABI de una biblioteca compartida anterior.
  • 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 las dos bibliotecas compartidas comparadas.

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, cambia el tipo de mfoo de foo_t a foo_t *. Dado que bar_t es un tipo accesible, header-abi-diff debería marcar esto 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
    }
  }
}

El archivo libfoo.so.abidiff contiene un informe de todos los cambios que interrumpen la ABI en libfoo. El mensaje record_type_diffs indica que se cambió un registro y enumera los cambios incompatibles, que incluyen los siguientes:

  • El tamaño del registro cambia de 24 bytes a 8 bytes.
  • El tipo de campo de mfoo cambia de foo a foo * (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ó.

Aplicar la ABI y la API

Para aplicar la ABI y la API de las bibliotecas compartidas del VNDK, se deben registrar las referencias de la ABI 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 y que genere un cambio incompatible en la ABI o la API de una biblioteca del 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 la ABI de libbinder, ejecuta lo siguiente:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder