Stabilità dell'ABI

La stabilità dell'interfaccia binaria dell'applicazione (ABI) è un prerequisito per gli aggiornamenti solo del framework perché i moduli del fornitore possono dipendere dalle librerie condivise del Vendor Native Development Kit (VNDK) che risiedono nella partizione di sistema. All'interno di una versione Android, le librerie condivise VNDK di nuova creazione devono essere compatibili con ABI con le librerie condivise VNDK rilasciate in precedenza, in modo che i moduli del fornitore possano funzionare con tali librerie senza ricompilazione e senza errori di runtime. Tra le versioni di Android, le librerie VNDK possono essere modificate e non ci sono garanzie ABI.

Per garantire la compatibilità ABI, Android 9 include un controllo ABI dell'intestazione, come descritto nelle sezioni seguenti.

Informazioni sulla conformità VNDK e ABI

Il VNDK è un insieme restrittivo di librerie a cui i moduli del fornitore possono collegarsi e che consentono aggiornamenti solo del framework. La conformità ABI si riferisce alla capacità di una versione più recente di una libreria condivisa di funzionare come previsto con un modulo che è collegato dinamicamente ad essa (cioè funziona come una versione precedente della libreria).

Informazioni sui simboli esportati

Un simbolo esportato (noto anche come simbolo globale ) si riferisce a un simbolo che soddisfa tutti i seguenti requisiti:

  • Esportato dalle intestazioni pubbliche di una libreria condivisa.
  • Appare nella tabella .dynsym del file .so corrispondente alla libreria condivisa.
  • Ha un legame DEBOLE o GLOBALE.
  • La visibilità è PREDEFINITA o PROTETTA.
  • L'indice della sezione non è INDEFINITO.
  • Il tipo è FUNC o OBJECT.

Le intestazioni pubbliche di una libreria condivisa sono definite come le intestazioni disponibili per altre librerie/binari tramite gli export_include_dirs , export_header_lib_headers , export_static_lib_headers , export_shared_lib_headers ed export_generated_headers nelle definizioni Android.bp del modulo corrispondente alla libreria condivisa.

Informazioni sui tipi raggiungibili

Un tipo raggiungibile è qualsiasi tipo C/C++ integrato o definito dall'utente che è raggiungibile direttamente o indirettamente tramite un simbolo esportato ED esportato tramite intestazioni pubbliche. Ad esempio, libfoo.so ha la funzione Foo , che è un simbolo esportato che si trova nella tabella .dynsym . La libreria libfoo.so include quanto segue:

foo_exported.h foo.privato.h
typedef struct foo_private foo_private_t;

typedef struct foo {
  int m1;
  int *m2;
  foo_private_t *mPfoo;
} foo_t;

typedef struct bar {
  foo_t mfoo;
} bar_t;

bool Foo(int id, bar_t *bar_ptr);
typedef struct foo_private {
  int m1;
  float mbar;
} foo_private_t;
Android.bp
cc_library {
  name : libfoo,
  vendor_available: true,
  vndk {
    enabled : true,
  }
  srcs : ["src/*.cpp"],
  export_include_dirs : [
    "include"
  ],
}
tabella .dynsym
Num Value Size Type Bind Vis Ndx Name
1 0 0 FUNC GLOB DEF UND dlerror@libc
2 1ce0 20 FUNC GLOB DEF 12 Foo

Guardando Foo , i tipi raggiungibili diretti/indiretti includono:

Tipo Descrizione
bool Tipo di ritorno di Foo .
int Tipo di primo parametro Foo .
bar_t * Tipo del secondo parametro Foo. Tramite bar_t * , bar_t viene esportato tramite foo_exported.h .

bar_t contiene un membro mfoo , di tipo foo_t , che viene esportato tramite foo_exported.h , che comporta l'esportazione di più tipi:
  • int : è il tipo di m1 .
  • int * : è il tipo di m2 .
  • foo_private_t * : è il tipo di mPfoo .

Tuttavia, foo_private_t NON è raggiungibile perché non viene esportato tramite foo_exported.h . ( foot_private_t * è opaco, quindi sono consentite le modifiche apportate a foo_private_t .)

Una spiegazione simile può essere fornita anche per i tipi raggiungibili tramite specificatori di classi di base e parametri di modello.

Garantire la conformità ABI

È necessario garantire la conformità ABI per le librerie contrassegnate vendor_available: true e vndk.enabled: true nei file Android.bp corrispondenti. Per esempio:

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}

Per i tipi di dati raggiungibili direttamente o indirettamente da una funzione esportata, le seguenti modifiche a una libreria sono classificate come ABI-breaking:

Tipo di dati Descrizione
Strutture e Classi
  • Modificare la dimensione del tipo di classe o del tipo struct.
  • Classi base
    • Aggiungi o rimuovi classi base.
    • Aggiungi o rimuovi classi base ereditate virtualmente.
    • Cambia l'ordine delle classi base.
  • Funzioni dei membri
    • Rimuovere le funzioni membro*.
    • Aggiungi o rimuovi argomenti dalle funzioni membro.
    • Modificare i tipi di argomento oi tipi restituiti delle funzioni membro*.
    • Modifica il layout della tabella virtuale.
  • Membri dei dati
    • Rimuovere i membri dati statici.
    • Aggiungi o rimuovi membri dati non statici.
    • Modificare i tipi di membri dati.
    • Modificare gli offset in membri dati non statici**.
    • Modificare i qualificatori const , volatile e/o restricted dei membri dati***.
    • Eseguire il downgrade degli identificatori di accesso dei membri dati***.
  • Modifica gli argomenti del modello.
sindacati
  • Aggiungi o rimuovi membri dati.
  • Modifica la dimensione del tipo di unione.
  • Modificare i tipi di membri dati.
  • Modificare l'ordine dei membri dati.
Enumerazioni
  • Cambia il tipo sottostante.
  • Cambia i nomi degli enumeratori.
  • Modifica i valori degli enumeratori.
Simboli globali
  • Rimuovere i simboli esportati dalle intestazioni pubbliche.
  • Per simboli globali di tipo FUNC
    • Aggiungi o rimuovi argomenti.
    • Modifica i tipi di argomento.
    • Modifica il tipo di reso.
    • Eseguire il downgrade dell'identificatore di accesso***.
  • Per simboli globali di tipo OBJECT
    • Modificare il tipo C/C++ corrispondente.
    • Eseguire il downgrade dell'identificatore di accesso***.

* Le funzioni membro pubbliche e private non devono essere modificate o rimosse perché le funzioni pubbliche inline possono fare riferimento a funzioni membro private. I riferimenti ai simboli alle funzioni membro private possono essere mantenuti nei binari del chiamante. La modifica o la rimozione di funzioni membro private dalle librerie condivise può causare file binari non compatibili con le versioni precedenti.

** Gli offset per i membri di dati pubblici o privati ​​non devono essere modificati perché le funzioni inline possono fare riferimento a questi membri di dati nel corpo della funzione. La modifica degli offset dei membri dati può comportare file binari non compatibili con le versioni precedenti.

*** Anche se questi non cambiano il layout di memoria del tipo, ci sono differenze semantiche che potrebbero portare a librerie non funzionanti come previsto.

Utilizzo degli strumenti di conformità ABI

Quando viene compilata una libreria VNDK, l'ABI della libreria viene confrontata con il riferimento ABI corrispondente per la versione del VNDK in fase di creazione. I dump ABI di riferimento si trovano in:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based

Ad esempio, durante la creazione di libfoo per il livello API 27 di VNDK, l'ABI dedotto di libfoo viene confrontato con il suo riferimento in:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump

Errore di rottura dell'ABI

In caso di interruzioni dell'ABI, il registro di compilazione visualizza avvisi con il tipo di avviso e un percorso per il report abi-diff. Ad esempio, se l'ABI di libbinder ha una modifica incompatibile, il sistema di compilazione genera un errore con un messaggio simile al seguente:

*****************************************************
error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES
Please check compatibility report at:
out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff
******************************************************
---- Please update abi references by running
platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ----

Costruire i controlli ABI della libreria VNDK

Quando viene creata una libreria VNDK:

  1. header-abi-dumper elabora i file di origine compilati per creare la libreria VNDK (i file di origine della libreria e i file di origine ereditati tramite dipendenze transitive statiche), per produrre file .sdump che corrispondono a ciascuna origine.
    sdump creation
    Figura 1. Creazione dei file .sdump
  2. header-abi-linker elabora quindi i file .sdump (utilizzando uno script di versione fornito o il file .so corrispondente alla libreria condivisa) per produrre un file .lsdump che registra tutte le informazioni ABI corrispondenti alla libreria condivisa.
    lsdump creation
    Figura 2. Creazione del file .lsdump
  3. header-abi-diff confronta il file .lsdump con un file .lsdump di riferimento per produrre un rapporto diff che delinea le differenze negli ABI delle due librerie.
    abi diff creation
    Figura 3. Creazione del rapporto diff

