La stabilité de l'interface binaire d'application (ABI) est une condition préalable aux mises à jour de l'infrastructure 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 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 d'Android, les bibliothèques VNDK peuvent être modifiées et il n'y a aucune garantie ABI.
Pour aider à garantir la compatibilité ABI, Android 9 inclut un vérificateur d'en-tête ABI, 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 liés et qui permettent les 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 lui 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 à toutes les conditions suivantes :
- 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 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 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
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 : [ "include" ], } |
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 | La description |
---|---|
bool | Type de retour de Foo . |
int | Type du premier paramètre Foo . |
bar_t * | Type du deuxième paramètre Foo. Au moyen 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 plusieurs types :
Cependant, foo_private_t n'est PAS accessible car il n'est pas exporté via foo_exported.h . ( foot_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.
Garantir 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 | La 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 en ligne 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 appelants. La modification ou la suppression de fonctions membres privées des bibliothèques partagées peut entraîner des binaires rétro-incompatibles.
** Les décalages vers les membres de données publics ou privés ne doivent pas être modifiés car les fonctions en ligne peuvent faire référence à ces membres de données dans leur corps de fonction. La modification des décalages des membres de données peut entraîner des binaires rétro-incompatibles.
*** 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.
Utilisation des outils de conformité ABI
Lorsqu'une bibliothèque VNDK est construite, 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 vidages ABI de référence sont situés dans :
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
Par exemple, lors de la construction de libfoo
pour l'API niveau 27 du libfoo
, l'ABI déduit de libfoo est comparé à sa référence à :
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump
Erreur de rupture ABI
Sur les ruptures ABI, le journal de construction affiche des avertissements avec le type d'avertissement et un chemin vers le rapport abi-diff. Par exemple, si l'ABI de libbinder
a une modification incompatible, le système de construction génère une erreur avec un message semblable 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 source compilés pour construire la bibliothèque VNDK (les propres fichiers source de la bibliothèque ainsi que les fichiers source 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 de référence.lsdump
pour produire un rapport diff qui décrit les différences dans les ABI des deux bibliothèques.Figure 3. Création du rapport diff
en-tête-abi-dumper
L'outil header-abi-dumper
analyse un fichier source C/C++ et vide 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 construisant une bibliothèque qui inclut les fichiers source des dépendances transitives.
Actuellement, les fichiers .sdump
sont formatés en tant que Protobuf TextFormatted , 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 génération.
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 -- -x c++
Cette commande indique à header-abi-dumper
d'analyser foo.cpp
et d'émettre les informations ABI qui sont exposées dans les en-têtes publics du répertoire exported
. Ceci est un extrait (pas une représentation complète) de foo.sdump
généré par header-abi-dumper
:
record_types { type_info { name: "foo" size: 12 alignment: 4 referenced_type: "type-1" source_file: "foo/include/foo_exported.h" linker_set_key: "foo" self_type: "type-1" } fields { referenced_type: "type-2" field_offset: 0 field_name: "m1" access: public_access } fields { referenced_type: "type-3" field_offset: 32 field_name: "m2" access: public_access } fields { referenced_type: "type-5" field_offset: 64 field_name: "mPfoo" access: public_access } access: public_access record_kind: struct_kind tag_info { unique_id: "_ZTS3foo" } } record_types { type_info { name: "bar" size: 12 alignment: 4 referenced_type: "type-6" … pointer_types { type_info { name: "bar *" size: 4 alignment: 4 referenced_type: "type-6" source_file: "foo/include/foo_exported.h" linker_set_key: "bar *" self_type: "type-8" } } builtin_types { type_info { name: "int" size: 4 alignment: 4 referenced_type: "type-2" source_file: "" linker_set_key: "int" self_type: "type-2" } is_unsigned: false is_integral: true } functions { return_type: "type-7" function_name: "Foo" source_file: "foo/include/foo_exported.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-8" default_arg: false } linker_set_key: "_Z3FooiP3bar" access: public_access }
foo.sdump
contient des informations ABI exposées par le fichier source foo.cpp
, par exemple :
-
record_types
. Faites référence aux structures, unions ou classes exposées par 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 a été exposé, etc. -
pointer_types
. Faites référence aux types de pointeur directement/indirectement référencés par les enregistrements/fonctions exposés par les en-têtes publics, ainsi qu'au type vers lequel le pointeur pointe (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 de journalisation sur les types permettent une différenciation récursive). -
functions
. Représente les fonctions exposées par des en-têtes publics. Ils contiennent également des informations sur le nom mutilé de la fonction, le type de retour, les types des paramètres, le spécificateur d'accès, etc.
en-tête-abi-linker
L'outil header-abi-linker
prend les fichiers intermédiaires produits par header-abi-dumper
en entrée puis lie ces fichiers :
Contributions |
|
---|---|
Sortir | Un fichier qui enregistre 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 donnés, en tenant compte des différences d'une 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 créer une liste des symboles exportés.
Par exemple, lorsque libfoo
ajoute le fichier bar.cpp
(qui expose une bar
de fonctions C) à sa compilation, header-abi-linker
peut être invoqué pour créer le vidage 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
:
record_types { type_info { name: "foo" size: 24 alignment: 8 referenced_type: "type-1" source_file: "foo/include/foo_exported.h" linker_set_key: "foo" self_type: "type-1" } fields { referenced_type: "type-2" field_offset: 0 field_name: "m1" access: public_access } fields { referenced_type: "type-3" field_offset: 64 field_name: "m2" access: public_access } fields { referenced_type: "type-4" field_offset: 128 field_name: "mPfoo" access: public_access } access: public_access record_kind: struct_kind tag_info { unique_id: "_ZTS3foo" } } record_types { type_info { name: "bar" size: 24 alignment: 8 ... builtin_types { type_info { name: "void" size: 0 alignment: 0 referenced_type: "type-6" source_file: "" linker_set_key: "void" self_type: "type-6" } is_unsigned: false is_integral: false } functions { return_type: "type-19" function_name: "Foo" source_file: "foo/include/foo_exported.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-20" default_arg: false } linker_set_key: "_Z3FooiP3bar" access: public_access } functions { return_type: "type-6" function_name: "FooBad" source_file: "foo/include/foo_exported_bad.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-7" default_arg: false } linker_set_key: "_Z6FooBadiP3foo" access: public_access } elf_functions { name: "_Z3FooiP3bar" } elf_functions { name: "_Z6FooBadiP3foo" }
L'outil header-abi-linker
:
- Lie les fichiers
.sdump
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
. -
libfoo.so
et collecte des informations sur les symboles exportés par la bibliothèque via sa table.dynsym
. - Ajoute
_Z3FooiP3bar
etBar
.
libfoo.so.lsdump
est le dernier vidage ABI 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 diff indiquant les différences entre les deux ABI.
Contributions |
|
---|---|
Sortir | Un rapport diff indiquant les différences dans les ABI proposés par les deux bibliothèques partagées comparées. |
Le fichier ABI diff est conçu pour être aussi détaillé et lisible que possible. Le format est sujet à changement 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 changez le type de mfoo
de foo_t
à foo_t *
. Étant donné que bar_t
est un type directement accessible, cela doit être signalé comme un changement de rupture 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 tous les changements de rupture 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 *
(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
est une fonction exportée qui prend bar *
comme paramètre, qui pointe vers bar
, qui a été exporté et modifié.
Application de l'ABI/API
Pour appliquer l'ABI/API des bibliothèques partagées VNDK et LLNDK, les références ABI doivent être archivées dans ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/
. 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 ou LLNDK entraîne désormais une erreur de génération.
Pour mettre à jour les références ABI pour des bibliothèques principales VNDK 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
Pour mettre à jour les références ABI pour des bibliothèques LLNDK spécifiques, exécutez la commande suivante :
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
Par exemple, pour mettre à jour les références libm
ABI, exécutez :
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk