Surveillance des ABI du kernel Android

Vous pouvez utiliser les outils de surveillance de l'interface binaire d'application (ABI), disponibles dans Android 11 et versions ultérieures, pour stabiliser l'ABI dans le noyau des noyaux Android. L'outil collecte et compare les représentations ABI des binaires de noyau existants (vmlinux + modules GKI). Ces représentations ABI sont les fichiers .stg et les listes de symboles. L'interface sur laquelle la représentation donne une vue s'appelle l'interface de module de kernel (KMI). Vous pouvez utiliser ces outils pour suivre et atténuer les modifications apportées au KMI.

Les outils de surveillance de l'ABI sont développés dans AOSP et utilisent STG (ou libabigail dans Android 13 et versions antérieures) pour générer et comparer des représentations.

Cette page décrit les outils, le processus de collecte et d'analyse des représentations ABI, ainsi que l'utilisation de ces représentations pour assurer la stabilité de l'ABI dans le noyau. Cette page fournit également des informations pour apporter des modifications aux noyaux Android.

Procédure

L'analyse de l'ABI du noyau nécessite plusieurs étapes, dont la plupart peuvent être automatisées:

  1. Compilez le noyau et sa représentation ABI.
  2. Analysez les différences ABI entre le build et une référence.
  3. Mettez à jour la représentation ABI (si nécessaire).
  4. Utiliser des listes de symboles

