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 :
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 |
|
Unions |
|
Énumérations |
|
Symboles globaux |
|
* 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:
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.
Figure 1. Créer les fichiers .sdump
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.
Figure 2 Créer le fichier .lsdump
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.
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 |
|
---|---|
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 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 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 |
|
---|---|
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
etbar.sdump
), en filtrant les informations ABI qui ne figurent pas dans les en-têtes situés dans le répertoireexported
. - 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 |
|
---|---|
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 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
é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