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 :
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 |
|
Les syndicats |
|
Énumérations |
|
Symboles globaux |
|
* 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 :
-
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.Figure 1. Création des fichiers .sdump
-
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.Figure 2. Création du fichier .lsdump
-
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.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 |
|
---|---|
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 champreferenced_type
danstype_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 |
|
---|---|
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
etbar.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 |
|
---|---|
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 defoo
à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