Stabilité ABI

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

Pour garantir la compatibilité ABI, Android 9 inclut un vérificateur 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 se lier et qui permettent des mises à jour uniquement du framework. 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 lié dynamiquement (c'est-à-dire qu'il fonctionne comme le ferait une ancienne version de la bibliothèque).

À propos des symboles exportés

Un symbole exporté (également appelé symbole global ) fait référence à un symbole qui satisfait à tous les critères suivants :

  • Exporté par les en-têtes publics d'une bibliothèque partagée.
  • Apparaît dans la table .dynsym du fichier .so correspondant à la bibliothèque partagée.
  • A une liaison FAIBLE ou GLOBALE.
  • La visibilité est PAR DÉFAUT ou PROTÉGÉE.
  • L'index de section n'est pas INDÉFINI.
  • 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 tout 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 a la fonction Foo , qui est un symbole exporté trouvé dans la table .dynsym . La bibliothèque libfoo.so comprend 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 regardant Foo , les types accessibles directement/indirectement incluent :

Taper Description
bool Type de retour de Foo .
int Type du premier paramètre Foo .
bar_t * Type du deuxième paramètre Foo. Grâce à 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 davantage 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, donc les modifications apportées à foo_private_t sont autorisées.)

Une explication similaire peut également ê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é ABI

La conformité ABI doit être assurée pour les bibliothèques marquées vendor_available: true et vndk.enabled: true dans les fichiers Android.bp correspondants. Par 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 cassant l'ABI :

Type de données Description
Structures et classes
  • Modifiez la taille du type de classe ou du type de structure.
  • Cours de base
    • Ajoutez ou supprimez des classes de base.
    • Ajoutez ou supprimez des classes de base virtuellement héritées.
    • Modifiez l'ordre des classes de base.
  • Fonctions des membres
    • Supprimer les fonctions membres*.
    • Ajoutez ou supprimez des arguments des fonctions membres.
    • Modifiez les types d'arguments ou les types de retour des fonctions membres*.
    • Modifiez la disposition de la table virtuelle.
  • Membres de données
    • Supprimez les données membres statiques.
    • Ajoutez ou supprimez des données membres non statiques.
    • Modifiez les types de données membres.
    • Modifiez les décalages en données membres non statiques**.
    • Modifiez les qualificatifs const , volatile et/ou restricted des données membres***.
    • Rétrograder les spécificateurs d’accès des données membres***.
  • Modifiez les arguments du modèle.
Les syndicats
  • Ajoutez ou supprimez des données membres.
  • Modifiez la taille du type d'union.
  • Modifiez les types de données membres.
Énumérations
  • Modifiez le type sous-jacent.
  • Changez les noms des enquêteurs.
  • 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.
    • Modifiez les types d'arguments.
    • Modifiez le type de retour.
    • Rétrograder le spécificateur d'accès***.
  • Pour les symboles globaux de type OBJET
    • 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 ou supprimées car les fonctions publiques en ligne 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 des appelants. La modification ou la suppression de fonctions de membre privé des bibliothèques partagées peut entraîner des binaires rétrocompatibles.

** Les décalages vers les données membres publiques ou privées ne doivent pas être modifiés car les fonctions en ligne peuvent faire référence à ces données membres dans leur corps de fonction. La modification des décalages des membres de données peut entraîner des fichiers binaires rétrocompatibles.

*** Bien que ceux-ci ne modifient pas la disposition de la mémoire du type, il existe des différences sémantiques qui pourraient empêcher les bibliothèques de fonctionner comme prévu.

Utiliser les outils de conformité 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 construction. 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 construction libfoo pour x86 au niveau API 27, l'ABI déduit de libfoo est comparé à sa référence à :

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

Erreur de casse ABI

En cas de pannes ABI, le journal de construction affiche les avertissements avec le type d'avertissement et un chemin vers le rapport abi-diff. Par exemple, si l'ABI de libbinder présente une modification incompatible, le système de build renvoie une erreur avec un message similaire au suivant :