header-abi-dumper

Lo strumento header-abi-dumper analizza un file di origine C/C++ e scarica l'ABI dedotto da tale file di origine in un file intermedio. Il sistema di compilazione esegue header-abi-dumper su tutti i file di origine compilati mentre crea anche una libreria che include i file di origine dalle dipendenze transitive.

Attualmente i file .sdump sono formattati come Protobuf TextFormatted , che non è garantito essere stabile nelle versioni future. In quanto tale, la formattazione del file .sdump dovrebbe essere considerata un dettaglio di implementazione del sistema di compilazione.

Ad esempio, libfoo.so ha il seguente file sorgente foo.cpp :

#include <stdio.h>
#include <foo_exported.h>

bool Foo(int id, bar_t *bar_ptr) {
    if (id > 0 && bar_ptr->mfoo.m1 > 0) {
        return true;
    }
    return false;
}

È possibile utilizzare header-abi-dumper per generare un file .sdump intermedio che rappresenta l'ABI presentato dal file di origine utilizzando:

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++

Questo comando dice header-abi-dumper di analizzare foo.cpp ed emettere le informazioni ABI che sono esposte nelle intestazioni pubbliche nella directory exported . Questo è un estratto (non una rappresentazione completa) da foo.sdump generato da header-abi-dumper :

