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 :
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 |
|
Syndicats |
|
É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 à 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 :
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.
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 produire un fichier.lsdump
qui enregistre toutes les informations ABI correspondant à la bibliothèque partagée.
Figure 2 : Créer le fichier .lsdump
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.
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 |
|
---|---|
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 champreferenced_type
danstype_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 |
|
---|---|
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
etbar.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 |
|
---|---|
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 defoo
à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