Vérifications Dexpreopt et

Android 12 apporte des modifications au système de compilation pour la compilation AOT des fichiers DEX (dexpreopt) pour les modules Java qui ont des <uses-library> dépendances. Dans certains cas, ces modifications du système de compilation peuvent endommager les compilations. Utilisez cette page pour vous préparer aux pannes et suivez les procédures décrites sur cette page pour les corriger et les atténuer.

Dexpreopt est le processus de compilation AOT des bibliothèques et applications Java. Dexpreopt se produit sur l'hôte au moment de la compilation (contrairement à dexopt, qui se produit sur l'appareil). La structure des dépendances de la bibliothèque partagée utilisée par un module Java (une bibliothèque ou une application) est appelée contexte du chargeur de classe (CLC). Pour garantir l'exactitude de dexpreopt, les CLC au moment de la compilation et au moment de l'exécution doivent coïncider. Le CLC au moment de la compilation est celui utilisé par le compilateur dex2oat au moment de dexpreopt (il est enregistré dans les fichiers ODEX), et le CLC au moment de l'exécution est le contexte dans lequel le code précompilé est chargé sur l'appareil.

Ces CLC au moment de la compilation et au moment de l'exécution doivent coïncider pour des raisons d'exactitude et de performances. Pour l'exactitude, il est nécessaire de gérer les classes en double. Si les dépendances de la bibliothèque partagée au moment de l'exécution sont différentes de celles utilisées pour la compilation, certaines classes peuvent être résolues différemment, ce qui entraîne des bugs d'exécution subtils. Les performances sont également affectées par les vérifications d'exécution des classes en double.

Cas d'utilisation concernés

Le premier démarrage est le principal cas d'utilisation concerné par ces modifications : si ART détecte une incompatibilité entre les CLC au moment de la compilation et au moment de l'exécution, il rejette les artefacts dexpreopt et exécute dexopt à la place. Pour les démarrages suivants, cela ne pose pas de problème, car les applications peuvent être dexoptées en arrière-plan et stockées sur le disque.

Zones d'Android concernées

Cela affecte toutes les applications et bibliothèques Java qui ont des dépendances d'exécution sur d'autres bibliothèques Java. Android compte des milliers d'applications, dont des centaines utilisent des bibliothèques partagées. Les partenaires sont également concernés, car ils possèdent leurs propres bibliothèques et applications.

Modifications destructives

Le système de compilation doit connaître les dépendances <uses-library> avant qu'il ne génère des règles de compilation dexpreopt. Toutefois, il ne peut pas accéder directement au fichier manifeste et lire les <uses-library> tags qu'il contient, car le système de compilation n'est pas autorisé à lire des fichiers arbitraires lorsqu' il génère des règles de compilation (pour des raisons de performances). De plus, le fichier manifeste peut être empaqueté dans un APK ou un fichier prédéfini. Par conséquent, les <uses-library> informations doivent être présentes dans les fichiers de compilation (Android.bp ou Android.mk).

Auparavant, ART utilisait une solution de contournement qui ignorait les dépendances de la bibliothèque partagée (connue sous le nom de &-classpath). Cette solution n'était pas sûre et entraînait des bugs subtils. Elle a donc été supprimée dans Android 12.

Par conséquent, les modules Java qui ne fournissent pas d'informations <uses-library> correctes dans leurs fichiers de compilation peuvent entraîner des pannes de compilation (dues à une incompatibilité de CLC au moment de la compilation) ou des régressions au premier démarrage (dues à une incompatibilité de CLC au moment du démarrage , suivie de dexopt).

Chemin de migration

Pour corriger une compilation endommagée, procédez comme suit :

  1. Désactivez globalement la vérification au moment de la compilation pour un produit spécifique en définissant

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    dans le fichier makefile du produit. Cela corrige les erreurs de compilation (sauf dans les cas particuliers, répertoriés dans la section Corriger les pannes). Toutefois, il s'agit d'une solution de contournement temporaire qui peut entraîner une incompatibilité de CLC au moment du démarrage, suivie de dexopt.

  2. Corrigez les modules qui ont échoué avant de désactiver globalement la vérification au moment de la compilation en ajoutant les informations <uses-library> nécessaires à leurs fichiers de compilation (pour en savoir plus, consultez Corriger les pannes). Pour la plupart des modules, cela nécessite d'ajouter quelques lignes dans Android.bp ou dans Android.mk.

  3. Désactivez la vérification au moment de la compilation et dexpreopt pour les cas problématiques, module par module. Désactivez dexpreopt pour ne pas perdre de temps de compilation ni d'espace de stockage sur les artefacts qui sont rejetés au démarrage.

  4. Réactivez globalement la vérification au moment de la compilation en annulant la définition de PRODUCT_BROKEN_VERIFY_USES_LIBRARIES définie à l'étape 1. La compilation ne devrait plus échouer après cette modification (en raison des étapes 2 et 3).

  5. Corrigez les modules que vous avez désactivés à l'étape 3, un par un, puis réactivez dexpreopt et la <uses-library> vérification. Si nécessaire, signalez les bugs.

