Stabilité de l'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 Vendor Native Development Kit (VNDK) qui se trouvent dans la partition système. Dans une version Android, les bibliothèques partagées VNDK nouvellement créées doivent être compatibles avec les ABI des bibliothèques partagées VNDK publiées précédemment afin que les modules du fournisseur puissent fonctionner avec ces bibliothèques sans recompilation et sans erreurs d'exécution. Entre les versions d'Android, les bibliothèques VNDK peuvent être modifiées et il n'y a aucune garantie d'ABI.

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

À propos de la conformité avec le VNDK et les ABI

Le VNDK est un ensemble restrictif de bibliothèques auxquelles les modules du fournisseur peuvent s'associer et qui permettent des mises à jour du framework uniquement. La conformité 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 associé de manière dynamique (c'est-à-dire qu'elle fonctionne comme une version plus ancienne de la bibliothèque).

À propos des symboles exportés

Un symbole exporté (également appelé symbole global) désigne un symbole qui répond à tous les critères suivants:

  • Exportées par les en-têtes publics d'une bibliothèque partagée.
  • Cet élément apparaît dans la table .dynsym du fichier .so correspondant à la bibliothèque partagée.
  • Présente une liaison FAIBLE ou GÉNÉRALE.
  • La visibilité est "PAR DÉFAUT" ou "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 accessibles

Un type accessible est un type intégré ou défini par l'utilisateur en C/C++ 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 la table .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"
  ],
}
Tableau .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 ce qui concerne Foo, les types accessibles directement/indirectement incluent les suivants :

Type Description
bool Type renvoyé de Foo.
int Type du premier paramètre Foo.
bar_t * Type du deuxième paramètre Foo. À l'aide 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 de plus de types :
  • int : est le type de m1.
  • int * : est le type de m2.
  • foo_private_t * : est le type de mPfoo.

Cependant, 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é avec les ABI

La conformité avec l'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 de structure.
  • Classes de base
    • Ajoutez ou supprimez des classes de base.
    • Ajoutez ou supprimez des classes de base héritées virtuellement.
    • Modifier l'ordre des classes de base.
  • Fonctions de membre
    • Supprimez les fonctions de membre*.
    • Ajoutez ou supprimez des arguments des fonctions membres.
    • Modifier les types d'arguments ou les types de retour des fonctions de membre*
    • Modifier la mise en page de la table virtuelle
  • Membres des données
    • Supprimer les membres des données statiques.
    • Ajoutez ou supprimez des membres de données non statiques.
    • Modifier les types de membres de données
    • Modifiez les décalages au niveau des membres de données non statiques**.
    • Modifiez les qualificatifs const, volatile et/ou restricted des membres de données***.
    • Déclassez les spécificateurs d'accès des membres de données***.
  • Modifiez les arguments du modèle.
Unions
  • Ajoutez ou supprimez des membres de données.
  • Modifiez la taille du type d'union.
  • Modifiez les types de membres des données.
Énumérations
  • Modifier le type sous-jacent.
  • Modifier 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 renvoyé.
    • Déclassez le spécificateur d'accès***.
  • Pour les symboles globaux de type OBJECT :
    • Modifiez le type C/C++ correspondant.
    • Déclassez 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 aux 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. Modifier ou supprimer des fonctions de membres privées des bibliothèques partagées peut entraîner des binaires non compatibles 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 la création de binaires rétrocompatibles.

*** Bien que ces éléments ne modifient pas la mise en page de la mémoire du type, il existe des différences sémantiques qui peuvent entraîner un dysfonctionnement des bibliothèques.

Utiliser des outils de conformité avec les ABI

Lorsqu'une bibliothèque VNDK est créée, l'ABI de la bibliothèque est comparée à la référence ABI correspondante pour la version du VNDK en cours de compilation. Les vidages d'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:

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

Erreur de faille de l'ABI

En cas de rupture 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 a subi 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 ----

Créer des vérifications d'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 compiler la bibliothèque VNDK (les fichiers sources de la bibliothèque ainsi que les fichiers sources hérités via des dépendances transitives statiques) afin de générer des fichiers .sdump correspondant à chaque source.
    Création d&#39;un fichier 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 générer un fichier .lsdump qui enregistre toutes les informations d'ABI correspondant à la bibliothèque partagée.
    Création d&#39;un fichier lsdump
    Figure 2 Créer le fichier .lsdump
  3. header-abi-diff compare le fichier .lsdump à un fichier .lsdump de référence pour générer un rapport de différences qui décrit les différences entre les ABI des deux bibliothèques.
    création de diff Abi Diff
    Figure 3 Créer le rapport de différences

header-abi-dumper

L'outil header-abi-dumper analyse un fichier source C/C++ et extrait l'ABI inféré à partir 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
  • Un fichier source C/C++
  • Répertoires d'inclusion exportés
  • Options du compilateur
Sortie Un fichier qui décrit 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, le formatage des fichiers .sdump doit être considéré comme un détail de l'implémentation du système de compilation.

Par exemple, libfoo.so possède le fichier source foo.cpp suivant :

#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 options du compilateur qui suivent --, et d'émettre les informations ABI exportées par les en-têtes publics du répertoire exported. Voici le 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. Reportez-vous aux structures, 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 pointeurs référencés directement/indirectement par les enregistrements/fonctions exportés dans les en-têtes publics, ainsi qu'au type auquel le pointeur fait référence (via le champ referenced_type dans type_info). Des informations similaires sont enregistrées dans le fichier .sdump pour les types qualifiés, les types C/C++ intégrés, les types de tableaux, ainsi que les types de référence lvalue et rvalue. Ces informations permettent une comparaison récursive.
  • functions : représente les fonctions exportées par les en-têtes publics. Ils contiennent également des informations sur le nom tronqué de la fonction, le type de retour, les types des paramètres, le spécificateur d'accès et d'autres attributs.

en-tête-abi-linker

L'outil header-abi-linker utilise les fichiers intermédiaires produits par header-abi-dumper comme entrée, puis les lie :

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 d'inclusion exportés
Sortie Un fichier qui décrit l'ABI d'une bibliothèque partagée (par exemple, libfoo.so.lsdump représente l'ABI de libfoo).

L'outil fusionne les graphes de types dans tous les fichiers intermédiaires qui lui sont fournis, en tenant compte des différences entre les unités de traduction (les types définis par l'utilisateur dans différentes unités de traduction avec le même nom complet peuvent être sémantiquement différents). 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 composé de foo.cpp et bar.cpp. header-abi-linker peut être appelé pour créer le vidage complet de l'ABI associée 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 figurent pas dans les en-têtes situés 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 dump ABI généré final 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 de différences 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 Rapport de différences indiquant les différences entre les ABI proposées par les deux bibliothèques partagées

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

Par exemple, vous disposez de 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, il doit être signalé comme une modification destructive de l'ABI par header-abi-diff.

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

libfoo.so.abidiff contient un rapport sur toutes les modifications ABI non compatibles dans libfoo. Le message record_type_diffs indique qu'un enregistrement a changé et liste les modifications incompatibles, qui incluent les suivantes:

  • La taille de l'enregistrement passe de 24 octets à 8 octets.
  • Le type de champ de mfoo passe de foo à foo * (toutes les définitions de type sont supprimées).

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 utilise 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 du VNDK, les références d'ABI doivent être enregistré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 ABI/API incompatible dans une bibliothèque VNDK entraîne 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