Stabilité IPS

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 :
  • 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 . ( 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
  • 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 membres de données statiques.
    • Ajoutez ou supprimez des membres de données non statiques.
    • Modifiez les types de membres de données.
    • Modifiez les décalages en membres de données non statiques**.
    • Modifiez les qualificatifs const , volatile et/ou restricted des membres de données***.
    • Rétrogradez les spécificateurs d'accès des membres de données***.
  • Modifiez les arguments du modèle.
Les syndicats
  • Ajouter ou supprimer des membres de données.
  • Modifiez la taille du type d'union.
  • Modifiez les types de membres de données.
  • Modifier l'ordre des membres de données.
É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étrogradez le spécificateur d'accès***.
  • Pour les symboles globaux de type OBJECT
    • Modifiez le type C/C++ correspondant.
    • Rétrogradez 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 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 :

  1. 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.
    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 de référence .lsdump pour produire un rapport diff qui décrit les différences dans les ABI des deux bibliothèques.
    abi diff creation
    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 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 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
  • Fichiers intermédiaires produits par header-abi-dumper
  • Script de version/fichier de carte (facultatif)
  • fichier .so de la bibliothèque partagée
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 et bar.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 et Bar .

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
  • 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 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 de foo à 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