Les vérifications <uses-library> au moment de la compilation sont appliquées dans Android 12.

Corriger les pannes

Les sections suivantes vous expliquent comment corriger des types de pannes spécifiques.

Erreur de compilation : incompatibilité de CLC

Le système de compilation effectue une vérification de cohérence au moment de la compilation entre les informations des fichiers Android.bp ou Android.mk et le fichier manifeste. Le système de compilation ne peut pas lire le fichier manifeste, mais il peut générer des règles de compilation pour le lire (en l'extrayant d'un APK si nécessaire) et comparer les tags <uses-library> du fichier manifeste avec les informations <uses-library> des fichiers de compilation. Si la vérification échoue, l'erreur se présente comme suit :

error: mismatch in the <uses-library> tags between the build system and the manifest:
    - required libraries in build system: []
                     vs. in the manifest: [org.apache.http.legacy]
    - optional libraries in build system: []
                     vs. in the manifest: [com.x.y.z]
    - tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
        <uses-library android:name="com.x.y.z"/>
        <uses-library android:name="org.apache.http.legacy"/>

note: the following options are available:
    - to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
    - to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
    - to fix the check, make build system properties coherent with the manifest
    - see build/make/Changes.md for details

Comme le suggère le message d'erreur, il existe plusieurs solutions, en fonction de l'urgence :

  • Pour une correction temporaire à l'échelle du produit, définissez PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true dans le fichier makefile du produit. La vérification de cohérence au moment de la compilation est toujours effectuée, mais un échec de vérification n'entraîne pas un échec de compilation. Au lieu de cela, un échec de vérification entraîne la rétrogradation par le système de compilation du filtre du compilateur dex2oat à verify dans dexpreopt, ce qui désactive complètement la compilation AOT pour ce module.
  • Pour une correction rapide et globale en ligne de commande, utilisez la variable d'environnement RELAX_USES_LIBRARY_CHECK=true. Elle a le même effet que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, mais est destinée à être utilisée en ligne de commande. La variable d'environnement remplace la variable de produit.
  • Pour une solution permettant de corriger la cause racine de l'erreur, informez le système de compilation des tags <uses-library> dans le fichier manifeste. Une inspection du message d'erreur indique les bibliothèques à l'origine du problème (tout comme l'inspection AndroidManifest.xml ou du fichier manifeste dans un APK qui peut être vérifié avec `aapt dump badging $APK | grep uses-library`).

Pour les modules Android.bp :

  1. Recherchez la bibliothèque manquante dans la propriété libs du module. Si elle s'y trouve, Soong ajoute normalement ces bibliothèques automatiquement, sauf dans les cas particuliers suivants :

    • La bibliothèque n'est pas une bibliothèque SDK (elle est définie comme java_library plutôt que java_sdk_library).
    • La bibliothèque a un nom de bibliothèque différent (dans le fichier manifeste) de son nom de module (dans le système de compilation).

    Pour résoudre ce problème temporairement, ajoutez provides_uses_lib: "<library-name>" dans la Android.bp définition de la bibliothèque. Pour une solution à long terme, corrigez le problème sous-jacent : convertissez la bibliothèque en bibliothèque SDK ou renommez son module.

  2. Si l'étape précédente n'a pas permis de résoudre le problème, ajoutez uses_libs: ["<library-module-name>"] pour les bibliothèques requises, ou optional_uses_libs: ["<library-module-name>"] pour les bibliothèques facultatives à la Android.bp définition du module. Ces propriétés acceptent une liste de noms de modules. L'ordre relatif des bibliothèques dans la liste doit être le même que dans le fichier manifeste.

Pour les modules Android.mk :

  1. Vérifiez si la bibliothèque a un nom de bibliothèque différent (dans le fichier manifeste) de son nom de module (dans le système de compilation). Si c'est le cas, corrigez ce problème temporairement en ajoutant LOCAL_PROVIDES_USES_LIBRARY := <library-name> dans le Android.mk fichier de la bibliothèque, ou ajoutez provides_uses_lib: "<library-name>" dans le Android.bp fichier de la bibliothèque (les deux cas sont possibles, car un module Android.mk peut dépendre d'une bibliothèque Android.bp). Pour une solution à long terme, corrigez le problème sous-jacent : renommez le module de la bibliothèque.

  2. Ajoutez LOCAL_USES_LIBRARIES := <library-module-name> pour les bibliothèques requises ; ajoutez LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> pour les bibliothèques facultatives à la définition Android.mk du module. Ces propriétés acceptent une liste de noms de modules. L'ordre relatif des bibliothèques dans la liste doit être le même que dans le fichier manifeste.

