Android-Kernel-ABI-Monitoring

Sie können die ABI-Monitoring-Tools (Application Binary Interface) verwenden, die in Android 11 und höher verfügbar sind, um die In-Kernel-ABI von Android-Kerneln zu stabilisieren. Das Tool erfasst und vergleicht ABI-Darstellungen aus vorhandenen Kernel-Binärdateien (vmlinux + GKI-Module). Diese ABI-Darstellungen sind die .stg-Dateien und die Symbollisten. Die Schnittstelle, die die Darstellung bietet, wird als Kernel-Modulschnittstelle (KMI) bezeichnet. Mit den Tools können Sie Änderungen an der KMI verfolgen und beheben.

Die ABI-Monitoring-Tools werden in AOSP entwickelt und verwenden STG (oder libabigail in Android 13 und niedriger), um Darstellungen zu generieren und zu vergleichen.

Auf dieser Seite werden die Tools, das Erfassen und Analysieren von ABI-Darstellungen sowie die Verwendung solcher Darstellungen zur Stabilisierung des In-Kernel-ABI beschrieben. Auf dieser Seite finden Sie auch Informationen dazu, wie Sie Änderungen an den Android-Kerneln vornehmen können.

Prozess

Die Analyse des ABI des Kernels umfasst mehrere Schritte, von denen die meisten automatisiert werden können:

  1. Erstellen Sie den Kernel und seine ABI-Darstellung.
  2. ABI-Unterschiede zwischen dem Build und einer Referenz analysieren
  3. Aktualisieren Sie die ABI-Darstellung (falls erforderlich).
  4. Mit Symbollisten arbeiten

Die folgende Anleitung gilt für jeden Kernel, den Sie mit einer unterstützten Toolchain erstellen können, z. B. die vorkonfigurierte Clang-Toolchain. repo manifests sind für alle gängigen Android-Kernel-Branches und für mehrere gerätespezifische Kernel verfügbar. Sie sorgen dafür, dass beim Erstellen einer Kernel-Distribution für die Analyse die richtige Toolchain verwendet wird.

Symbollisten