record_types {
  type_info {
    name: "foo"
    size: 12
    alignment: 4
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 32
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-5"
    field_offset: 64
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 12
    alignment: 4
    referenced_type: "type-6"
…
pointer_types {
  type_info {
    name: "bar *"
    size: 4
    alignment: 4
    referenced_type: "type-6"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "bar *"
    self_type: "type-8"
  }
}
builtin_types {
  type_info {
    name: "int"
    size: 4
    alignment: 4
    referenced_type: "type-2"
    source_file: ""
    linker_set_key: "int"
    self_type: "type-2"
  }
  is_unsigned: false
  is_integral: true
}
functions {
  return_type: "type-7"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-8"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}

foo.sdump contiene informazioni ABI esposte dal file sorgente foo.cpp , ad esempio:

  • record_types . Fare riferimento a struct, unioni o classi esposte dalle intestazioni pubbliche. Ogni tipo di record ha informazioni sui suoi campi, le sue dimensioni, l'identificatore di accesso, il file di intestazione in cui è stato esposto, ecc.
  • pointer_types . Fare riferimento ai tipi di puntatore direttamente/indirettamente referenziati da record/funzioni esposti da intestazioni pubbliche, insieme al tipo a cui punta il puntatore (tramite il campo referenced_type in type_info ). Informazioni simili vengono registrate nel file .sdump per i tipi qualificati, i tipi C/C++ incorporati, i tipi di matrice e i tipi di riferimento lvalue e rvalue (tale informazioni di registrazione sui tipi consentono differenze ricorsive).
  • functions . Rappresenta le funzioni esposte dalle intestazioni pubbliche. Hanno anche informazioni sul nome alterato della funzione, sul tipo restituito, sui tipi dei parametri, sull'identificatore di accesso, ecc.

header-abi-linker

Lo strumento header-abi-linker prende come input i file intermedi prodotti da header-abi-dumper , quindi collega quei file:

Ingressi
  • File intermedi prodotti da header-abi-dumper
  • Script versione/file mappa (opzionale)
  • File .so della libreria condivisa
Produzione Un file che registra l'ABI di una libreria condivisa (ad es. libfoo.so.lsdump rappresenta l'ABI di libfoo ).

Lo strumento unisce i grafici dei tipi in tutti i file intermedi ad esso assegnati, tenendo conto delle differenze di una definizione (tipi definiti dall'utente in diverse unità di traduzione con lo stesso nome completo, potrebbero essere semanticamente differenti) tra le unità di traduzione. Lo strumento analizza quindi uno script di versione o la tabella .dynsym della libreria condivisa (file .so ) per creare un elenco dei simboli esportati.

Ad esempio, quando libfoo aggiunge il file bar.cpp (che espone una bar delle funzioni C ) alla sua compilazione, è possibile invocare header-abi-linker per creare il dump ABI completo collegato di libfoo come segue:

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current

Esempio di output del comando in libfoo.so.lsdump :

record_types {
  type_info {
    name: "foo"
    size: 24
    alignment: 8
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 64
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-4"
    field_offset: 128
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 24
    alignment: 8
...
builtin_types {
  type_info {
    name: "void"
    size: 0
    alignment: 0
    referenced_type: "type-6"
    source_file: ""
    linker_set_key: "void"
    self_type: "type-6"
  }
  is_unsigned: false
  is_integral: false
}
functions {
  return_type: "type-19"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-20"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}
functions {
  return_type: "type-6"
  function_name: "FooBad"
  source_file: "foo/include/foo_exported_bad.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
parameters {
    referenced_type: "type-7"
    default_arg: false
  }
  linker_set_key: "_Z6FooBadiP3foo"
  access: public_access
}
elf_functions {
  name: "_Z3FooiP3bar"
}
elf_functions {
  name: "_Z6FooBadiP3foo"
}

Lo strumento header-abi-linker :

  • Collega i file .sdump ad esso forniti ( foo.sdump e bar.sdump ), filtrando le informazioni ABI non presenti nelle intestazioni che risiedono nella directory: exported .
  • Analizza libfoo.so e raccoglie informazioni sui simboli esportati dalla libreria tramite la sua tabella .dynsym .
  • Aggiunge _Z3FooiP3bar e Bar .

libfoo.so.lsdump è il dump ABI finale generato di libfoo.so .

header-abi-diff

Lo strumento header-abi-diff confronta due file .lsdump che rappresentano l'ABI di due librerie e produce un rapporto diff che indica le differenze tra i due ABI.

Ingressi
  • File .lsdump che rappresenta l'ABI di una vecchia libreria condivisa.
  • File .lsdump che rappresenta l'ABI di una nuova libreria condivisa.
Produzione Un rapporto diff che afferma le differenze negli ABI offerti dalle due biblioteche condivise confrontate.

Il file ABI diff è progettato per essere il più dettagliato e leggibile possibile. Il formato è soggetto a modifiche nelle versioni future. Ad esempio, hai due versioni di libfoo : libfoo_old.so e libfoo_new.so . In libfoo_new.so , in bar_t , si cambia il tipo di mfoo da foo_t a foo_t * . Poiché bar_t è un tipo direttamente raggiungibile, questo dovrebbe essere contrassegnato come una modifica di rilievo ABI da header-abi-diff .

Per eseguire header-abi-diff :

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo

Esempio di output del comando in libfoo.so.abidiff :

lib_name: "libfoo"
arch: "arm64"
record_type_diffs {
  name: "bar"
  type_stack: "Foo-> bar *->bar "
  type_info_diff {
    old_type_info {
      size: 24
      alignment: 8
    }
    new_type_info {
      size: 8
      alignment: 8
    }
  }
  fields_diff {
    old_field {
      referenced_type: "foo"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
    new_field {
      referenced_type: "foo *"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
  }
}

libfoo.so.abidiff contiene un report di tutte le modifiche sostanziali dell'ABI in libfoo . Il messaggio record_type_diffs indica che un record è stato modificato ed elenca le modifiche incompatibili, che includono:

  • La dimensione del record cambia da 24 byte a 8 byte.
  • Il tipo di campo di mfoo che cambia da foo a foo * (tutti i typedef vengono rimossi).

Il campo type_stack indica come header-abi-diff ha raggiunto il tipo che è cambiato ( bar ). Questo campo può essere interpretato come Foo è una funzione esportata che accetta bar * come parametro, che punta a bar , che è stato esportato e modificato.

Applicazione di ABI/API

Per applicare l'ABI/API delle librerie condivise VNDK e LLNDK, i riferimenti ABI devono essere archiviati in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ . Per creare questi riferimenti, eseguire il comando seguente:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py

Dopo aver creato i riferimenti, qualsiasi modifica apportata al codice sorgente che risulta in una modifica ABI/API incompatibile in una libreria VNDK o LLNDK genera ora un errore di compilazione.

Per aggiornare i riferimenti ABI per librerie di base VNDK specifiche, eseguire il comando seguente:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>

Ad esempio, per aggiornare i riferimenti ABI di libbinder , eseguire:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder

Per aggiornare i riferimenti ABI per librerie LLNDK specifiche, eseguire il comando seguente:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk

Ad esempio, per aggiornare i riferimenti ABI di libm , eseguire:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk