Stabilité des ABI

La stabilité de l'interface binaire d'application (ABI) est une condition préalable aux mises à jour du framework uniquement, car les modules du fournisseur peuvent dépendre des bibliothèques partagées du kit de développement natif du fournisseur (VNDK) qui résident dans la partition système. Dans une version Android, les bibliothèques partagées VNDK nouvellement créées doivent être ABI-compatibles avec les bibliothèques partagées VNDK publiées précédemment afin que les modules du fournisseur puissent fonctionner avec ces bibliothèques sans recompilation ni erreurs d'exécution. Entre les versions d'Android, les bibliothèques VNDK peuvent être modifiées et il n'y a aucune garantie ABI.

Pour garantir la compatibilité de l'ABI, Android 9 inclut un vérificateur d'ABI d'en-tête, comme décrit dans les sections suivantes.

À propos de la conformité VNDK et ABI

Le VNDK est un ensemble restrictif de bibliothèques auxquelles les modules du fournisseur peuvent être associés et qui permettent des mises à jour du framework uniquement. La compatibilité ABI fait référence à la capacité d'une version plus récente d'une bibliothèque partagée à fonctionner comme prévu avec un module qui y est lié de manière dynamique (c'est-à-dire comme le ferait une ancienne version de la bibliothèque).

À propos des symboles exportés

Un symbole exporté (également appelé symbole global) désigne un symbole qui répond à toutes les conditions suivantes :

  • Exporté par les en-têtes publics d'une bibliothèque partagée.
  • Apparaît dans le tableau .dynsym du fichier .so correspondant à la bibliothèque partagée.
  • Comporte une liaison WEAK ou GLOBAL.
  • La visibilité est définie sur "DEFAULT" (PAR DÉFAUT) ou "PROTECTED" (PROTÉGÉE).
  • L'index de section n'est pas UNDEFINED.
  • Le type est FUNC ou OBJECT.

Les en-têtes publics d'une bibliothèque partagée sont définis comme les en-têtes disponibles pour d'autres bibliothèques/binaires via les attributs export_include_dirs, export_header_lib_headers, export_static_lib_headers, export_shared_lib_headers et export_generated_headers dans les définitions Android.bp du module correspondant à la bibliothèque partagée.

À propos des types de couverture

Un type accessible est un type C/C++ intégré ou défini par l'utilisateur qui est accessible directement ou indirectement via un symbole exporté ET exporté via des en-têtes publics. Par exemple, libfoo.so possède la fonction Foo, qui est un symbole exporté trouvé dans le tableau .dynsym. La bibliothèque libfoo.so inclut les éléments suivants :

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"
  ],
}
Table .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 examinant Foo, les types accessibles directement/indirectement incluent :

Saisie Description
bool Type de retour de Foo.
int Type du premier paramètre Foo.
bar_t * Type du deuxième paramètre Foo. Par le biais de bar_t *, bar_t est exporté via foo_exported.h.

bar_t contient un membre mfoo, de type foo_t, qui est exporté via foo_exported.h, ce qui entraîne l'exportation d'un plus grand nombre de types :
  • int : correspond au type de m1.
  • int * : correspond au type de m2.
  • foo_private_t * : correspond au type de mPfoo.

Toutefois, foo_private_t n'est PAS accessible, car il n'est pas exporté via foo_exported.h. (foo_private_t * est opaque. Les modifications apportées à foo_private_t sont donc autorisées.)

Une explication similaire peut être donnée pour les types accessibles via les spécificateurs de classe de base et les paramètres de modèle.

Assurer la conformité de l'ABI

La compatibilité ABI doit être assurée pour les bibliothèques marquées vendor_available: true et vndk.enabled: true dans les fichiers Android.bp correspondants. Exemple :

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}

Pour les types de données accessibles directement ou indirectement par une fonction exportée, les modifications suivantes apportées à une bibliothèque sont classées comme incompatibles avec l'ABI :

Type de données Description
Structures et classes
  • Modifiez la taille du type de classe ou du type de struct.
  • Classes de base
    • Ajoutez ou supprimez des classes de base.
    • Ajoutez ou supprimez des classes de base héritées virtuellement.
    • Modifiez l'ordre des classes de base.
  • Fonctions membres
    • Supprimez les fonctions membres*.
    • Ajoutez ou supprimez des arguments des fonctions membres.
    • Modifiez les types d'arguments ou les types renvoyés des fonctions membres*.
    • Modifiez la mise en page de la table virtuelle.
  • Membres de données
    • Supprimez les membres de données statiques.
    • Ajoutez ou supprimez des membres de données non statiques.
    • Modifiez les types de membres de données.
    • Modifiez les décalages pour qu'ils correspondent à des membres de données non statiques**.
    • Modifiez les qualificateurs const, volatile et/ou restricted des membres de données***.
    • Rétrograder les spécificateurs d'accès des membres de données***.
  • Modifiez les arguments du modèle.