Erreur de compilation : chemin de bibliothèque inconnu

Si le système de compilation ne trouve pas de chemin d'accès à un fichier JAR DEX <uses-library> (chemin d'accès au moment de la compilation sur l'hôte ou chemin d'installation sur l'appareil), la compilation échoue généralement. L'échec de la recherche d'un chemin d'accès peut indiquer que la bibliothèque est configurée de manière inattendue. Corrigez temporairement la compilation en désactivant dexpreopt pour le module problématique.

Android.bp (propriétés du module) :

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (variables du module) :

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Signalez un bug pour examiner tous les scénarios non compatibles.

Erreur de compilation : dépendance de bibliothèque manquante

Une tentative d'ajout de <uses-library> X à partir du fichier manifeste du module Y au fichier de compilation pour Y peut entraîner une erreur de compilation en raison de la dépendance manquante, X.

Voici un exemple de message d'erreur pour les modules Android.bp :

"Y" depends on undefined module "X"

Voici un exemple de message d'erreur pour les modules Android.mk :

'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it

Une source courante de ces erreurs se produit lorsqu'une bibliothèque est nommée différemment de son module correspondant dans le système de compilation. Par exemple, si l'entrée du fichier manifeste <uses-library> est com.android.X, mais que le nom du module de la bibliothèque est simplement X, cela provoque une erreur. Pour résoudre ce problème, indiquez au système de compilation que le module nommé X fournit un <uses-library> nommé com.android.X.

Voici un exemple pour les bibliothèques Android.bp (propriété du module) :

provides_uses_lib: “com.android.X”,

Voici un exemple pour les bibliothèques Android.mk (variable du module) :

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Incompatibilité de CLC au moment du démarrage

Au premier démarrage, recherchez dans logcat les messages liés à l'incompatibilité de CLC, comme indiqué ci-dessous :

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

La sortie peut contenir des messages au format suivant :

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

Si vous recevez un avertissement d'incompatibilité de CLC, recherchez une commande dexopt pour le module défectueux. Pour résoudre ce problème, assurez-vous que la vérification au moment de la compilation pour le module est réussie. Si cela ne fonctionne pas, il s'agit peut-être d'un cas particulier qui n'est pas compatible avec le système de compilation (par exemple, une application qui charge un autre APK, et non une bibliothèque). Le système de compilation ne gère pas tous les cas, car au moment de la compilation, il est impossible de savoir avec certitude ce que l'application charge au moment de l'exécution.

Contexte du chargeur de classe

Le CLC est une structure arborescente qui décrit la hiérarchie du chargeur de classe. Le système de compilation utilise le CLC au sens strict (il ne couvre que les bibliothèques, et non les APK ni les chargeurs de classe personnalisés) : il s'agit d'une arborescence de bibliothèques qui représente la fermeture transitive de toutes les <uses-library> dépendances d'une bibliothèque ou d'une application. Les éléments de premier niveau d'un CLC sont les dépendances <uses-library> directes spécifiées dans le fichier manifeste (le classpath). Chaque nœud d'une arborescence CLC est un <uses-library> nœud qui peut avoir ses propres <uses-library> sous-nœuds.

Étant donné que les dépendances <uses-library> sont un graphe orienté acyclique, et pas nécessairement une arborescence, le CLC peut contenir plusieurs sous-arborescences pour la même bibliothèque. En d'autres termes, le CLC est le graphe de dépendances "déplié" dans une arborescence. La duplication n'est qu'au niveau logique. Les chargeurs de classe sous-jacents réels ne sont pas dupliqués (au moment de l'exécution, il existe une seule instance de chargeur de classe pour chaque bibliothèque).

Le CLC définit l'ordre de recherche des bibliothèques lors de la résolution des classes Java utilisées par la bibliothèque ou l'application. L'ordre de recherche est important, car les bibliothèques peuvent contenir des classes en double, et la classe est résolue en fonction de la première correspondance.

CLC sur l'appareil (au moment de l'exécution)

PackageManager (dans frameworks/base) crée un CLC pour charger un module Java sur l'appareil. Il ajoute les bibliothèques listées dans les <uses-library> tags du fichier manifeste du module en tant qu'éléments CLC de premier niveau.

Pour chaque bibliothèque utilisée, PackageManager obtient toutes ses <uses-library> dépendances (spécifiées sous forme de tags dans le fichier manifeste de cette bibliothèque) et ajoute un CLC imbriqué pour chaque dépendance. Ce processus se poursuit de manière récursive jusqu'à ce que tous les nœuds feuilles de l'arborescence CLC construite soient des bibliothèques sans <uses-library> dépendances.

PackageManager ne connaît que les bibliothèques partagées. La définition de "partagé" dans cet usage diffère de sa signification habituelle (comme dans "partagé" par rapport à "statique"). Dans Android, les bibliothèques partagées Java sont celles listées dans les configurations XML installées sur l'appareil (/system/etc/permissions/platform.xml). Chaque entrée contient le nom d'une bibliothèque partagée, un chemin d'accès à son fichier JAR DEX et une liste de dépendances (autres bibliothèques partagées que celle-ci utilise au moment de l'exécution et spécifie dans <uses-library> de son fichier manifeste).

En d'autres termes, il existe deux sources d'informations qui permettent à PackageManager de construire le CLC au moment de l'exécution : les tags <uses-library> dans le fichier manifeste et les dépendances de la bibliothèque partagée dans les configurations XML.

CLC sur l'hôte (au moment de la compilation)

Le CLC n'est pas seulement nécessaire lors du chargement d'une bibliothèque ou d'une application, mais aussi lors de sa compilation. La compilation peut avoir lieu sur l'appareil (dexopt) ou lors de la compilation (dexpreopt). Étant donné que dexopt a lieu sur l'appareil, il dispose des mêmes informations que PackageManager (fichiers manifestes et dépendances de la bibliothèque partagée). Dexpreopt, en revanche, a lieu sur l'hôte et dans un environnement totalement différent, et doit obtenir les mêmes informations du système de compilation.

Ainsi, le CLC au moment de la compilation utilisé par dexpreopt et le CLC au moment de l'exécution utilisé par PackageManager sont identiques, mais calculés de deux manières différentes.

Les CLC au moment de la compilation et au moment de l'exécution doivent coïncider, sinon le code compilé AOT créé par dexpreopt est rejeté. Pour vérifier l'égalité des CLC au moment de la compilation et au moment de l'exécution, le compilateur dex2oat enregistre le CLC au moment de la compilation dans les fichiers *.odex (dans le champ classpath de l'en-tête du fichier OAT). Pour trouver le CLC stocké, utilisez la commande suivante :

oatdump --oat-file=<FILE> | grep '^classpath = '

L'incompatibilité de CLC au moment de la compilation et au moment de l'exécution est signalée dans logcat au démarrage. Recherchez-la à l'aide de la commande suivante :

logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'

L'incompatibilité est mauvaise pour les performances, car elle oblige la bibliothèque ou l'application à être dexoptée ou à s'exécuter sans optimisation (par exemple, le code de l'application peut avoir besoin d'être extrait en mémoire à partir de l'APK, une opération très coûteuse).

Une bibliothèque partagée peut être facultative ou requise. Du point de vue de dexpreopt, une bibliothèque requise doit être présente au moment de la compilation (son absence est une erreur de compilation). Une bibliothèque facultative peut être présente ou absente au moment de la compilation : si elle est présente, elle est ajoutée au CLC, transmise à dex2oat et enregistrée dans le fichier *.odex. Si une bibliothèque facultative est absente, elle est ignorée et n'est pas ajoutée au CLC. En cas d'incompatibilité entre l'état au moment de la compilation et au moment de l'exécution (la bibliothèque facultative est présente dans un cas, mais pas dans l'autre), les CLC au moment de la compilation et au moment de l'exécution ne correspondent pas et le code compilé est rejeté.

Informations avancées sur le système de compilation (correcteur de fichier manifeste)

Parfois, les tags <uses-library> sont manquants dans le fichier manifeste source d'une bibliothèque ou d'une application. Cela peut se produire, par exemple, si l'une des dépendances transitives de la bibliothèque ou de l'application commence à utiliser un autre tag <uses-library>, et que le fichier manifeste de la bibliothèque ou de l'application n'est pas mis à jour pour l'inclure.

Soong peut calculer automatiquement certains des tags manquants <uses-library> pour une bibliothèque ou une application donnée, en tant que bibliothèques SDK dans la fermeture de dépendance transitive de la bibliothèque ou de l'application. La fermeture est nécessaire, car la bibliothèque (ou l'application) peut dépendre d'une bibliothèque statique qui dépend d'une bibliothèque SDK, et peut-être encore dépendre de manière transitive via une autre bibliothèque.

Tous les tags <uses-library> ne peuvent pas être calculés de cette manière, mais lorsque cela est possible, il est préférable de laisser Soong ajouter automatiquement des entrées de fichier manifeste. Cela réduit les risques d'erreurs et simplifie la maintenance. Par exemple, lorsque de nombreuses applications utilisent une bibliothèque statique qui ajoute une nouvelle dépendance <uses-library>, toutes les applications doivent être mises à jour, ce qui est difficile à maintenir.