*****************************************************
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éation de vérifications 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 construire la bibliothèque VNDK (les propres fichiers sources de la bibliothèque ainsi que les fichiers sources hérités via des dépendances transitives statiques), pour produire des fichiers .sdump qui correspondent à chaque source.
    sdump creation
    Figure 1. Création des fichiers .sdump
  2. header-abi-linker traite ensuite les fichiers .sdump (en utilisant soit un script de version qui lui est fourni, soit le 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.
    lsdump creation
    Figure 2. Création du fichier .lsdump
  3. header-abi-diff compare le fichier .lsdump avec un fichier .lsdump de référence pour produire un rapport de comparaison qui décrit les différences dans les ABI des deux bibliothèques.
    abi diff creation
    Figure 3. Création du rapport de comparaison

en-tête-abi-dumper

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

Contributions
  • Fichier source AC/C++
  • Répertoires d'inclusion exportés
  • Indicateurs du compilateur
Sortir 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, dont la stabilité n'est pas garantie dans les versions futures. En tant que tel, le formatage du fichier .sdump doit être considéré comme un détail d’implémentation du système de construction.

Par exemple, libfoo.so a 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é par le fichier source en utilisant :

$ 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 suivants -- et d'émettre les informations ABI qui sont exportées par les en-têtes publics dans le répertoire exported . Ce qui suit est 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 des informations ABI exportées par le fichier source foo.cpp et les en-têtes publics, par exemple :

  • record_types . Faites référence aux structures, unions ou classes définies 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 . Faites référence aux types de pointeurs 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 enregistré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 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 mutilé de la fonction, le type de retour, les types de paramètres, le spécificateur d'accès et d'autres attributs.

en-tête-abi-linker

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

Contributions
  • Fichiers intermédiaires produits par header-abi-dumper
  • Script de version/fichier de carte (facultatif)
  • fichier .so de la bibliothèque partagée
  • Répertoires d'inclusion exportés
Sortir 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 graphiques de types 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 avec le même nom complet, peuvent être sémantiquement différents) entre les unités de traduction. L'outil analyse ensuite soit un script de version, soit la table .dynsym de la bibliothèque partagée (fichier .so ) pour dresser une liste des symboles exportés.

Par exemple, libfoo se compose de foo.cpp et bar.cpp . header-abi-linker pourrait être invoqué pour créer le dump ABI lié 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 sortie de 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 :

  • Lie les fichiers .sdump qui lui sont fournis ( foo.sdump et bar.sdump ), en filtrant les informations ABI non 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 .
  • Ajoute _Z3FooiP3bar et _Z6FooBadiP3foo .

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

en-tête-abi-diff

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

Contributions
  • 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.
Sortir Un rapport de comparaison indiquant les différences dans les ABI proposés par les deux bibliothèques partagées comparées.

Le fichier de comparaison ABI est au format texte protobuf . Le format est susceptible de changer dans les versions futures.

Par exemple, vous disposez de deux versions de libfoo : libfoo_old.so et libfoo_new.so . Dans libfoo_new.so , dans bar_t , vous changez le type de mfoo de foo_t à foo_t * . Puisque bar_t est un type accessible, cela doit être signalé comme un changement avec rupture d'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 sortie de 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 libfoo.so.abidiff contient un rapport de toutes les modifications avec rupture d'ABI dans libfoo . Le message record_type_diffs indique qu'un enregistrement a été modifié et répertorie les modifications incompatibles, notamment :

  • 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 est une fonction exportée qui prend bar * comme paramètre, qui pointe vers bar , qui a été exportée et modifiée.

Application de l'ABI/API

Pour appliquer l'ABI/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 entraînant une modification ABI/API incompatible dans une bibliothèque VNDK entraîne désormais une erreur de construction.

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 :

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