Das KMI enthält nicht alle Symbole im Kernel oder sogar nicht alle der über 30.000 exportierten Symbole. Stattdessen sind die Symbole, die von Anbietermodulen verwendet werden können, explizit in einer Reihe von Symbollistendateien aufgeführt, die öffentlich im Kernel-Baum verwaltet werden (gki/{ARCH}/symbols/* oder android/abi_gki_{ARCH}_* in Android 15 und niedriger). Die Vereinigung aller Symbole in allen Symbollistendateien definiert die als stabil gehaltenen KMI-Symbole. Eine Beispieldatei für Symbollisten ist gki/aarch64/symbols/db845c, die die für das DragonBoard 845c erforderlichen Symbole deklariert.

Nur die in einer Symbolliste aufgeführten Symbole und die zugehörigen Strukturen und Definitionen gelten als Teil des KMI. Sie können Änderungen an Ihren Symbollisten vornehmen, wenn die benötigten Symbole nicht vorhanden sind. Nachdem neue Schnittstellen in einer Symbolliste enthalten sind und Teil der KMI-Beschreibung sind, werden sie als stabil gepflegt und dürfen nach dem Einfrieren des Branches nicht aus der Symbolliste entfernt oder geändert werden.

Jeder KMI-Kernel-Zweig des Android Common Kernel (ACK) hat seine eigenen Symbollisten. Es wird nicht versucht, die ABI-Stabilität zwischen verschiedenen KMI-Kernel-Branches zu gewährleisten. Beispielsweise ist die KMI für android12-5.10 völlig unabhängig von der KMI für android13-5.10.

ABI-Tools verwenden KMI-Symbollisten, um einzuschränken, welche Schnittstellen auf Stabilität überwacht werden müssen. Anbieter müssen ihre eigenen Symbollisten einreichen und aktualisieren, um dafür zu sorgen, dass die von ihnen verwendeten Schnittstellen ABI-kompatibel bleiben. Eine Liste der Symbollisten für den android16-6.12-Kernel finden Sie beispielsweise unter https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols.

Eine Symbolliste enthält die Symbole, die für den jeweiligen Anbieter oder das jeweilige Gerät erforderlich sind. Die vollständige Liste, die von den Tools verwendet wird, ist die Vereinigung aller KMI-Symbollistendateien. ABI-Tools ermitteln die Details jedes Symbols, einschließlich Funktionssignatur und verschachtelter Datenstrukturen.

Wenn das KMI eingefroren ist, sind keine Änderungen an den vorhandenen KMI-Schnittstellen zulässig. Sie sind stabil. Anbieter können dem KMI jedoch jederzeit Symbole hinzufügen, solange die Stabilität des vorhandenen ABI dadurch nicht beeinträchtigt wird. Neu hinzugefügte Symbole werden so lange als stabil betrachtet, bis sie in einer KMI-Symbolliste aufgeführt sind. Symbole sollten nur dann aus einer Liste für einen Kernel entfernt werden, wenn bestätigt werden kann, dass kein Gerät jemals mit einer Abhängigkeit von diesem Symbol ausgeliefert wurde.

Eine KMI-Symbolliste für ein Gerät können Sie mithilfe der Anleitung unter Mit Symbollisten arbeiten generieren. Viele Partner reichen eine Symbolliste pro ACK ein, dies ist jedoch keine zwingende Anforderung. Wenn es die Wartung erleichtert, können Sie mehrere Symbollisten einreichen.

KMI erweitern

KMI-Symbole und zugehörige Strukturen bleiben stabil, d. h. Änderungen, die stabile Schnittstellen in einem Kernel mit einem eingefrorenen KMI beeinträchtigen, können nicht akzeptiert werden. Der GKI-Kernel bleibt jedoch für Erweiterungen offen, sodass Geräte, die später im Jahr auf den Markt kommen, nicht alle ihre Abhängigkeiten definieren müssen, bevor das KMI eingefroren wird. Sie können dem KMI neue Symbole für neue oder vorhandene exportierte Kernelfunktionen hinzufügen, auch wenn das KMI eingefroren ist. Neue Kernel-Patches können auch akzeptiert werden, wenn sie die KMI nicht beeinträchtigen.

KMI-Unterbrechungen

Ein Kernel hat Quellen und aus diesen Quellen werden Binärdateien erstellt. ABI-überwachte Kernelzweige enthalten eine ABI-Darstellung des aktuellen GKI-ABI (in Form einer .stg-Datei). Nachdem die Binärdateien (vmlinux, Image und alle GKI-Module) erstellt wurden, kann eine ABI-Darstellung aus den Binärdateien extrahiert werden. Jede Änderung an einer Kernel-Quelldatei kann sich auf die Binärdateien und damit auch auf die extrahierte .stg auswirken. Der AbiAnalyzer-Analysator vergleicht die committete .stg-Datei mit der aus den Build-Artefakten extrahierten und setzt in Gerrit ein Lint-1-Label auf die Änderung, wenn er einen semantischen Unterschied findet.

Umgang mit ABI-Unterbrechungen

Der folgende Patch führt beispielsweise zu einer sehr offensichtlichen ABI-Unterbrechung:

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.

Wenn Sie das Build-ABI mit diesem Patch ausführen, wird das Tool mit einem Fehlercode ungleich null beendet und meldet eine ABI-Abweichung, die in etwa so aussieht:

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

Beim Build wurden ABI-Unterschiede erkannt

Der häufigste Grund für Fehler ist, wenn ein Treiber ein neues Symbol aus dem Kernel verwendet, das in keiner der Symbollisten enthalten ist.

Wenn das Symbol nicht in Ihrer Symbolliste enthalten ist, müssen Sie zuerst prüfen, ob es mit EXPORT_SYMBOL_GPL(symbol_name) exportiert wurde. Aktualisieren Sie dann die Symbolliste und die ABI-Darstellung. Mit den folgenden Änderungen wird beispielsweise die neue Incremental FS-Funktion dem android-12-5.10-Branch hinzugefügt. Dazu gehört auch die Aktualisierung der Symbolliste und der ABI-Darstellung.

  • Ein Beispiel für eine Änderungsfunktion finden Sie unter aosp/1345659.
  • Ein Beispiel für eine Symbolliste finden Sie unter aosp/1346742.
  • Ein Beispiel für die Änderung der ABI-Darstellung finden Sie unter aosp/1349377.

Wenn das Symbol exportiert wurde (entweder von Ihnen oder es wurde zuvor exportiert), es aber von keinem anderen Treiber verwendet wird, wird möglicherweise ein Buildfehler wie der folgende angezeigt.

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

Aktualisieren Sie dazu die KMI-Symbolliste sowohl im Kernel als auch im ACK (siehe ABI-Darstellung aktualisieren). Ein Beispiel zum Aktualisieren einer Symbolliste und der ABI-Darstellung im ACK finden Sie unter aosp/1367601.

Kernel-ABI-Unterbrechungen beheben

Sie können Kernel-ABI-Unterbrechungen beheben, indem Sie den Code so umstrukturieren, dass das ABI nicht geändert wird, oder die ABI-Darstellung aktualisieren. Anhand des folgenden Diagramms können Sie den für Ihre Situation am besten geeigneten Ansatz ermitteln.

ABI-Ablaufdiagramm für Unterbrechungen

Abbildung 1: Lösung für ABI-Unterbrechungen

Code umstrukturieren, um ABI-Änderungen zu vermeiden

Ändern Sie das vorhandene ABI nach Möglichkeit nicht. In vielen Fällen können Sie Ihren Code umstrukturieren, um Änderungen zu entfernen, die sich auf das ABI auswirken.

  • Refactoring von Änderungen an Struct-Feldern Wenn durch eine Änderung das ABI für eine Debugfunktion geändert wird, setzen Sie in den Strukturen und Quellreferenzen #ifdef um die Felder herum und achten Sie darauf, dass die für #ifdef verwendete CONFIG für die Produktions-defconfig und gki_defconfig deaktiviert ist. Ein Beispiel dafür, wie einer Struktur eine Debug-Konfiguration hinzugefügt werden kann, ohne die ABI zu verletzen, finden Sie in diesem Patch-Set.

  • Funktionen umstrukturieren, um den Kern nicht zu ändern Wenn ACK neue Funktionen hinzugefügt werden müssen, um die Partnermodule zu unterstützen, versuchen Sie, den ABI-Teil der Änderung zu überarbeiten, um die Änderung des Kernel-ABI zu vermeiden. Ein Beispiel für die Verwendung des vorhandenen Kernel-ABI, um zusätzliche Funktionen hinzuzufügen, ohne das Kernel-ABI zu ändern, finden Sie unter aosp/1312213.

Fehlerhafte ABIs in Android Gerrit beheben

Wenn Sie das Kernel-ABI nicht absichtlich verletzt haben, müssen Sie die Ursache anhand der Anleitungen in den ABI-Monitoring-Tools ermitteln. Die häufigsten Ursachen für Unterbrechungen sind geänderte Datenstrukturen und die zugehörigen Symbol-CRC-Änderungen oder Änderungen an Konfigurationsoptionen, die zu einer der oben genannten Ursachen führen. Beheben Sie zuerst die vom Tool gefundenen Probleme.

Sie können die ABI-Ergebnisse lokal reproduzieren. Weitere Informationen finden Sie unter Kernel und ABI-Darstellung erstellen.

Lint-1-Labels

Wenn Sie Änderungen in einen Branch hochladen, der eine eingefrorene oder finalisierte KMI enthält, müssen die Änderungen die AbiAnalyzer bestehen, damit sie die stabile ABI nicht inkompatibel beeinflussen. Während dieses Vorgangs sucht AbiAnalyzer nach dem ABI-Bericht, der während des Builds erstellt wird (ein erweiterter Build, bei dem der normale Build und dann einige ABI-Extraktions- und Vergleichsschritte ausgeführt werden).

Wenn AbiAnalyzer einen nicht leeren Bericht findet, wird das Lint-1-Label festgelegt und die Änderung kann erst eingereicht werden, wenn das Problem behoben ist, also das Patch-Set das Lint+1-Label erhält.

Kernel-ABI aktualisieren

Wenn die Änderung des ABI unvermeidlich ist, müssen Sie Ihre Codeänderungen, die ABI-Darstellung und die Symbolliste auf die ACK anwenden. So sorgen Sie dafür, dass Lint die -1 entfernt und die GKI-Kompatibilität nicht beeinträchtigt:

  1. Lade die Codeänderungen in die ACK-Datei hoch.

  2. Warten Sie, bis Sie eine Codeüberprüfung +2 für das Patch-Set erhalten.

  3. Aktualisieren Sie die ABI-Referenzdarstellung.

  4. Führen Sie die Codeänderungen und die Änderung des ABI-Updates zusammen.

ABI-Codeänderungen in die ACK hochladen

Das Aktualisieren des ACK-ABI hängt von der Art der Änderung ab.

  • Wenn eine ABI-Änderung mit einer Funktion zusammenhängt, die sich auf CTS- oder VTS-Tests auswirkt, kann die Änderung in der Regel so übernommen werden, wie sie ist. Beispiele:

  • Wenn eine ABI-Änderung für eine Funktion gilt, die mit dem ACK geteilt werden kann, kann diese Änderung unverändert in das ACK übernommen werden. Die folgenden Änderungen sind beispielsweise für CTS- oder VTS-Tests nicht erforderlich, können aber mit dem ACK geteilt werden:

  • Wenn durch eine ABI-Änderung eine neue Funktion eingeführt wird, die nicht in ACK enthalten sein muss, können Sie die Symbole mithilfe eines Symbolstups in ACK einfügen, wie im folgenden Abschnitt beschrieben.

Stubs für ACK verwenden

Stubs dürfen nur für Kerneländerungen erforderlich sein, die dem ACK nicht zugutekommen, z. B. Leistungs- und Energieänderungen. In der folgenden Liste finden Sie Beispiele für Stubs und teilweise Cherry-Picks in ACK für GKI.

  • Stub für die Core-Isolate-Funktion (aosp/1284493). Die Funktionen in ACK sind nicht erforderlich, aber die Symbole müssen in ACK vorhanden sein, damit Ihre Module diese Symbole verwenden können.

  • Platzhaltersymbol für Anbietermodul (aosp/1288860).

  • ABI-spezifische Auswahl der mm-Ereignis-Tracking-Funktion pro Prozess (aosp/1288454). Der ursprüngliche Patch wurde für die Bestätigung ausgewählt und dann so gekürzt, dass nur die erforderlichen Änderungen enthalten sind, um die ABI-Differenzen für task_struct und mm_event_count zu beheben. Mit diesem Patch wird auch das mm_event_type-Enum aktualisiert, sodass es die endgültigen Mitglieder enthält.

  • Teilweise Auswahl von ABI-Änderungen an der thermischen Struktur, für die nicht nur die neuen ABI-Felder hinzugefügt werden mussten.

    • Der Patch aosp/1255544 behebt ABI-Unterschiede zwischen dem Partnerkernel und ACK.

    • Mit dem Patch aosp/1291018 wurden die Funktionsprobleme behoben, die beim GKI-Test des vorherigen Patches gefunden wurden. Die Korrektur umfasste die Initialisierung des Sensorparameter-Structs, um mehrere Wärmezonen für einen einzelnen Sensor zu registrieren.

  • CONFIG_NL80211_TESTMODE ABI-Änderungen (aosp/1344321) Mit diesem Patch wurden die erforderlichen Strukturänderungen für das ABI hinzugefügt und dafür gesorgt, dass die zusätzlichen Felder keine funktionalen Unterschiede verursachen. So können Partner CONFIG_NL80211_TESTMODE in ihre Produktionskerne aufnehmen und gleichzeitig die GKI-Compliance aufrechterhalten.

KMI zur Laufzeit erzwingen

Die GKI-Kernel verwenden die Konfigurationsoptionen TRIM_UNUSED_KSYMS=y und UNUSED_KSYMS_WHITELIST=<union of all symbol lists>, mit denen die exportierten Symbole (z. B. Symbole, die mit EXPORT_SYMBOL_GPL() exportiert wurden) auf diejenigen in einer Symbolliste beschränkt werden. Alle anderen Symbole sind nicht exportiert und das Laden eines Moduls, das ein nicht exportiertes Symbol erfordert, wird abgelehnt. Diese Einschränkung wird zum Zeitpunkt des Builds erzwungen und fehlende Einträge werden gemeldet.

Für Entwicklungszwecke können Sie einen GKI-Kernel-Build verwenden, der keine Symbolbeschneidung enthält. Das bedeutet, dass alle normalerweise exportierten Symbole verwendet werden können. Sie finden diese Builds unter ci.android.com.kernel_debug_aarch64

KMI mithilfe der Modulversionierung erzwingen

Die GKI-Kernel (Generic Kernel Image) verwenden die Modulversionierung (CONFIG_MODVERSIONS) als zusätzliche Maßnahme, um die KMI-Compliance zur Laufzeit durchzusetzen. Die Modulversionierung kann zu CRC-Abweichungen (Cyclic Redundancy Check) beim Laden des Moduls führen, wenn die erwartete KMI eines Moduls nicht mit der vmlinux-KMI übereinstimmt. Das folgende Beispiel zeigt einen typischen Fehler, der beim Laden des Moduls aufgrund einer CRC-Nichtübereinstimmung für das Symbol module_layout() auftritt:

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 ''

Verwendung der Modulversionsverwaltung

Die Versionierung von Modulen ist aus folgenden Gründen nützlich:

  • Mit der Modulversionierung werden Änderungen an der Sichtbarkeit der Datenstruktur erfasst. Wenn Module undurchsichtige Datenstrukturen ändern, also Datenstrukturen, die nicht Teil der KMI sind, funktionieren sie nach zukünftigen Änderungen an der Struktur nicht mehr.

    Betrachten Sie beispielsweise das Feld fwnode in struct device. Dieses Feld MUSS für Module undurchsichtig sein, damit sie keine Änderungen an Feldern von device->fw_node vornehmen oder Annahmen über deren Größe treffen können.

    Wenn ein Modul jedoch <linux/fwnode.h> (direkt oder indirekt) enthält, ist das Feld fwnode in struct device für dieses Modul nicht mehr undurchsichtig. Das Modul kann dann Änderungen an device->fwnode->dev oder device->fwnode->ops vornehmen. Dieses Szenario ist aus mehreren Gründen problematisch:

    • Das kann Annahmen des Kernel-Kerncodes über seine internen Datenstrukturen aufheben.

    • Wenn bei einem zukünftigen Kernelupdate die struct fwnode_handle (der Datentyp von fwnode) geändert wird, funktioniert das Modul nicht mehr mit dem neuen Kernel. Außerdem zeigt stgdiff keine Unterschiede, da das Modul die KMI verletzt, indem es interne Datenstrukturen direkt auf eine Weise manipuliert, die nicht durch bloße Prüfung der Binärdarstellung erfasst werden kann.

  • Ein aktuelles Modul gilt als KMI-inkompatibel, wenn es zu einem späteren Zeitpunkt von einem neuen, inkompatiblen Kernel geladen wird. Durch die Modulversionierung wird eine Laufzeitprüfung hinzugefügt, um zu verhindern, dass versehentlich ein Modul geladen wird, das nicht KMI-kompatibel mit dem Kernel ist. Diese Prüfung verhindert schwer zu behebende Laufzeitprobleme und Kernel-Abstürze, die aufgrund einer nicht erkannten Inkompatibilität in der KMI auftreten können.

Wenn Sie die Modulversionierung aktivieren, können diese Probleme vermieden werden.

CRC-Abweichungen prüfen, ohne das Gerät zu starten

stgdiff vergleicht CRC-Abweichungen zwischen Kernen und meldet diese zusammen mit anderen ABI-Unterschieden.

Außerdem wird bei einem vollständigen Kernel-Build mit aktivierter CONFIG_MODVERSIONS-Funktion im Rahmen des normalen Build-Prozesses eine Module.symvers-Datei generiert. Diese Datei enthält eine Zeile für jedes Symbol, das vom Kernel (vmlinux) und den Modulen exportiert wurde. Jede Zeile besteht aus dem CRC-Wert, dem Symbolnamen, dem Symbol-Namespace, dem vmlinux- oder Modulnamen, über den das Symbol exportiert wird, und dem Exporttyp (z. B. EXPORT_SYMBOL oder EXPORT_SYMBOL_GPL).

Sie können die Module.symvers-Dateien des GKI-Builds mit denen Ihres Builds vergleichen, um nach CRC-Unterschieden in den von vmlinux exportierten Symbolen zu suchen. Wenn der CRC-Wert eines Symbols, das mit vmlinux exportiert wurde, von einem der Module abweicht, das Sie auf Ihr Gerät laden, wird das Modul nicht geladen.

Wenn Sie nicht alle Build-Artefakte haben, aber die vmlinux-Dateien des GKI-Kernels und Ihres Kernels, können Sie die CRC-Werte für ein bestimmtes Symbol vergleichen. Führen Sie dazu den folgenden Befehl auf beiden Kerneln aus und vergleichen Sie die Ausgabe:

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

Mit dem folgenden Befehl wird beispielsweise der CRC-Wert für das Symbol module_layout geprüft:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

CRC-Abweichungen beheben

So beheben Sie einen CRC-Nichtübereinstimmung beim Laden eines Moduls:

  1. Erstellen Sie den GKI-Kernel und den Gerätekernel mit der Option --kbuild_symtypes, wie im folgenden Befehl gezeigt:

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

    Mit diesem Befehl wird für jede .o-Datei eine .symtypes-Datei generiert. Weitere Informationen finden Sie unter KBUILD_SYMTYPES in Kleaf.

    Erstellen Sie für Android 13 und niedriger den GKI-Kernel und den Gerätekernel, indem Sie dem Befehl zum Erstellen des Kernels KBUILD_SYMTYPES=1 voranstellen, wie im folgenden Befehl gezeigt:

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

    Wenn Sie build_abi.sh, verwenden, ist das Flag KBUILD_SYMTYPES=1 bereits implizit gesetzt.

  2. Suchen Sie mit dem folgenden Befehl nach der .c-Datei, in der das Symbol mit CRC-Nichtübereinstimmung exportiert wurde:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. Die .c-Datei hat eine entsprechende .symtypes-Datei in der GKI und Ihre Build-Artefakte für den Gerätekernel. Suchen Sie mit den folgenden Befehlen nach der .symtypes-Datei:

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

    Unter Android 13 und niedriger ist der Speicherort bei Verwendung der bisherigen Build-Scripts wahrscheinlich out/$BRANCH/common oder out_abi/$BRANCH/common.

    Jede .symtypes-Datei ist eine Nur-Text-Datei mit Typ- und Symbolbeschreibungen:

    • Jede Zeile hat das Format key description. Die Beschreibung kann sich auf andere Schlüssel in derselben Datei beziehen.

    • Tasten wie [s|u|e|t]#foo beziehen sich auf [struct|union|enum|typedef] foo. Beispiel:

      t#bool typedef _Bool bool
      
    • Schlüssel ohne x#-Präfix sind nur Symbolnamen. Beispiel:

      find_module s#module * find_module ( const char * )
      
  4. Vergleichen Sie die beiden Dateien und beheben Sie alle Unterschiede.

Am besten generieren Sie symtypes mit einem Build kurz vor und dann nach der problematischen Änderung. Wenn Sie alle Dateien speichern, können sie im Bulk-Verfahren verglichen werden.

Beispiel:

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

Andernfalls können Sie einfach die gewünschten Dateien vergleichen.

Fall 1: Unterschiede aufgrund der Sichtbarkeit des Datentyps

Eine neue #include kann eine neue Typdefinition (z. B. von struct foo) in eine Quelldatei einfügen. In diesen Fällen ändert sich die Beschreibung in der entsprechenden .symtypes-Datei von einer leeren structure_type foo { } in eine vollständige Definition.

Dies wirkt sich auf alle CRCs aller Symbole in der .symtypes-Datei aus, deren Beschreibungen direkt oder indirekt von der Typdefinition abhängen.

Wenn Sie der Datei include/linux/device.h im Kernel beispielsweise die folgende Zeile hinzufügen, kommt es zu CRC-Abweichungen, eine davon für module_layout():

 #include <linux/fwnode.h>

Beim Vergleich der module/version.symtypes für dieses Symbol sind folgende Unterschiede zu erkennen:

 $ 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 ] ; }
  ...

Wenn der GKI-Kernel die vollständige Typdefinition enthält, sie aber in Ihrem Kernel fehlt (sehr unwahrscheinlich), führen Sie den neuesten Android Common Kernel in Ihren Kernel ein, damit Sie die neueste GKI-Kernelbasis verwenden.

In den meisten Fällen fehlt dem GKI-Kernel die vollständige Typdefinition in .symtypes, aber Ihrem Kernel aufgrund zusätzlicher #include-Direktiven.

Auflösung für Android 16 und höher

Achten Sie darauf, dass die betroffene Quelldatei den Android KABI-Stabilisierungsheader enthält:

#include <linux/android_kabi.h>

Fügen Sie für jeden betroffenen Typ ANDROID_KABI_DECLONLY(name); im globalen Gültigkeitsbereich zur betroffenen Quelldatei hinzu.

Angenommen, die symtypes-Differenze lautet:

--- 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)

Dann besteht das Problem darin, dass struct ubuf_info jetzt eine vollständige Definition in symtypes hat. Die Lösung besteht darin, drivers/android/vendor_hooks.c eine Zeile hinzuzufügen:

ANDROID_KABI_DECLONLY(ubuf_info);

Dadurch wird gendwarfksyms angewiesen, den benannten Typ in der Datei als nicht definiert zu behandeln.

Eine komplexere Möglichkeit ist, dass sich die neue #include selbst in einer Headerdatei befindet. In diesem Fall müssen Sie möglicherweise verschiedene ANDROID_KABI_DECLONLY-Makroaufrufe auf die Quelldateien verteilen, die indirekt zusätzliche Typdefinitionen einbeziehen, da einige davon möglicherweise bereits einige der Typdefinitionen enthalten.

Aus Gründen der Lesbarkeit sollten Sie solche Makroaufrufe am Anfang der Quelldatei platzieren.

Lösung für Android 15 und niedriger

Häufig lässt sich das Problem beheben, indem die neue #include für genksyms ausgeblendet wird.

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

Andernfalls können Sie so vorgehen, um die #include zu ermitteln, die den Unterschied verursacht:

  1. Öffnen Sie die Headerdatei, in der das Symbol oder der Datentyp mit dieser Abweichung definiert ist. Ändern Sie beispielsweise include/linux/fwnode.h in struct fwnode_handle.

  2. Fügen Sie oben in der Headerdatei den folgenden Code ein:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. Fügen Sie in der .c-Datei des Moduls mit der CRC-Nichtübereinstimmung die folgende Zeile als erste Zeile vor allen #include-Zeilen hinzu.

    #define CRC_CATCH 1
    
  4. Kompilieren Sie das Modul. Der resultierende Buildzeitfehler zeigt die Kette der Headerdatei #include, die zu dieser CRC-Nichtübereinstimmung geführt hat. Beispiel:

    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"
    

    Einer der Glieder in dieser #include-Kette ist auf eine Änderung in Ihrem Kernel zurückzuführen, die im GKI-Kernel fehlt.

Fall 2: Unterschiede aufgrund von Änderungen am Datentyp

Wenn die CRC-Nichtübereinstimmung für ein Symbol oder einen Datentyp nicht auf eine unterschiedliche Sichtbarkeit zurückzuführen ist, liegt dies an tatsächlichen Änderungen (Hinzufügungen, Entfernungen oder Änderungen) am Datentyp selbst.

Die folgende Änderung am Kernel führt beispielsweise zu mehreren CRC-Abweichungen, da viele Symbole indirekt von dieser Art von Änderung betroffen sind:

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);

Eine CRC-Abweichung betrifft devm_of_platform_populate().

Wenn Sie die .symtypes-Dateien für dieses Symbol vergleichen, könnte das so aussehen:

 $ 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 * ) ; ...

So ermitteln Sie den geänderten Typ:

  1. Suchen Sie im Quellcode (normalerweise in .h-Dateien) nach der Definition des Symbols.

    • Wenn Sie Symbolunterschiede zwischen Ihrem Kernel und dem GKI-Kernel finden möchten, führen Sie den folgenden Befehl aus, um den Commit zu ermitteln:
    git blame
    • Bei gelöschten Symbolen (wenn ein Symbol in einem Baum gelöscht wurde und Sie es auch im anderen Baum löschen möchten) müssen Sie die Änderung finden, durch die die Linie gelöscht wurde. Verwenden Sie den folgenden Befehl auf dem Baum, in dem die Linie gelöscht wurde:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. Sehen Sie sich die zurückgegebene Liste der Commits an, um die Änderung oder das Löschen zu finden. Der erste Commit ist wahrscheinlich der, den Sie suchen. Wenn nicht, gehen Sie die Liste durch, bis Sie den Commit finden.

  3. Nachdem Sie den Commit identifiziert haben, kehren Sie ihn entweder in Ihrem Kernel zurück oder aktualisieren Sie ihn, um die CRC-Änderung zu unterdrücken. Laden Sie ihn dann auf ACK hoch und lassen Sie ihn zusammenführen. Jede verbleibende ABI-Unterbrechung muss aus Sicherheitsgründen überprüft werden. Bei Bedarf kann eine zulässige Unterbrechung aufgezeichnet werden.

Vorhandenes Padding bevorzugt verwenden

Einige Strukturen in GKI sind um zusätzliche Elemente erweitert, damit sie erweitert werden können, ohne dass vorhandene Anbietermodule beschädigt werden. Wenn einem solchen Element beispielsweise durch einen vorgelagerten Commit ein Mitglied hinzugefügt wird, kann es sein, dass es möglich ist, stattdessen einen Teil des Paddings zu verwenden. Diese Änderung wird dann bei der CRC-Berechnung ausgeblendet.

Das standardisierte, selbstdokumentierende Makro ANDROID_KABI_RESERVE reserviert einen (ausgerichteten) Bereich von u64. Sie wird anstelle einer Mitgliedererklärung verwendet.

Beispiel:

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

Mit ANDROID_KABI_USE (oder ANDROID_KABI_USE2 oder anderen definierbaren Varianten) kann Padding ohne Auswirkungen auf Symbol-CRCs verwendet werden.

Das Mitglied sekret ist verfügbar, als wäre es direkt deklariert. Das Makro wird jedoch tatsächlich in ein anonymes Union-Mitglied mit sekret und Elementen erweitert, die von gendwarfksyms zur Aufrechterhaltung der Stabilität des Symboltyps verwendet werden.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Auflösung für Android 16 und höher

CRCs werden von gendwarfksyms berechnet, wobei DWARF-Debugging-Informationen verwendet werden. So werden sowohl C- als auch Rust-Typen unterstützt. Die Auflösung hängt von der Art der Änderung ab. Hier einige Beispiele:

Neue oder geänderte Zähler

Manchmal werden neue Enumeratoren hinzugefügt und gelegentlich ist auch ein MAX-Wert oder ein ähnlicher Enumeratorwert betroffen. Diese Änderungen sind sicher, wenn sie GKI nicht „entkommen“ oder wenn wir sicher sein können, dass sich Anbietermodule nicht um ihre Werte kümmern können.

Beispiel:

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

Das Hinzufügen von TRY_HARDER und die Änderung zu OUTCOME_LIMIT können mit Makroaufrufen im globalen Gültigkeitsbereich von der CRC-Berechnung ausgeblendet werden:

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

Platzieren Sie sie aus Gründen der Lesbarkeit direkt nach der enum-Definition.

Ein neues Bauteil, das ein vorhandenes Loch belegt

Aufgrund der Ausrichtung gibt es zwischen urgent und scratch nicht verwendete Bytes.

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

Die Größe des Elements oder die Größe des Bauteils wird durch das Hinzufügen von retry nicht beeinflusst. Es kann jedoch Symbol-CRCs oder die ABI-Darstellung oder beides beeinträchtigen.

Dadurch wird es von der CRC-Berechnung ausgeschlossen:

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

Das Mitglied retry ist verfügbar, als wäre es direkt deklariert. Das Makro wird jedoch tatsächlich in ein anonymes Union-Mitglied mit retry und Elementen erweitert, die von gendwarfksyms zur Aufrechterhaltung der Stabilität des Symboltyps verwendet werden.

Erweiterung eines Gebäudes durch neue Mitglieder

Manchmal werden Elemente am Ende eines Bauwerks hinzugefügt. Das hat keine Auswirkungen auf die Offset-Werte vorhandener Mitglieder oder auf vorhandene Nutzer der Struktur, die nur über einen Verweis darauf zugreifen. Die Größe der Struktur wirkt sich auf ihren CRC aus. Änderungen daran können mit einer zusätzlichen Makroaufrufung im globalen Gültigkeitsbereich unterdrückt werden:

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

ANDROID_KABI_BYTE_SIZE(data, 16);

Platzieren Sie sie aus Gründen der Lesbarkeit direkt nach der Definition von struct.

Alle anderen Änderungen an einem Typ oder dem Typ eines Symbols

In sehr seltenen Fällen gibt es Änderungen, die in keine der vorherigen Kategorien fallen. Dies führt zu CRC-Änderungen, die mit den vorherigen Makros nicht unterdrückt werden können.

In diesen Fällen kann die ursprüngliche symtypes-Beschreibung eines Typs oder Symbols mit einer ANDROID_KABI_TYPE_STRING-Aufrufung im globalen Gültigkeitsbereich versehen werden.

struct data {
        /* extensive changes */
};

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

Platzieren Sie sie zur besseren Lesbarkeit direkt nach der Typ- oder Symboldefinition.

Lösung für Android 15 und niedriger

Änderungen am Schrift- und Symboltyp müssen für genksyms ausgeblendet sein. Dazu können Sie die Vorverarbeitung mit __GENKSYMS__ steuern.

So können beliebige Codetransformationen ausgedrückt werden.

So blenden Sie beispielsweise ein neues Mitglied aus, das ein Loch in einem vorhandenen Bauwerk einnimmt:

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