Syndicats
  • Ajoutez ou supprimez des membres de données.
  • Modifiez la taille du type union.
  • Modifiez les types de membres de données.
Énumérations
  • Modifiez le type sous-jacent.
  • Modifiez les noms des énumérateurs.
  • Modifiez les valeurs des énumérateurs.
Symboles globaux
  • Supprimez les symboles exportés par les en-têtes publics.
  • Pour les symboles globaux de type FUNC
    • Ajoutez ou supprimez des arguments.
    • Modifier les types d'arguments.
    • Modifiez le type de retour.
    • Rétrograder le spécificateur d'accès***.
  • Pour les symboles globaux de type OBJECT
    • Modifiez le type C/C++ correspondant.
    • Rétrograder le spécificateur d'accès***.

* Les fonctions membres publiques et privées ne doivent pas être modifiées ni supprimées, car les fonctions inline publiques peuvent faire référence à des fonctions membres privées. Les références de symboles aux fonctions membres privées peuvent être conservées dans les binaires de l'appelant. La modification ou la suppression de fonctions membres privées dans des bibliothèques partagées peut entraîner des binaires incompatibles avec les versions antérieures.

** Les décalages des membres de données publics ou privés ne doivent pas être modifiés, car les fonctions intégrées peuvent faire référence à ces membres de données dans le corps de leur fonction. La modification des décalages des membres de données peut entraîner des binaires incompatibles avec les versions antérieures.

*** Bien que ces modifications ne changent pas la disposition de la mémoire du type, il existe des différences sémantiques qui pourraient entraîner un dysfonctionnement des bibliothèques.

Utiliser les outils de conformité ABI

Lorsqu'une bibliothèque VNDK est créée, son ABI est comparée à la référence ABI correspondante pour la version du VNDK en cours de création. Les dumps ABI de référence se trouvent dans :

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

Par exemple, lors de la compilation de libfoo pour x86 au niveau d'API 27, l'ABI inférée de libfoo est comparée à sa référence à l'adresse suivante :

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

Erreur de rupture de l'ABI

En cas de rupture de l'ABI, le journal de compilation affiche des avertissements avec le type d'avertissement et un chemin d'accès au rapport abi-diff. Par exemple, si l'ABI de libbinder présente une modification incompatible, le système de compilation génère une erreur avec un message semblable à celui-ci :

*****************************************************
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 ----

Compiler les vérifications de l'ABI de la bibliothèque VNDK

Lorsqu'une bibliothèque VNDK est créée :

  1. header-abi-dumper traite les fichiers sources compilés pour créer la bibliothèque VNDK (les fichiers sources de la bibliothèque elle-même ainsi que les fichiers sources hérités via des dépendances transitives statiques) afin de produire des fichiers .sdump qui correspondent à chaque source.
    Création de sdump
    Figure 1. Créer les fichiers .sdump
  2. header-abi-linker traite ensuite les fichiers .sdump (à l'aide d'un script de version qui lui est fourni ou du fichier .so correspondant à la bibliothèque partagée) pour produire un fichier .lsdump qui enregistre toutes les informations ABI correspondant à la bibliothèque partagée.
    Création de lsdump
    Figure 2 : Créer le fichier .lsdump
  3. header-abi-diff compare le fichier .lsdump avec un fichier .lsdump de référence pour générer un rapport diff qui décrit les différences dans les ABI des deux bibliothèques.
    Création de diff ABI
    Figure 3 : Créer le rapport de comparaison

header-abi-dumper

L'outil header-abi-dumper analyse un fichier source C/C++ et transfère l'ABI déduite de ce fichier source dans un fichier intermédiaire. Le système de compilation exécute header-abi-dumper sur tous les fichiers sources compilés tout en créant une bibliothèque qui inclut les fichiers sources des dépendances transitives.

Entrées
  • Fichier source C/C++
  • Répertoires include exportés
  • Indicateurs du compilateur
Sortie Fichier décrivant l'ABI du fichier source (par exemple, foo.sdump représente l'ABI de foo.cpp).

Actuellement, les fichiers .sdump sont au format JSON, qui n'est pas garanti d'être stable dans les futures versions. Par conséquent, la mise en forme des fichiers .sdump doit être considérée comme un détail d'implémentation du système de compilation.

Par exemple, libfoo.so possède le fichier source suivant : 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;
}

Vous pouvez utiliser header-abi-dumper pour générer un fichier .sdump intermédiaire qui représente l'ABI présentée par le fichier source à l'aide de :

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++

