Monitoraggio dell'ABI del kernel di Android

Puoi utilizzare gli strumenti di monitoraggio dell'interfaccia binaria dell'applicazione (ABI), disponibili in Android 11 e versioni successive, per stabilizzare l'ABI in-kernel dei kernel Android. Gli strumenti raccolgono e confrontano le rappresentazioni ABI dai binari del kernel esistenti (vmlinux + moduli GKI). Queste rappresentazioni ABI sono i file .stg e gli elenchi di simboli. L'interfaccia su cui la rappresentazione fornisce una visualizzazione è chiamata interfaccia del modulo kernel (KMI). Puoi utilizzare gli strumenti per monitorare e mitigare le modifiche all'indicatore chiave di misurazione.

Gli strumenti di monitoraggio ABI sono sviluppati in AOSP e utilizzano STG (o libabigail in Android 13 e versioni precedenti) per generare e confrontare le rappresentazioni.

Questa pagina descrive gli strumenti, la procedura di raccolta e analisi delle rappresentazioni ABI e l'utilizzo di queste rappresentazioni per garantire la stabilità dell'ABI in-kernel. Questa pagina fornisce anche informazioni per contribuire con modifiche ai kernel Android.

Elabora

L'analisi dell'ABI del kernel richiede più passaggi, la maggior parte dei quali può essere automatizzata:

  1. Crea il kernel e la relativa rappresentazione ABI.
  2. Analizza le differenze ABI tra la build e un riferimento.
  3. Aggiorna la rappresentazione dell'ABI (se necessario).
  4. Utilizzare gli elenchi di simboli.

Le seguenti istruzioni funzionano per qualsiasi kernel che puoi compilare utilizzando una toolchain supportata (ad esempio la toolchain Clang precompilata). repo manifests sono disponibili per tutti i rami del kernel comune di Android e per diversi kernel specifici del dispositivo. Verificano che venga utilizzata la toolchain corretta quando crei una distribuzione del kernel per l'analisi.

Elenchi di simboli

Il KMI non include tutti i simboli nel kernel e nemmeno tutti gli oltre 30.000 simboli esportati. I simboli che possono essere utilizzati dai moduli del fornitore sono invece elencati esplicitamente in un insieme di file di elenchi di simboli gestiti pubblicamente nell'albero del kernel (gki/{ARCH}/symbols/* o android/abi_gki_{ARCH}_* in Android 15 e versioni precedenti). L'unione di tutti i simboli in tutti i file dell'elenco dei simboli definisce l'insieme di simboli KMI mantenuti stabili. Un file di elenco di simboli di esempio è gki/aarch64/symbols/db845c, che dichiara i simboli richiesti per DragonBoard 845c.

Solo i simboli elencati in un elenco di simboli e le relative strutture e definizioni sono considerati parte del KMI. Puoi pubblicare modifiche agli elenchi di simboli se i simboli che ti servono non sono presenti. Una volta che le nuove interfacce sono in un elenco di simboli e fanno parte della descrizione KMI, vengono mantenute stabili e non devono essere rimosse dall'elenco di simboli o modificate dopo il blocco del ramo.

Ogni ramo del kernel KMI (Kernel comune Android) ha il proprio insieme di elenchi di simboli. Non viene effettuato alcun tentativo di fornire stabilità ABI tra diversi rami del kernel KMI. Ad esempio, l'interfaccia macchina-uomo per android12-5.10 è completamente indipendente dall'interfaccia macchina-uomo per android13-5.10.

Gli strumenti ABI utilizzano elenchi di simboli KMI per limitare le interfacce che devono essere monitorate per la stabilità. I fornitori sono tenuti a inviare e aggiornare i propri elenchi di simboli per verificare che le interfacce su cui si basano mantengano la compatibilità ABI. Ad esempio, per visualizzare un elenco di elenchi di simboli per il kernel android16-6.12, consulta https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols

Un elenco di simboli contiene i simboli segnalati come necessari per il particolare fornitore o dispositivo. L'elenco completo utilizzato dagli strumenti è l'unione di tutti i file dell'elenco dei simboli KMI. Gli strumenti ABI determinano i dettagli di ogni simbolo, tra cui la firma della funzione e le strutture di dati nidificate.