Les instructions suivantes s'appliquent à tout noyau que vous pouvez compiler à l'aide d'une chaîne d'outils compatible (telle que la chaîne d'outils Clang précompilée). repo manifests est disponible pour toutes les branches de kernel communes Android et pour plusieurs kernels spécifiques à l'appareil. Il garantit que la chaîne d'outils appropriée est utilisée lorsque vous créez une distribution de kernel à des fins d'analyse.

Listes de symboles

Le KMI n'inclut pas tous les symboles du noyau,ni même tous les plus de 30 000 symboles exportés. À la place, les symboles pouvant être utilisés par les modules du fournisseur sont listés explicitement dans un ensemble de fichiers de liste de symboles gérés publiquement dans l'arborescence du noyau (gki/{ARCH}/symbols/* ou android/abi_gki_{ARCH}_* sous Android 15 et versions antérieures). L'union de tous les symboles de tous les fichiers de liste de symboles définit l'ensemble des symboles KMI maintenus comme stables. Un exemple de fichier de liste de symboles est gki/aarch64/symbols/db845c, qui déclare les symboles requis pour la DragonBoard 845c.

Seuls les symboles listés dans une liste de symboles, ainsi que leurs structures et définitions associées, sont considérés comme faisant partie du KMI. Vous pouvez publier des modifications dans vos listes de symboles si les symboles dont vous avez besoin ne sont pas présents. Une fois que les nouvelles interfaces figurent dans une liste de symboles et font partie de la description du KMI, elles sont maintenues comme stables et ne doivent pas être supprimées de la liste de symboles ni modifiées une fois la branche figée.

Chaque branche du kernel KMI (Android Common Kernel) possède son propre ensemble de listes de symboles. Aucune tentative n'est faite pour assurer la stabilité de l'ABI entre les différentes branches du kernel KMI. Par exemple, le KMI pour android12-5.10 est complètement indépendant du KMI pour android13-5.10.

Les outils ABI utilisent des listes de symboles KMI pour limiter les interfaces à surveiller pour la stabilité. Les fournisseurs doivent envoyer et mettre à jour leurs propres listes de symboles pour s'assurer que les interfaces sur lesquelles ils s'appuient conservent la compatibilité ABI. Par exemple, pour afficher une liste de listes de symboles pour le noyau android16-6.12, consultez https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols.

Une liste de symboles contient les symboles signalés comme nécessaires pour le fournisseur ou l'appareil en question. La liste complète utilisée par les outils est l'union de tous les fichiers de liste de symboles KMI. Les outils ABI déterminent les détails de chaque symbole, y compris la signature de fonction et les structures de données imbriquées.

Lorsque le KMI est gelé, aucune modification n'est autorisée aux interfaces KMI existantes. Elles sont stables. Toutefois, les fournisseurs sont libres d'ajouter des symboles à la KMI à tout moment, à condition que ces ajouts n'affectent pas la stabilité de l'ABI existante. Les symboles nouvellement ajoutés sont considérés comme stables dès qu'ils sont cités dans une liste de symboles KMI. Les symboles ne doivent pas être supprimés d'une liste pour un noyau, sauf si vous pouvez confirmer qu'aucun appareil n'a jamais été expédié avec une dépendance à ce symbole.

Vous pouvez générer une liste de symboles KMI pour un appareil en suivant les instructions de la section Utiliser des listes de symboles. De nombreux partenaires envoient une liste de symboles par ACK, mais ce n'est pas une exigence stricte. Si cela vous aide à la maintenance, vous pouvez envoyer plusieurs listes de symboles.

Développer le KMI

Bien que les symboles KMI et les structures associées soient maintenus comme stables (ce qui signifie que les modifications qui endommagent les interfaces stables d'un noyau avec un KMI figé ne peuvent pas être acceptées), le noyau GKI reste ouvert aux extensions afin que les appareils expédiés plus tard dans l'année n'aient pas besoin de définir toutes leurs dépendances avant que le KMI ne soit figé. Pour étendre le KMI, vous pouvez ajouter de nouveaux symboles au KMI pour les nouvelles ou les fonctions de kernel exportées existantes, même si le KMI est gelé. Les nouveaux correctifs du noyau peuvent également être acceptés s'ils ne cassent pas le KMI.

À propos des erreurs KMI

Un noyau possède des sources et les binaires sont créés à partir de ces sources. Les branches du kernel surveillées par l'ABI incluent une représentation ABI de l'ABI GKI actuelle (sous la forme d'un fichier .stg). Une fois les binaires (vmlinux, Image et tous les modules GKI) compilés, une représentation ABI peut être extraite des binaires. Toute modification apportée à un fichier source du kernel peut affecter les binaires, et par conséquent également l'.stg extrait. L'analyseur AbiAnalyzer compare le fichier .stg validé avec celui extrait des artefacts de compilation et définit un libellé lint-1 sur la modification dans Gerrit s'il détecte une différence sémantique.

Gérer les erreurs ABI

Par exemple, le correctif suivant introduit une rupture ABI très évidente:

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
                ANDROID_KABI_RESERVE(1);
        } __randomize_layout;

+       int tickle_count;
        /*
         * The mm_cpumask needs to be at the end of mm_struct, because it
         * is dynamically sized based on nr_cpu_ids.

Lorsque vous exécutez l'ABI de compilation avec ce correctif appliqué, l'outil s'arrête avec un code d'erreur différent de zéro et signale une différence d'ABI semblable à celle-ci:

function symbol 'struct block_device* I_BDEV(struct inode*)' changed
  CRC changed from 0x8d400dbd to 0xabfc92ad

function symbol 'void* PDE_DATA(const struct inode*)' changed
  CRC changed from 0xc3c38b5c to 0x7ad96c0d

function symbol 'void __ClearPageMovable(struct page*)' changed
  CRC changed from 0xf489e5e8 to 0x92bd005e

... 4492 omitted; 4495 symbols have only CRC changes

type 'struct mm_struct' changed
  byte size changed from 992 to 1000
  member 'int tickle_count' was added
  member 'unsigned long cpu_bitmap[0]' changed
    offset changed by 64

Différences d'ABI détectées au moment de la compilation

La cause la plus courante des erreurs est qu'un pilote utilise un nouveau symbole du noyau qui ne figure dans aucune des listes de symboles.

Si le symbole n'est pas inclus dans votre liste de symboles, vous devez d'abord vérifier qu'il est exporté avec EXPORT_SYMBOL_GPL(symbol_name), puis mettre à jour la liste des symboles et la représentation ABI. Par exemple, les modifications suivantes ajoutent la nouvelle fonctionnalité FS incrémentielle à la branche android-12-5.10, ce qui inclut la mise à jour de la liste des symboles et de la représentation ABI.

  • L'exemple de modification de fonctionnalité se trouve dans aosp/1345659.
  • L'exemple de liste de symboles se trouve dans aosp/1346742.
  • L'exemple de modification de la représentation ABI se trouve dans aosp/1349377.

Si le symbole est exporté (par vous ou s'il a déjà été exporté) mais qu'aucun autre pilote ne l'utilise, une erreur de compilation semblable à celle-ci peut s'afficher.

Comparing the KMI and the symbol lists:
+ build/abi/compare_to_symbol_list out/$BRANCH/common/Module.symvers out/$BRANCH/common/abi_symbollist.raw
ERROR: Differences between ksymtab and symbol list detected!
Symbols missing from ksymtab:
Symbols missing from symbol list:
 - simple_strtoull

Pour résoudre ce problème, mettez à jour la liste des symboles KMI dans le noyau et l'ACK (voir la section Mettre à jour la représentation de l'ABI). Pour obtenir un exemple de mise à jour d'une liste de symboles et de la représentation de l'ABI dans l'ACK, consultez aosp/1367601.

Résoudre les erreurs ABI du noyau

Vous pouvez gérer les erreurs de l'ABI du kernel en refactorant le code pour ne pas modifier l'ABI ou en mettant à jour la représentation de l'ABI. Utilisez le tableau suivant pour déterminer la meilleure approche à adopter dans votre cas.

Organigramme de la rupture de l'ABI

Figure 1 : Résolution des erreurs ABI

Refactoriser le code pour éviter les modifications de l'ABI

Faites tout votre possible pour éviter de modifier l'ABI existant. Dans de nombreux cas, vous pouvez refactoriser votre code pour supprimer les modifications qui affectent l'ABI.

  • Réfactorisation des modifications apportées aux champs de structure. Si une modification modifie l'ABI pour une fonctionnalité de débogage, ajoutez un #ifdef autour des champs (dans les structures et les références de source) et assurez-vous que le CONFIG utilisé pour le #ifdef est désactivé pour la defconfig de production et le gki_defconfig. Pour savoir comment ajouter une configuration de débogage à une struct sans endommager l'ABI, consultez ce correctif.

  • Fonctionnalités de refactorisation pour ne pas modifier le noyau principal. Si de nouvelles fonctionnalités doivent être ajoutées à ACK pour prendre en charge les modules partenaires, essayez de refactoriser la partie ABI de la modification pour éviter de modifier l'ABI du kernel. Pour voir un exemple d'utilisation de l'ABI du kernel existant pour ajouter des fonctionnalités supplémentaires sans modifier l'ABI du kernel, consultez aosp/1312213.

Correction d'une ABI défectueuse sur Android Gerrit

Si vous n'avez pas intentionnellement endommagé l'ABI du kernel, vous devez procéder à une analyse en vous appuyant sur les conseils fournis par les outils de surveillance de l'ABI. Les causes les plus courantes de cassures sont les modifications des structures de données et les modifications du CRC du symbole associé, ou les modifications des options de configuration qui entraînent l'une des causes mentionnées ci-dessus. Commencez par résoudre les problèmes détectés par l'outil.

Vous pouvez reproduire les résultats de l'ABI localement. Pour ce faire, consultez la section Compiler le kernel et sa représentation ABI.

À propos des libellés Lint-1

Si vous importez des modifications dans une branche contenant un KMI figé ou finalisé, les modifications doivent passer par AbiAnalyzer pour s'assurer qu'elles n'affectent pas l'ABI stable de manière incompatible. Au cours de ce processus, AbiAnalyzer recherche le rapport ABI créé lors de la compilation (une compilation étendue qui effectue la compilation normale, puis certaines étapes d'extraction et de comparaison ABI.

Si AbiAnalyzer trouve un rapport non vide, il définit le libellé Lint-1 et l'envoi du changement est bloqué jusqu'à ce qu'il soit résolu, jusqu'à ce que le correctif reçoive un libellé Lint+1.

Mettre à jour l'ABI du noyau

Si la modification de l'ABI est inévitable, vous devez appliquer vos modifications de code, la représentation de l'ABI et la liste des symboles à l'ACK. Pour que Lint supprime le -1 et ne brise pas la compatibilité avec GKI, procédez comme suit:

  1. Importez les modifications de code dans l'ACK.

  2. Attendez de recevoir un code-review +2 pour le correctif.

  3. Mettez à jour la représentation de l'ABI de référence.

  4. Fusionnez vos modifications de code et la modification de mise à jour de l'ABI.

Importer les modifications de code ABI dans l'ACK

La mise à jour de l'ABI ACK dépend du type de modification apportée.

  • Si une modification d'ABI est liée à une fonctionnalité qui affecte les tests CTS ou VTS, la modification peut généralement être sélectionnée pour être confirmée telle quelle. Par exemple:

  • Si une modification de l'ABI concerne une fonctionnalité pouvant être partagée avec l'ACK, cette modification peut être sélectionnée pour l'ACK telle quelle. Par exemple, les modifications suivantes ne sont pas nécessaires pour les tests CTS ou VTS, mais peuvent être partagées avec l'ACK:

  • Si un changement d'ABI introduit une nouvelle fonctionnalité qui n'a pas besoin d'être incluse dans l'ACK, vous pouvez introduire les symboles dans l'ACK à l'aide d'un bouchon, comme décrit dans la section suivante.

Utiliser des bouchons pour l'ACK

Les bouchons ne doivent être nécessaires que pour les modifications de kernel de base qui ne profitent pas à l'ACK, telles que les modifications de performances et d'alimentation. La liste suivante détaille des exemples de bouchons et de choix partiels dans ACK pour GKI.

  • Bouchon de fonctionnalité d'isolation de base (aosp/1284493). Les fonctionnalités d'ACK ne sont pas nécessaires, mais les symboles doivent être présents dans ACK pour que vos modules puissent les utiliser.

  • Symbole d'espace réservé pour le module du fournisseur (aosp/1288860).

  • Sélection de la fonctionnalité de suivi des événements mm par processus (aosp/1288454) uniquement pour les ABI. Le correctif d'origine a été sélectionné pour l'ACK, puis ajusté pour n'inclure que les modifications nécessaires pour résoudre la différence ABI pour task_struct et mm_event_count. Ce correctif met également à jour l'énumération mm_event_type pour qu'elle contienne les membres finaux.

  • Sélection partielle des modifications de l'ABI de la structure thermique qui nécessitaient plus que l'ajout des nouveaux champs d'ABI.

    • Le correctif aosp/1255544 a résolu les différences ABI entre le kernel partenaire et ACK.

    • Le correctif aosp/1291018 a corrigé les problèmes fonctionnels détectés lors des tests GKI du correctif précédent. La correction consistait à initialiser la struct de paramètres du capteur pour enregistrer plusieurs zones thermiques sur un seul capteur.

  • Modifications de l'ABI CONFIG_NL80211_TESTMODE (aosp/1344321). Ce correctif a ajouté les modifications de struct nécessaires pour l'ABI et s'est assuré que les champs supplémentaires ne provoquaient pas de différences fonctionnelles, ce qui permet aux partenaires d'inclure CONFIG_NL80211_TESTMODE dans leurs noyaux de production tout en conservant la conformité GKI.

Appliquer le KMI au moment de l'exécution

Les noyaux GKI utilisent les options de configuration TRIM_UNUSED_KSYMS=y et UNUSED_KSYMS_WHITELIST=<union of all symbol lists>, qui limitent les symboles exportés (tels que les symboles exportés à l'aide de EXPORT_SYMBOL_GPL()) à ceux figurant sur une liste de symboles. Tous les autres symboles ne sont pas exportés, et le chargement d'un module nécessitant un symbole non exporté est refusé. Cette restriction est appliquée au moment de la compilation, et les entrées manquantes sont signalées.

À des fins de développement, vous pouvez utiliser un build du kernel GKI qui n'inclut pas le recadrage des symboles (ce qui signifie que tous les symboles exportés habituellement peuvent être utilisés). Pour trouver ces builds, recherchez les builds kernel_debug_aarch64 sur ci.android.com.

Appliquer le KMI à l'aide de la gestion des versions des modules

Les noyaux de l'image de kernel générique (GKI) utilisent le gestionnaire de versions de modules (CONFIG_MODVERSIONS) comme mesure supplémentaire pour appliquer la conformité de la KMI au moment de l'exécution. La gestion des versions des modules peut entraîner des échecs de non-correspondance de la vérification de redondance cyclique (CRC) au moment du chargement du module si le KMI attendu d'un module ne correspond pas au KMI de vmlinux. Par exemple, voici une erreur typique qui se produit au moment du chargement du module en raison d'une non-concordance de CRC pour le symbole module_layout():

init: Loading module /lib/modules/kernel/.../XXX.ko with args ""
XXX: disagrees about version of symbol module_layout
init: Failed to insmod '/lib/modules/kernel/.../XXX.ko' with args ''

Utilisations de la gestion des versions des modules

Le contrôle des versions des modules est utile pour les raisons suivantes:

  • La gestion des versions des modules permet de détecter les modifications de la visibilité de la structure des données. Si les modules modifient des structures de données opaques, c'est-à-dire des structures de données qui ne font pas partie du KMI, ils ne fonctionnent plus après de futures modifications de la structure.

    Prenons l'exemple du champ fwnode dans struct device. Ce champ DOIT être opaque pour les modules afin qu'ils ne puissent pas modifier les champs de device->fw_node ni faire d'hypothèses sur sa taille.

    Toutefois, si un module inclut <linux/fwnode.h> (directement ou indirectement), le champ fwnode de struct device n'est plus opaque pour lui. Le module peut ensuite apporter des modifications à device->fwnode->dev ou device->fwnode->ops. Ce scénario pose problème pour plusieurs raisons, comme suit:

    • Il peut briser les hypothèses que le code du noyau principal fait sur ses structures de données internes.

    • Si une future mise à jour du kernel modifie le struct fwnode_handle (le type de données de fwnode), le module ne fonctionne plus avec le nouveau kernel. De plus, stgdiff ne montrera aucune différence, car le module enfreint le KMI en manipulant directement les structures de données internes d'une manière qui ne peut pas être capturée en inspectant uniquement la représentation binaire.

  • Un module actuel est considéré comme incompatible avec le KMI lorsqu'il est chargé ultérieurement par un nouveau kernel incompatible. La gestion des versions des modules ajoute une vérification au moment de l'exécution pour éviter de charger accidentellement un module qui n'est pas compatible avec le noyau KMI. Cette vérification évite les problèmes d'exécution difficiles à déboguer et les plantages du noyau pouvant résulter d'une incompatibilité non détectée dans le KMI.

L'activation de la gestion des versions des modules évite tous ces problèmes.

Vérifier les incohérences de CRC sans démarrer l'appareil

stgdiff compare et signale les différences de CRC entre les noyaux, ainsi que d'autres différences ABI.

De plus, une compilation complète du noyau avec CONFIG_MODVERSIONS activé génère un fichier Module.symvers dans le cadre du processus de compilation normal. Ce fichier comporte une ligne pour chaque symbole exporté par le noyau (vmlinux) et les modules. Chaque ligne comprend la valeur CRC, le nom du symbole, l'espace de noms du symbole, le nom vmlinux ou le nom du module qui exporte le symbole, ainsi que le type d'exportation (par exemple, EXPORT_SYMBOL par rapport à EXPORT_SYMBOL_GPL).

Vous pouvez comparer les fichiers Module.symvers entre la compilation GKI et votre compilation pour vérifier s'il existe des différences de CRC dans les symboles exportés par vmlinux. S'il existe une différence de valeur CRC dans un symbole exporté par vmlinux et que ce symbole est utilisé par l'un des modules que vous chargez sur votre appareil, le module ne se charge pas.

Si vous ne disposez pas de tous les artefacts de compilation, mais que vous disposez des fichiers vmlinux du kernel GKI et de votre kernel, vous pouvez comparer les valeurs CRC d'un symbole spécifique en exécutant la commande suivante sur les deux kernels et en comparant la sortie:

nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>

Par exemple, la commande suivante vérifie la valeur CRC du symbole module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Résoudre les incohérences au niveau du CRC

Pour résoudre un problème de non-concordance de CRC lors du chargement d'un module, procédez comme suit:

  1. Créez le kernel GKI et le kernel de votre appareil à l'aide de l'option --kbuild_symtypes, comme indiqué dans la commande suivante:

    tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist

    Cette commande génère un fichier .symtypes pour chaque fichier .o. Pour en savoir plus, consultez KBUILD_SYMTYPES dans Kleaf.

    Pour Android 13 et versions antérieures, créez le kernel GKI et le kernel de votre appareil en ajoutant KBUILD_SYMTYPES=1 au début de la commande que vous utilisez pour créer le kernel, comme indiqué dans la commande suivante:

    KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh

    Lorsque vous utilisez build_abi.sh,, l'indicateur KBUILD_SYMTYPES=1 est déjà défini implicitement.

  2. Recherchez le fichier .c dans lequel le symbole avec une non-concordance de CRC est exporté à l'aide de la commande suivante:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. Le fichier .c possède un fichier .symtypes correspondant dans le GKI et les artefacts de compilation du kernel de votre appareil. Recherchez le fichier .symtypes à l'aide des commandes suivantes:

    cd bazel-bin/common/kernel_aarch64/symtypes
    ls -1 kernel/module/version.symtypes

    Sous Android 13 ou version antérieure, avec les anciens scripts de compilation, l'emplacement est probablement out/$BRANCH/common ou out_abi/$BRANCH/common.

    Chaque fichier .symtypes est un fichier texte brut composé de descriptions de type et de symboles:

    • Chaque ligne est au format key description, où la description peut faire référence à d'autres clés du même fichier.

    • Les clés telles que [s|u|e|t]#foo font référence à [struct|union|enum|typedef] foo. Exemple :

      t#bool typedef _Bool bool
      
    • Les clés sans préfixe x# ne sont que des noms de symboles. Exemple :

      find_module s#module * find_module ( const char * )
      
  4. Comparez les deux fichiers et corrigez toutes les différences.

Il est préférable de générer symtypes avec un build juste avant la modification problématique, puis au moment de la modification problématique. Enregistrer tous les fichiers permet de les comparer de manière groupée.

Par exemple,

for f in $(find good bad -name '*.symtypes' | sed -r 's;^(good|bad)/;;' | LANG=C sort -u); do
  diff -N -U0 --label good/"$f" --label bad/"$f" <(LANG=C sort good/"$f") <(LANG=C sort bad/"$f")
done

Sinon, il vous suffit de comparer les fichiers spécifiques qui vous intéressent.

Cas 1: Différences dues à la visibilité des types de données

Un nouveau #include peut extraire une nouvelle définition de type (par exemple, de struct foo) dans un fichier source. Dans ce cas, sa description dans le fichier .symtypes correspondant passe d'un structure_type foo { } vide à une définition complète.

Cela affectera tous les CRC de tous les symboles du fichier .symtypes dont les descriptions dépendent directement ou indirectement de la définition de type.

Par exemple, l'ajout de la ligne suivante au fichier include/linux/device.h de votre kernel entraîne des différences de CRC, dont l'une concerne module_layout():

 #include <linux/fwnode.h>

En comparant les module/version.symtypes de ce symbole, vous pouvez constater les différences suivantes:

 $ diff -u <GKI>/kernel/module/version.symtypes <your kernel>/kernel/module/version.symtypes
  --- <GKI>/kernel/module/version.symtypes
  +++ <your kernel>/kernel/module/version.symtypes
  @@ -334,12 +334,15 @@
  ...
  -s#fwnode_handle structure_type fwnode_handle { }
  +s#fwnode_reference_args structure_type fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
  ...

Si le kernel GKI dispose de la définition de type complète, mais que votre kernel ne l'a pas (très peu probable), fusionnez le dernier kernel Android Common dans votre kernel afin d'utiliser la dernière base de kernel GKI.

Dans la plupart des cas, la définition complète du type est manquante dans .symtypes pour le noyau GKI, mais elle est présente dans votre noyau en raison de directives #include supplémentaires.

Résolution pour Android 16 ou version ultérieure

Assurez-vous que le fichier source concerné inclut l'en-tête de stabilisation Android KABI:

#include <linux/android_kabi.h>

Pour chaque type concerné, ajoutez ANDROID_KABI_DECLONLY(name); à portée globale au fichier source concerné.

Par exemple, si la différence symtypes était la suivante:

--- good/drivers/android/vendor_hooks.symtypes
+++ bad/drivers/android/vendor_hooks.symtypes
@@ -1051 +1051,2 @@
-s#ubuf_info structure_type ubuf_info { }
+s#ubuf_info structure_type ubuf_info { member pointer_type { const_type { s#ubuf_info_ops } } ops data_member_location(0) , member t#refcount_t refcnt data_member_location(8) , member t#u8 flags data_member_location(12) } byte_size(16)
+s#ubuf_info_ops structure_type ubuf_info_ops { member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } , formal_parameter t#bool ) -> base_type void } complete data_member_location(0) , member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } ) -> base_type int byte_size(4) encoding(5) } link_skb data_member_location(8) } byte_size(16)

Le problème est alors que struct ubuf_info dispose désormais d'une définition complète dans symtypes. La solution consiste à ajouter une ligne à drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

Cette instruction indique à gendwarfksyms de traiter le type nommé comme non défini dans le fichier.

Une possibilité plus complexe est que le nouveau #include se trouve lui-même dans un fichier d'en-tête. Dans ce cas, vous devrez peut-être distribuer différents ensembles d'appels de macro ANDROID_KABI_DECLONLY dans des fichiers sources qui extraient indirectement des définitions de type supplémentaires, car certains d'entre eux peuvent déjà en avoir.

Pour une meilleure lisibilité, placez ces appels de macro au début du fichier source.

Résolution pour Android 15 et versions antérieures

Souvent, la solution consiste simplement à masquer le nouveau #include de genksyms.

#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif

Sinon, pour identifier le #include à l'origine de la différence, procédez comme suit:

  1. Ouvrez le fichier d'en-tête qui définit le symbole ou le type de données présentant cette différence. Par exemple, modifiez include/linux/fwnode.h pour struct fwnode_handle.

  2. Ajoutez le code suivant en haut du fichier d'en-tête:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. Dans le fichier .c du module présentant un décalage de CRC, ajoutez la ligne suivante en tant que première ligne avant toutes les lignes #include.

    #define CRC_CATCH 1
    
  4. Compilez votre module. L'erreur de temps de compilation qui en résulte affiche la chaîne du fichier d'en-tête #include qui a entraîné cette non-concordance de CRC. Exemple :

    In file included from .../drivers/clk/XXX.c:16:`
    In file included from .../include/linux/of_device.h:5:
    In file included from .../include/linux/cpu.h:17:
    In file included from .../include/linux/node.h:18:
    .../include/linux/device.h:16:2: error: "Included from here"
    #error "Included from here"
    

    L'un des maillons de cette chaîne de #include est dû à une modification apportée à votre kernel, qui est manquante dans le kernel GKI.

Cas 2: Différences dues à des modifications de type de données

Si le non-respect du CRC pour un symbole ou un type de données n'est pas dû à une différence de visibilité, il est dû à des modifications réelles (ajouts, suppressions ou modifications) du type de données lui-même.

Par exemple, la modification suivante dans votre kernel entraîne plusieurs différences de CRC, car de nombreux symboles sont indirectement affectés par ce type de modification:

diff --git a/include/linux/iommu.h b/include/linux/iommu.h
  --- a/include/linux/iommu.h
  +++ b/include/linux/iommu.h
  @@ -259,7 +259,7 @@ struct iommu_ops {
     void (*iotlb_sync)(struct iommu_domain *domain);
     phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
     phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
  -        dma_addr_t iova);
  +        dma_addr_t iova, unsigned long trans_flag);
     int (*add_device)(struct device *dev);
     void (*remove_device)(struct device *dev);
     struct iommu_group *(*device_group)(struct device *dev);

Une erreur de CRC concerne devm_of_platform_populate().

Si vous comparez les fichiers .symtypes de ce symbole, ils peuvent se présenter comme suit:

 $ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
  --- <GKI>/drivers/of/platform.symtypes
  +++ <your kernel>/drivers/of/platform.symtypes
  @@ -399,7 +399,7 @@
  ...
  -s#iommu_ops structure_type iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
    ( * add_device ) ( s#device * ) ; ...
  +s#iommu_ops structure_type iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...

Pour identifier le type modifié, procédez comme suit:

  1. Recherchez la définition du symbole dans le code source (généralement dans les fichiers .h).

    • Pour identifier les différences de symboles entre votre kernel et le kernel GKI, recherchez le commit en exécutant la commande suivante:
    git blame
    • Pour les symboles supprimés (lorsque vous supprimez un symbole dans un arbre et que vous souhaitez également le supprimer dans l'autre arbre), vous devez trouver la modification qui a supprimé la ligne. Utilisez la commande suivante sur l'arborescence où la ligne a été supprimée:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. Passez en revue la liste des commits renvoyés pour trouver la modification ou la suppression. Le premier commit est probablement celui que vous recherchez. Si ce n'est pas le cas, parcourez la liste jusqu'à ce que vous trouviez le commit.

  3. Une fois le commit identifié, rétablissez-le dans votre kernel ou mettez-le à jour pour supprimer la modification du CRC, puis importez-le dans ACK et fusionnez-le. Chaque interruption ABI résiduelle doit être examinée pour des raisons de sécurité et, si nécessaire, une interruption autorisée peut être enregistrée.

Privilégier la consommation de la marge existante

Certaines structures de GKI sont rembourrées pour permettre leur extension sans endommager les modules fournisseurs existants. Si un commit en amont (par exemple) ajoute un membre à une telle structure, il est possible de le modifier pour qu'il consomme une partie de la marge intérieure. Cette modification est ensuite masquée du calcul du CRC.

La macro ANDROID_KABI_RESERVE standardisée et auto-documentée réserve un espace (aligné) d'une valeur u64. Il est utilisé à la place d'une déclaration de membre.

Exemple :

struct data {
        u64 handle;
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
};

Le remplissage peut être utilisé, sans affecter les CRC de symbole, avec ANDROID_KABI_USE (ou ANDROID_KABI_USE2 ou d'autres variantes pouvant être définies).

Le membre sekret est disponible comme s'il était déclaré directement, mais la macro se développe en réalité en un membre d'union anonyme contenant sekret ainsi que des éléments utilisés par gendwarfksyms pour maintenir la stabilité du symtype.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Résolution pour Android 16 ou version ultérieure

Les CRC sont calculés par gendwarfksyms, qui utilise des informations de débogage DWARF, et sont donc compatibles avec les types C et Rust. La résolution varie en fonction du type de modification. Voici quelques exemples :

Énumérateurs nouveaux ou modifiés

Parfois, de nouveaux énumérateurs sont ajoutés, et parfois une valeur d'énumérateur MAX ou similaire est également affectée. Ces modifications sont sûres si elles n'"échappent" pas à la GKI ou si nous pouvons être sûrs que les modules du fournisseur ne peuvent pas se soucier de leurs valeurs.

Exemple :

 enum outcome {
       SUCCESS,
       FAILURE,
       RETRY,
+      TRY_HARDER,
       OUTCOME_LIMIT
 };

L'ajout de TRY_HARDER et le passage à OUTCOME_LIMIT peuvent être masqués du calcul du CRC avec des appels de macro à portée globale:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

Pour améliorer la lisibilité, placez-les juste après la définition de enum.

Un nouveau membre de structure occupant un trou existant

En raison de l'alignement, des octets inutilisés se trouvent entre urgent et scratch.

        void *data;
        bool urgent;
+       bool retry;
        void *scratch;

Aucun décalage de membre existant ni la taille de la structure ne sont affectés par l'ajout de retry. Toutefois, cela peut affecter les CRC de symbole ou la représentation ABI, ou les deux.

Elle ne sera plus prise en compte dans le calcul du CRC:

        void *data;
        bool urgent;
+       ANDROID_KABI_IGNORE(1, bool retry);
        void *scratch_space;

Le membre retry est disponible comme s'il était déclaré directement, mais la macro se développe en réalité en un membre d'union anonyme contenant retry ainsi que des éléments utilisés par gendwarfksyms pour maintenir la stabilité du symtype.

Extension d'une structure avec de nouveaux membres

Les membres sont parfois ajoutés à la fin d'une structure. Cela n'a aucune incidence sur les décalages des membres existants ni sur les utilisateurs existants de la structure qui n'y accèdent que par pointeur. La taille de la structure affecte son CRC, et les modifications apportées à celle-ci peuvent être supprimées avec une invocation de macro supplémentaire au niveau de la portée globale, comme suit:

struct data {
        u64 handle;
        u64 counter;
        ANDROID_KABI_IGNORE(1, void *sekret);
};

ANDROID_KABI_BYTE_SIZE(data, 16);

Pour une meilleure lisibilité, placez-le juste après la définition de struct.

Toutes les autres modifications apportées à un type ou au type d'un symbole

Il arrive parfois que des modifications ne rentrent pas dans l'une des catégories précédentes, ce qui entraîne des modifications de CRC qui ne peuvent pas être supprimées à l'aide des macros précédentes.

Dans ce cas, la description symtypes d'origine d'un type ou d'un symbole peut être fournie avec une invocation de ANDROID_KABI_TYPE_STRING au niveau de la portée globale.

struct data {
        /* extensive changes */
};

ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");

Pour plus de lisibilité, placez-le juste après la définition du type ou du symbole.

Résolution pour Android 15 et versions antérieures

Les modifications de type et de type de symbole doivent être masquées pour genksyms. Pour ce faire, contrôlez le prétraitement avec __GENKSYMS__.

Des transformations de code arbitraires peuvent être exprimées de cette manière.

Par exemple, pour masquer un nouveau membre occupant un trou dans une structure existante:

struct parcel {
        void *data;
        bool urgent;
#ifndef __GENKSYMS__
        bool retry;
#endif
        void *scratch_space;
};