Cette commande indique à header-abi-dumper d'analyser foo.cpp avec les indicateurs du compilateur suivant -- et d'émettre les informations ABI exportées par les en-têtes publics dans le répertoire exported. Voici foo.sdump généré par 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 contient les informations ABI exportées par le fichier source foo.cpp et les en-têtes publics, par exemple :

  • record_types. Consultez les structs, unions ou classes définis dans les en-têtes publics. Chaque type d'enregistrement contient des informations sur ses champs, sa taille, son spécificateur d'accès, le fichier d'en-tête dans lequel il est défini et d'autres attributs.
  • pointer_types. Fait référence aux types de pointeur directement/indirectement référencés par les enregistrements/fonctions exportés dans les en-têtes publics, ainsi qu'au type vers lequel pointe le pointeur (via le champ referenced_type dans type_info). Des informations similaires sont consignées dans le fichier .sdump pour les types qualifiés, les types C/C++ intégrés, les types de tableau et les types de référence lvalue et rvalue. Ces informations permettent d'effectuer des comparaisons récursives.
  • functions. Représente les fonctions exportées par les en-têtes publics. Ils contiennent également des informations sur le nom déformé de la fonction, le type de retour, les types de paramètres, le spécificateur d'accès et d'autres attributs.

header-abi-linker

L'outil header-abi-linker prend en entrée les fichiers intermédiaires produits par header-abi-dumper, puis les associe :

Entrées
  • Fichiers intermédiaires produits par header-abi-dumper
  • Script de version/Fichier de mappage (facultatif)
  • Fichier .so de la bibliothèque partagée
  • Répertoires include exportés
Sortie Fichier décrivant l'ABI d'une bibliothèque partagée (par exemple, libfoo.so.lsdump représente l'ABI de libfoo).

L'outil fusionne les graphiques de type dans tous les fichiers intermédiaires qui lui sont fournis, en tenant compte des différences de définition (les types définis par l'utilisateur dans différentes unités de traduction portant le même nom complet peuvent être sémantiquement différents) entre les unités de traduction. L'outil analyse ensuite un script de version ou la table .dynsym de la bibliothèque partagée (fichier .so) pour créer une liste des symboles exportés.

Par exemple, libfoo est constitué de foo.cpp et de bar.cpp. header-abi-linker peut être invoqué pour créer le dump ABI associé complet de libfoo comme suit :

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current

Exemple de résultat de la commande dans 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" : []
}

L'outil header-abi-linker :

  • Associe les fichiers .sdump qui lui sont fournis (foo.sdump et bar.sdump), en filtrant les informations ABI qui ne sont pas présentes dans les en-têtes résidant dans le répertoire : exported.
  • Analyse libfoo.so et collecte des informations sur les symboles exportés par la bibliothèque via sa table .dynsym.
  • Ajout de _Z3FooiP3bar et _Z6FooBadiP3foo.

libfoo.so.lsdump est le fichier de dump ABI final généré à partir de libfoo.so.

header-abi-diff

L'outil header-abi-diff compare deux fichiers .lsdump représentant l'ABI de deux bibliothèques et génère un rapport diff indiquant les différences entre les deux ABI.

Entrées
  • Fichier .lsdump représentant l'ABI d'une ancienne bibliothèque partagée.
  • Fichier .lsdump représentant l'ABI d'une nouvelle bibliothèque partagée.
Sortie Un rapport diff indiquant les différences entre les ABI proposées par les deux bibliothèques partagées comparées.

Le fichier de différences ABI est au format texte protobuf. Le format est susceptible d'être modifié dans les prochaines versions.

Par exemple, vous avez deux versions de libfoo : libfoo_old.so et libfoo_new.so. Dans libfoo_new.so, dans bar_t, vous remplacez le type de mfoo de foo_t par foo_t *. Étant donné que bar_t est un type accessible, header-abi-diff doit le signaler comme modification destructive de l'ABI.

Pour exécuter header-abi-diff :

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo

Exemple de résultat de la commande dans 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
    }
  }
}

Le fichier libfoo.so.abidiff contient un rapport de toutes les modifications de rupture de l'ABI dans libfoo. Le message record_type_diffs indique qu'un enregistrement a été modifié et liste les modifications incompatibles, qui incluent :

  • Taille de l'enregistrement passant de 24 octets à 8 octets.
  • Le type de champ de mfoo passe de foo à foo * (tous les typedefs sont supprimés).

Le champ type_stack indique comment header-abi-diff a atteint le type qui a changé (bar). Ce champ peut être interprété comme Foo étant une fonction exportée qui prend bar * comme paramètre, qui pointe vers bar, qui a été exporté et modifié.

Appliquer l'ABI et l'API

Pour appliquer l'ABI et l'API des bibliothèques partagées VNDK, les références ABI doivent être archivées dans ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/. Pour créer ces références, exécutez la commande suivante :

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

Après avoir créé les références, toute modification apportée au code source qui entraîne une modification incompatible de l'ABI/API dans une bibliothèque VNDK génère désormais une erreur de compilation.

Pour mettre à jour les références ABI pour des bibliothèques spécifiques, exécutez la commande suivante :

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>

Par exemple, pour mettre à jour les références ABI libbinder, exécutez la commande suivante :

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