Quando l'interfaccia KMI è bloccata, non sono consentite modifiche alle interfacce KMI esistenti; sono stabili. Tuttavia, i fornitori sono liberi di aggiungere simboli all'interfaccia KMI in qualsiasi momento, a condizione che le aggiunte non influiscano sulla stabilità dell'interfaccia ABI esistente. I simboli aggiunti di recente vengono mantenuti stabili non appena vengono citati da un elenco di simboli KMI. I simboli non devono essere rimossi da un elenco per un kernel, a meno che non sia possibile confermare che nessun dispositivo è mai stato spedito con una dipendenza da quel simbolo.

Puoi generare un elenco di simboli KMI per un dispositivo seguendo le istruzioni riportate in Come utilizzare gli elenchi di simboli. Molti partner inviano un elenco di simboli per ogni ACK, ma questo non è un requisito rigido. Se può essere utile per la manutenzione, puoi inviare più elenchi di simboli.

Estendere l'intent di acquisto

Sebbene i simboli KMI e le strutture correlate vengano mantenuti stabili (il che significa che non è possibile accettare modifiche che interrompono le interfacce stabili in un kernel con un KMI bloccato), il kernel GKI rimane aperto alle estensioni, in modo che i dispositivi spediti più avanti nell'anno non debbano definire tutte le loro dipendenze prima che il KMI venga bloccato. Per estendere l'interfaccia KMI, puoi aggiungere nuovi simboli all'interfaccia KMI per funzioni del kernel nuove o esistenti esportate, anche se l'interfaccia KMI è bloccata. Potrebbero essere accettate anche nuove patch del kernel se non interrompono la KMI.

Informazioni sui malfunzionamenti di KMI

Un kernel ha origini e i binari vengono creati da queste origini. I rami del kernel monitorati dall'ABI includono una rappresentazione dell'ABI GKI attuale (sotto forma di file .stg). Una volta compilati i binari (vmlinux, Image e qualsiasi modulo GKI), è possibile estrarre una rappresentazione ABI dai binari. Qualsiasi modifica apportata a un file di origine del kernel può influire sui file binari e, a sua volta, anche sul .stg estratto. L'analisi della conformità ABI confronta il file .stg di cui è stato eseguito il commit con quello estratto dagli artefatti di build e imposta un'etichetta Lint-1 sulla modifica in Gerrit se rileva una differenza semantica.

Gestire le interruzioni dell'ABI

Ad esempio, la seguente patch introduce un'interruzione dell'ABI molto evidente:

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.

Quando esegui build ABI con questa patch applicata, lo strumento esce con un codice di errore diverso da zero e segnala una differenza ABI simile a questa:

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

Differenze ABI rilevate al momento della build

Il motivo più comune degli errori è quando un driver utilizza un nuovo simbolo del kernel che non è presente in nessuno degli elenchi di simboli.

Se il simbolo non è incluso nell'elenco dei simboli, devi prima verificare che sia esportato con EXPORT_SYMBOL_GPL(symbol_name) e poi aggiornare l'elenco dei simboli e la rappresentazione ABI. Ad esempio, le seguenti modifiche aggiungono la nuova funzionalità Incremental FS al ramo android-12-5.10, che include l'aggiornamento dell'elenco dei simboli e della rappresentazione ABI.

  • L'esempio di modifica della funzionalità è disponibile in aosp/1345659.
  • L'esempio di elenco di simboli è in aosp/1346742.
  • L'esempio di modifica della rappresentazione ABI è disponibile in aosp/1349377.

Se il simbolo viene esportato (da te o in precedenza), ma nessun altro driver lo utilizza, potresti ricevere un errore di compilazione simile al seguente.

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

Per risolvere il problema, aggiorna l'elenco dei simboli KMI sia nel kernel che nell'ACK (vedi Aggiornare la rappresentazione dell'ABI). Per un esempio di aggiornamento di un elenco di simboli e della rappresentazione ABI nell'ACK, consulta aosp/1367601.

Risolvere le interruzioni dell'ABI del kernel

Puoi gestire le interruzioni dell'ABI del kernel refactoring del codice per non modificare l'ABI o aggiornando la rappresentazione dell'ABI. Utilizza il seguente grafico per determinare l'approccio migliore per la tua situazione.

Diagramma di flusso della rottura dell'ABI

Figura 1. Risoluzione della rottura dell'ABI

Refactoring del codice per evitare modifiche all'ABI

Fai il possibile per evitare di modificare l'ABI esistente. In molti casi, puoi refactoring del codice per rimuovere le modifiche che influiscono sull'ABI.

  • Refactoring delle modifiche ai campi della struttura. Se una modifica modifica l'ABI per una funzionalità di debug, aggiungi un #ifdef intorno ai campi (nelle struct e nei riferimenti di origine) e assicurati che CONFIG utilizzato per #ifdef sia disattivato per la defconfig di produzione e gki_defconfig. Per un esempio di come aggiungere una configurazione di debug a una struttura senza interrompere l'ABI, consulta questo patchset.

  • Refactoring delle funzionalità per non modificare il kernel principale. Se è necessario aggiungere nuove funzionalità all'ACK per supportare i moduli partner, prova a eseguire il refactoring della parte ABI della modifica per evitare di modificare l'ABI del kernel. Per un esempio di utilizzo dell'ABI del kernel esistente per aggiungere funzionalità aggiuntive senza modificare l'ABI del kernel, consulta aosp/1312213.

Correggere un'ABI non funzionante su Android Gerrit

Se non hai interrotto intenzionalmente l'ABI del kernel, devi eseguire un'indagine utilizzando le indicazioni fornite dagli strumenti di monitoraggio dell'ABI. Le cause più comuni di interruzioni sono le modifiche alle strutture dei dati e le modifiche al CRC dei simboli associati oppure le modifiche alle opzioni di configurazione che comportano una delle cause sopra menzionate. Inizia risolvendo i problemi rilevati dallo strumento.

Puoi riprodurre i risultati dell'ABI localmente. Consulta Compilare il kernel e la relativa rappresentazione ABI.

Informazioni sulle etichette Lint-1

Se carichi modifiche a un ramo contenente un KMI bloccato o finalizzato, le modifiche devono superare le analisi di conformità e compatibilità ABI per garantire che le modifiche alla rappresentazione ABI riflettano l'ABI effettiva e non contengano incompatibilità (rimozione di simboli o modifiche di tipo).

Ciascuna di queste analisi ABI potrebbe impostare l'etichetta Lint-1 e bloccare l'invio delle modifiche fino a quando tutti i problemi non vengono risolti o l'etichetta non viene ignorata.

Aggiornare l'ABI del kernel

Se la modifica dell'ABI è inevitabile, devi applicare le modifiche al codice, la rappresentazione dell'ABI e l'elenco dei simboli all'ACK. Per fare in modo che Lint rimuova -1 e non interrompa la compatibilità con GKI, segui questi passaggi:

  1. Carica le modifiche al codice nell'ACK.

  2. Attendi di ricevere un Code-Review +2 per il patchset.

  3. Aggiorna la rappresentazione ABI di riferimento.

  4. Unisci le modifiche al codice e la modifica dell'aggiornamento dell'ABI.

Carica le modifiche al codice ABI nell'ACK

L'aggiornamento dell'ABI dell'ACK dipende dal tipo di modifica apportata.

  • Se una modifica dell'ABI è correlata a una funzionalità che influisce sui test CTS o VTS, la modifica può in genere essere selezionata per l'ACK così com'è. Ad esempio:

  • Se una modifica dell'ABI riguarda una funzionalità che può essere condivisa con l'ACK, questa modifica può essere selezionata per l'ACK così com'è. Ad esempio, le seguenti modifiche non sono necessarie per il test CTS o VTS, ma possono essere condivise con l'ACK:

  • Se una modifica dell'ABI introduce una nuova funzionalità che non deve essere inclusa nell'ACK, puoi introdurre i simboli nell'ACK utilizzando uno stub come descritto nella sezione seguente.

Utilizzare stub per ACK

Gli stub devono essere necessari solo per le modifiche al kernel di base che non avvantaggiano l'ACK, ad esempio le modifiche alle prestazioni e al consumo energetico. Il seguente elenco descrive in dettaglio esempi di stub e cherry-pick parziali in ACK per GKI.

  • Stub della funzionalità Core-isolate (aosp/1284493). Le funzionalità in ACK non sono necessarie, ma i simboli devono essere presenti in ACK affinché i moduli possano utilizzarli.

  • Simbolo segnaposto per il modulo del fornitore (aosp/1288860).

  • Selezione mirata solo ABI della funzionalità di monitoraggio degli eventi mm per processo (aosp/1288454). La patch originale è stata selezionata per l'ACK e poi tagliata per includere solo le modifiche necessarie per risolvere la differenza ABI per task_struct e mm_event_count. Questa patch aggiorna anche l'enumerazione mm_event_type in modo che contenga i membri finali.

  • Estrazione parziale delle modifiche all'ABI della struttura termica che richiedevano più della semplice aggiunta dei nuovi campi ABI.

    • Patch aosp/1255544 ha risolto le differenze ABI tra il kernel del partner e ACK.

    • La patch aosp/1291018 ha risolto i problemi funzionali riscontrati durante i test GKI della patch precedente. La correzione includeva l'inizializzazione della struttura del parametro del sensore per registrare più zone termiche in un unico sensore.

  • CONFIG_NL80211_TESTMODE Modifiche ABI (aosp/1344321). Questa patch ha aggiunto le modifiche strutturali necessarie per l'ABI e ha verificato che i campi aggiuntivi non causassero differenze funzionali, consentendo ai partner di includere CONFIG_NL80211_TESTMODE nei propri kernel di produzione e di mantenere la conformità a GKI.

Applica l'interfaccia KMI al runtime

I kernel GKI utilizzano le opzioni di configurazione TRIM_UNUSED_KSYMS=y e UNUSED_KSYMS_WHITELIST=<union of all symbol lists>, che limitano i simboli esportati (ad esempio i simboli esportati utilizzando EXPORT_SYMBOL_GPL()) a quelli elencati in un elenco di simboli. Tutti gli altri simboli non vengono esportati e il caricamento di un modulo che richiede un simbolo non esportato viene negato. Questa limitazione viene applicata in fase di compilazione e le voci mancanti vengono segnalate.

A scopo di sviluppo, puoi utilizzare una build del kernel GKI che non include il troncamento dei simboli (il che significa che è possibile utilizzare tutti i simboli esportati di solito). Per trovare queste build, cerca le build kernel_debug_aarch64 su ci.android.com.

Applica l'interfaccia KMI utilizzando il controllo delle versioni dei moduli

I kernel Generic Kernel Image (GKI) utilizzano il controllo delle versioni dei moduli (CONFIG_MODVERSIONS) come misura aggiuntiva per garantire la conformità KMI in fase di runtime. Il controllo delle versioni dei moduli può causare errori di mancata corrispondenza del controllo di ridondanza ciclica (CRC) durante il caricamento del modulo se il KMI previsto di un modulo non corrisponde al KMI vmlinux. Ad esempio, il seguente è un errore tipico che si verifica al momento del caricamento del modulo a causa di una mancata corrispondenza del CRC per il simbolo 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 ''

Utilizzi del controllo delle versioni dei moduli

Il controllo delle versioni dei moduli è utile per i seguenti motivi:

  • Il controllo delle versioni dei moduli rileva le modifiche alla visibilità della struttura dei dati. Se i moduli modificano le strutture di dati opache, ovvero le strutture di dati che non fanno parte della KMI, si interrompono dopo le modifiche future alla struttura.

    Ad esempio, considera il campo fwnode in struct device. Questo campo DEVE essere opaco per i moduli in modo che non possano apportare modifiche ai campi di device->fw_node o fare ipotesi sulle sue dimensioni.

    Tuttavia, se un modulo include <linux/fwnode.h> (direttamente o indirettamente), il campo fwnode in struct device non è più opaco. Il modulo può quindi apportare modifiche a device->fwnode->dev o device->fwnode->ops. Questo scenario è problematico per diversi motivi, indicati di seguito:

    • Può violare le ipotesi che il codice del kernel principale fa sulle sue strutture di dati interne.

    • Se un futuro aggiornamento del kernel modifica struct fwnode_handle (il tipo di dati di fwnode), il modulo non funziona più con il nuovo kernel. Inoltre, stgdiff non mostrerà alcuna differenza perché il modulo interrompe il KMI manipolando direttamente le strutture di dati interne in modi che non possono essere acquisiti ispezionando solo la rappresentazione binaria.

  • Un modulo corrente è considerato incompatibile con KMI quando viene caricato in un secondo momento da un nuovo kernel incompatibile. Il controllo delle versioni dei moduli aggiunge un controllo di runtime per evitare di caricare accidentalmente un modulo non compatibile con l'interfaccia KMI del kernel. Questo controllo previene problemi di runtime e arresti anomali del kernel difficili da eseguire il debug che potrebbero derivare da un'incompatibilità non rilevata nel KMI.

L'attivazione del controllo delle versioni dei moduli previene tutti questi problemi.

Verifica la presenza di mancate corrispondenze CRC senza avviare il dispositivo

stgdiff confronta e segnala le mancate corrispondenze CRC tra i kernel insieme ad altre differenze ABI.

Inoltre, una build completa del kernel con CONFIG_MODVERSIONS abilitato genera un file Module.symvers nell'ambito della normale procedura di build. Questo file ha una riga per ogni simbolo esportato dal kernel (vmlinux) e dai moduli. Ogni riga è costituita dal valore CRC, dal nome del simbolo, dallo spazio dei nomi del simbolo, dal nome del modulo o vmlinux che esporta il simbolo e dal tipo di esportazione (ad esempio, EXPORT_SYMBOL rispetto a EXPORT_SYMBOL_GPL).

Puoi confrontare i file Module.symvers tra la build GKI e la tua build per verificare la presenza di differenze CRC nei simboli esportati da vmlinux. Se esiste una differenza nel valore CRC in qualsiasi simbolo esportato da vmlinux e che viene utilizzato da uno dei moduli caricati nel dispositivo, il modulo non viene caricato.

Se non disponi di tutti gli artefatti di build, ma hai i file vmlinux del kernel GKI e del tuo kernel, puoi confrontare i valori CRC per un simbolo specifico eseguendo il seguente comando su entrambi i kernel e confrontando l'output:

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

Ad esempio, il seguente comando controlla il valore CRC per il simbolo module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Risolvere i mancati riscontri CRC

Per risolvere un problema di mancata corrispondenza del CRC durante il caricamento di un modulo:

  1. Crea il kernel GKI e il kernel del dispositivo utilizzando l'opzione --kbuild_symtypes come mostrato nel comando seguente:

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

    Questo comando genera un file .symtypes per ogni file .o. Per i dettagli, consulta KBUILD_SYMTYPES in Kleaf.

    Per Android 13 e versioni precedenti, crea il kernel GKI e il kernel del dispositivo anteponendo KBUILD_SYMTYPES=1 al comando che utilizzi per creare il kernel, come mostrato nel comando seguente:

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

    Quando utilizzi build_abi.sh,, il flag KBUILD_SYMTYPES=1 è già impostato implicitamente.

  2. Trova il file .c in cui viene esportato il simbolo con mancata corrispondenza del CRC utilizzando il seguente comando:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. Il file .c ha un file .symtypes corrispondente nel GKI e negli artefatti di build del kernel del dispositivo. Individua il file .symtypes utilizzando i seguenti comandi:

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

    In Android 13 e versioni precedenti, utilizzando gli script di build legacy, la posizione è probabilmente out/$BRANCH/common o out_abi/$BRANCH/common.

    Ogni file .symtypes è un file di testo normale costituito da descrizioni di tipo e simbolo:

    • Ogni riga ha il formato key description, dove la descrizione può fare riferimento ad altre chiavi nello stesso file.

    • Tasti come [s|u|e|t]#foo fanno riferimento a [struct|union|enum|typedef] foo. Ad esempio:

      t#bool typedef _Bool bool
      
    • Le chiavi senza prefisso x# sono solo nomi di simboli. Ad esempio:

      find_module s#module * find_module ( const char * )
      
  4. Confronta i due file e correggi tutte le differenze.

È preferibile generare symtypes con una build appena prima della modifica problematica e poi in corrispondenza della modifica problematica. Il salvataggio di tutti i file consente di confrontarli collettivamente.

Ad esempio,

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

In caso contrario, confronta solo i file specifici di tuo interesse.

Scenario 1: differenze dovute alla visibilità del tipo di dati

Un nuovo #include può inserire una nuova definizione di tipo (ad esempio di struct foo) in un file sorgente. In questi casi, la sua descrizione nel file .symtypes corrispondente cambierà da un structure_type foo { } vuoto a una definizione completa.

Ciò influirà su tutti i CRC di tutti i simboli nel file .symtypes le cui descrizioni dipendono direttamente o indirettamente dalla definizione del tipo.

Ad esempio, l'aggiunta della seguente riga al file include/linux/device.h nel kernel causa mancate corrispondenze CRC, una delle quali riguarda module_layout():

 #include <linux/fwnode.h>

Il confronto tra module/version.symtypes per questo simbolo rivela le seguenti differenze:

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

Se il kernel GKI ha la definizione completa del tipo, ma il tuo kernel non la include (molto improbabile), unisci l'ultimo kernel comune Android al tuo kernel in modo da utilizzare la base del kernel GKI più recente.

Nella maggior parte dei casi, il kernel GKI non include la definizione completa del tipo in .symtypes, ma il tuo kernel la include a causa di direttive #include aggiuntive.

Risoluzione per Android 16 e versioni successive

Assicurati che il file sorgente interessato includa l'intestazione di stabilizzazione KABI di Android:

#include <linux/android_kabi.h>

Per ogni tipo interessato, aggiungi ANDROID_KABI_DECLONLY(name); all'ambito globale al file sorgente interessato.

Ad esempio, se la differenza symtypes era questa:

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

Il problema è che ora struct ubuf_info ha una definizione completa in symtypes. La soluzione consiste nell'aggiungere una riga a drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

In questo modo, gendwarfksyms tratta il tipo denominato come non definito nel file.

Una possibilità più complessa è che il nuovo #include si trovi in un file di intestazione. In questo caso, potrebbe essere necessario distribuire diversi set di invocazioni di macro ANDROID_KABI_DECLONLY nei file sorgente che includono indirettamente definizioni di tipi aggiuntive, poiché alcune di queste potrebbero già contenere alcune delle definizioni di tipi.

Per una maggiore leggibilità, posiziona queste invocazioni di macro all'inizio del file di origine.

Risoluzione per Android 15 e versioni precedenti

Spesso, la soluzione consiste semplicemente nel nascondere il nuovo #include da genksyms.

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

In caso contrario, per identificare il #include che causa la differenza, segui questi passaggi:

  1. Apri il file di intestazione che definisce il simbolo o il tipo di dati con questa differenza. Ad esempio, modifica include/linux/fwnode.h per struct fwnode_handle.

  2. Aggiungi il seguente codice all'inizio del file di intestazione:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. Nel file .c del modulo con mancata corrispondenza del CRC, aggiungi quanto segue come prima riga prima di qualsiasi riga #include.

    #define CRC_CATCH 1
    
  4. Compila il modulo. L'errore di compilazione risultante mostra la catena di file di intestazione #include che ha causato questa mancata corrispondenza del CRC. Ad esempio:

    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"
    

    Uno dei link di questa catena di #include è dovuto a una modifica apportata al tuo kernel, che non è presente nel kernel GKI.

Scenario 2: differenze dovute a modifiche del tipo di dati

Se la mancata corrispondenza del CRC per un simbolo o un tipo di dati non è dovuta a una differenza di visibilità, è dovuta a modifiche effettive (aggiunte, rimozioni o modifiche) nel tipo di dati stesso.

Ad esempio, apportare la seguente modifica al kernel causa diverse mancate corrispondenze CRC in quanto molti simboli sono interessati indirettamente da questo tipo di modifica:

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

Una mancata corrispondenza del CRC riguarda devm_of_platform_populate().

Se confronti i file .symtypes per quel simbolo, il risultato potrebbe essere il seguente:

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

Per identificare il tipo modificato:

  1. Trova la definizione del simbolo nel codice sorgente (di solito nei file .h).

    • Per le differenze tra i simboli del kernel e del kernel GKI, trova il commit eseguendo il seguente comando:
    git blame
    • Per i simboli eliminati (dove un simbolo viene eliminato in un albero e vuoi eliminarlo anche nell'altro albero), devi trovare la modifica che ha eliminato la riga. Utilizza il seguente comando nell'albero in cui è stata eliminata la riga:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. Esamina l'elenco dei commit restituiti per individuare la modifica o l'eliminazione. Il primo commit è probabilmente quello che stai cercando. In caso contrario, scorri l'elenco fino a trovare il commit.

  3. Dopo aver identificato il commit, ripristinalo nel kernel o aggiornalo per eliminare la modifica del CRC e caricalo su ACK per la fusione. Ogni pausa ABI residua dovrà essere esaminata per verificarne la sicurezza e, se necessario, potrà essere registrata una pausa consentita.

Preferisce consumare il padding esistente

Alcune strutture nel GKI vengono riempite per consentire la loro estensione senza interrompere i moduli del fornitore esistenti. Se un commit upstream (ad esempio) aggiunge un membro a una struttura di questo tipo, potrebbe essere possibile modificarla per utilizzare parte del padding. Questa modifica viene quindi nascosta dal calcolo del CRC.

La macro standardizzata e autodescrittiva ANDROID_KABI_RESERVE riserva uno spazio di u64 allineato. Viene utilizzata al posto di una dichiarazione del membro.

Ad esempio:

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

Il riempimento può essere utilizzato, senza influire sui CRC dei simboli, con ANDROID_KABI_USE (o ANDROID_KABI_USE2 o altre varianti che possono essere definite).

Il membro sekret è disponibile come se fosse dichiarato direttamente, ma la macro si espande in realtà in un membro dell'unione anonimo contenente sekret e elementi utilizzati da gendwarfksyms per mantenere la stabilità del tipo di simbolo.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Risoluzione per Android 16 e versioni successive

I CRC vengono calcolati da gendwarfksyms, che utilizza le informazioni di debug DWARF, supportando così i tipi C e Rust. La risoluzione varia in base al tipo di modifica. Di seguito sono riportati alcuni esempi.

Enumeratori nuovi o modificati

A volte vengono aggiunti nuovi enumeratori e occasionalmente viene interessato anche un valore dell'enumeratore MAX o simile. Queste modifiche sono sicure se non "escono" dal GKI o se possiamo essere certi che i moduli del fornitore non possano interessarsi ai loro valori.

Ad esempio:

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

L'aggiunta di TRY_HARDER e la modifica a OUTCOME_LIMIT possono essere nascoste dal calcolo del CRC con chiamate di macro nell'ambito globale:

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

Per una maggiore leggibilità, inseriscili subito dopo la definizione di enum.

Un nuovo membro della struttura che occupa un foro esistente

A causa dell'allineamento, ci saranno byte inutilizzati tra urgent e scratch.

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

Nessun offset dei membri esistente o la dimensione della struttura è interessato dall'aggiunta di retry. Tuttavia, potrebbe influire sui CRC dei simboli o sulla rappresentazione ABI o su entrambi.

In questo modo, non verrà incluso nel calcolo del CRC:

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

Il membro retry è disponibile come se fosse dichiarato direttamente, ma la macro si espande in realtà in un membro dell'unione anonimo contenente retry e elementi utilizzati da gendwarfksyms per mantenere la stabilità del tipo di simbolo.

Estensione di una struttura con nuovi membri

A volte i membri vengono aggiunti alla fine di una struttura. Ciò non influisce sulle compensazioni dei membri esistenti né sugli utenti esistenti della struttura che vi accedono solo tramite puntatore. Le dimensioni della struttura influiscono sul relativo CRC e le modifiche possono essere eliminate con un'invocazione di macro aggiuntiva nell'ambito globale, come segue:

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

ANDROID_KABI_BYTE_SIZE(data, 16);

Per una maggiore leggibilità, inseriscilo subito dopo la definizione di struct.

Tutte le altre modifiche a un tipo o al tipo di un simbolo

In rare occasioni, potrebbero verificarsi modifiche che non rientrano in una delle categorie precedenti, con conseguenti modifiche del CRC che non possono essere eliminate utilizzando le macro precedenti.

In questi casi, la descrizione originale symtypes di un tipo o simbolo può essere fornita con un'invocazione di ANDROID_KABI_TYPE_STRING nell'ambito globale.

struct data {
        /* extensive changes */
};

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

Per una maggiore leggibilità, posiziona questo elemento subito dopo la definizione del tipo o del simbolo.

Risoluzione per Android 15 e versioni precedenti

Le modifiche al tipo e al tipo di simbolo devono essere nascoste da genksyms. Puoi farlo controllando la preelaborazione con __GENKSYMS__.

In questo modo è possibile esprimere trasformazioni di codice arbitrarie.

Ad esempio, per nascondere un nuovo membro che occupa un posto in una struttura esistente:

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