Stabilità ABI

La stabilità dell'interfaccia a bit dell'applicazione (ABI) è un prerequisito per gli aggiornamenti solo del framework perché i moduli del fornitore potrebbero dipendere dalle librerie condivise del Vendor Native Development Kit (VNDK) che si trovano nella partizione di sistema. All'interno di una release di Android, le librerie condivise VNDK appena create devono essere compatibili con ABI con le librerie condivise VNDK rilasciate in precedenza in modo che i moduli del fornitore possano funzionare con queste librerie senza ricompilarli e senza errori di runtime. Tra le release di Android, le librerie VNDK possono essere modificate e non sono previste garanzie per le ABI.

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

Informazioni sulla conformità a VNDK e ABI

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 collegato dinamicamente (ovvero funziona come farebbe una versione precedente della libreria).

Informazioni sui simboli esportati

Un simbolo esportato (noto anche come simbolo globale) si riferisce a un simbolo che soddisfa tutte le seguenti condizioni:

  • Esportato dalle intestazioni pubbliche di una libreria condivisa.
  • Viene visualizzato nella tabella .dynsym del file .so corrispondente alla libreria condivisa.
  • Ha un'associazione WEAK o GLOBAL.
  • La visibilità è PREDEFINITA o PROTETTA.
  • L'indice della sezione non è UNDEFINED.
  • Il tipo può essere FUNC o OBJECT.

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

Informazioni sui tipi di utenti raggiungibili

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

foo_exported.h foo.private.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 : [
    "exported"
  ],
}
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

Se guardiamo Foo, i tipi raggiungibili direttamente/indiretti includono:

Digitazione Descrizione
bool Tipo restituito di Foo.
int Tipo del 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, il 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. (foo_private_t * è opaco, pertanto le modifiche apportate a foo_private_t sono consentite.)

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

Garantire la conformità all'ABI

La conformità ABI deve essere garantita per le librerie contrassegnate come vendor_available: true e vndk.enabled: true nei file Android.bp corrispondenti. Ad 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 non conformi all'ABI:

Tipo di dati Descrizione
Strutture e classi
  • Modifica le dimensioni del tipo di classe o del tipo di struct.
  • Classi di base
    • Aggiungi o rimuovi classi di base.
    • Aggiungi o rimuovi le classi di base ereditate virtualmente.
    • Modifica l'ordine delle classi di base.
  • Funzioni membro
    • Rimuovi le funzioni membro*.
    • Aggiungi o rimuovi argomenti dalle funzioni membro.
    • Modificare i tipi di argomenti o i tipi di ritorno delle funzioni membro*.
    • Modifica il layout della tabella virtuale.
  • Membri dei dati
    • Rimuovi gli elementi di dati statici.
    • Aggiungi o rimuovi i membri di dati non statici.
    • Modificare i tipi di membri di dati.
    • Modifica gli offset dei membri di dati non statici**.
    • Modifica i qualificatori const, volatile e/o restricted degli elementi dati***.
    • Esegui il downgrade degli specificatori di accesso degli elementi dati***.
  • Modifica gli argomenti del modello.
Sindacati
  • Aggiungere o rimuovere membri dei dati.
  • Modifica le dimensioni del tipo di unione.
  • Modificare i tipi di membri di dati.
Enumerazioni
  • Modifica il tipo sottostante.
  • Modifica i nomi degli enumeratori.
  • Modifica i valori degli enumeratori.
Simboli globali
  • Rimuovi i simboli esportati dagli intestazioni pubblici.
  • Per i simboli globali di tipo FUNC
    • Aggiungi o rimuovi gli argomenti.
    • Cambia i tipi di argomento.
    • Modifica il tipo di reso.
    • Esegui il downgrade dello specificatore di accesso***.
  • Per i simboli globali di tipo OBJECT
    • Modifica il tipo C/C++ corrispondente.
    • Esegui il downgrade dell'indicatore di accesso***.

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

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

*** Anche se queste non modificano il layout della memoria del tipo, esistono differenze semantiche che potrebbero portare al mancato funzionamento delle librerie.

Utilizzare gli strumenti di conformità ABI

Quando viene creata una libreria VNDK, l'ABI della libreria viene confrontata con il riferimento ABI corrispondente alla versione della VNDK in creazione. I dump ABI di riferimento si trovano in:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based

Ad esempio, in sede di creazione di libfoo per x86 al livello API 27, l'ABI dedotta di libfoo viene confrontata con il suo riferimento in:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

Errore di interruzione dell&#39;ABI

In caso di guasti ABI, il log di compilazione mostra avvisi con il tipo di avviso e un percorso al 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 ----

Esegui controlli ABI della libreria VNDK

Quando viene creata una libreria VNDK:

  1. header-abi-dumper elabora i file di origine compilati per compilare la libreria VNDK (i file di origine della libreria e i file di origine ereditati tramite dipendenze transitorie statiche) per produrre file .sdump corrispondenti a ogni origine.
    creazione di sdump
    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.
    Creazione di lsdump
    Figura 2. Creazione del file .lsdump
    in corso...
  3. header-abi-diff confronta il file .lsdump con un file .lsdump di riferimento per produrre un report sulle differenze che illustra le differenze nelle ABI delle due librerie.
    creazione di differenze abi
    Figura 3. Creazione del report sulle differenze

header-abi-dumper

Lo strumento header-abi-dumper analizza un file di origine C/C++ e esegue il dump dell'ABI dedotto da quel file di origine in un file intermedio. Il sistema di compilazione esegue header-abi-dumper su tutti i file di origine compilati e contemporaneamente genera una libreria che include i file di origine delle dipendenze trascendenti.

Input
  • Un file di codice sorgente C/C++
  • Directory include esportate
  • Flag del compilatore
Output Un file che descrive l'ABI del file di origine (ad es. foo.sdump rappresenta l'ABI di foo.cpp).

Attualmente i file .sdump sono in formato JSON, che non è garantito essere stabile nelle release future. Di conseguenza, la formattazione del file .sdump deve essere considerata un dettaglio dell'implementazione del sistema di compilazione.

Ad esempio, libfoo.so ha il seguente file di origine 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;
}

Puoi 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 -- -I exported -x c++

Questo comando indica a header-abi-dumper di analizzare foo.cpp con i flag del compilatore che seguono -- e di emettere le informazioni ABI esportate dalle intestazioni pubbliche nella directory exported. Di seguito è riportato foo.sdump generato da header-abi-dumper:

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" : [],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

foo.sdump contiene informazioni ABI esportate dal file di origine foo.cpp e dalle intestazioni pubbliche, ad esempio:

  • record_types. Fai riferimento a strutture, unioni o classi definite negli intestazioni pubbliche. Ogni tipo di record contiene informazioni sui relativi campi, sulle dimensioni, sul selettore di accesso, sul file di intestazione in cui è definito e su altri attributi.
  • pointer_types. Fai riferimento ai tipi di puntatori direttamente/indirettamente a cui fanno riferimento le funzioni/record esportati nelle 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++ integrati, i tipi di array e i tipi di riferimento lvalue e rvalue. Queste informazioni consentono di eseguire il confronto ricorsivo.
  • functions. Rappresenta le funzioni esportate da intestazioni pubbliche. Contengono anche informazioni sul nome manipolato della funzione, sul tipo di ritorno, sui tipi di parametri, sullo specificatore di accesso e su altri attributi.

header-abi-linker

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

Input
  • File intermedi prodotti da header-abi-dumper
  • Script della versione/file mappa (facoltativo)
  • .so file della libreria condivisa
  • Directory di inclusione esportate
Output Un file che descrive l'ABI di una libreria condivisa (ad esempio, libfoo.so.lsdump rappresenta l'ABI di libfoo).

Lo strumento unisce i grafici di tipo in tutti i file intermedi che gli vengono forniti, tenendo conto delle differenze tra le unità di traduzione con una definizione (tipi definiti dall'utente in unità di traduzione diverse con lo stesso nome completo, che potrebbero essere semanticamente diverse). 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, libfoo è composto da foo.cpp e bar.cpp. header-abi-linker può essere invocato per creare il dump ABI collegato completo 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:

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 1,
   "is_integral" : true,
   "is_unsigned" : true,
   "linker_set_key" : "_ZTIb",
   "name" : "bool",
   "referenced_type" : "_ZTIb",
   "self_type" : "_ZTIb",
   "size" : 1
  },
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" :
 [
  {
   "name" : "_Z3FooiP3bar"
  },
  {
   "name" : "_Z6FooBadiP3foo"
  }
 ],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "Foo",
   "linker_set_key" : "_Z3FooiP3bar",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3bar"
    }
   ],
   "return_type" : "_ZTIb",
   "source_file" : "exported/foo_exported.h"
  },
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3bar",
   "name" : "bar *",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTIP3bar",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

Lo strumento header-abi-linker:

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

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

header-abi-diff

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

Input
  • .lsdump file che rappresenta l'ABI di una vecchia libreria condivisa.
  • File .lsdump che rappresenta l'ABI di una nuova raccolta condivisa.
Output Un report delle differenze in cui vengono indicate le differenze nelle ABI offerte dalle due librerie condivise confrontate.

Il file diff ABI è in formato di testo protobuf. Il formato è soggetto a modifiche nelle release future.

Ad esempio, hai due versioni di libfoo: libfoo_old.so e libfoo_new.so. In libfoo_new.so, in bar_t, modifichi il tipo di mfoo da foo_t a foo_t *. Poiché bar_t è un tipo reachable, deve essere contrassegnato come una modifica che comporta la rottura dell'ABI da parte di 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

Output comando di esempio 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 di interruzione dell'ABI in libfoo. Il messaggio record_type_diffs indica che un record è stato modificato ed elenca le modifiche incompatibili, tra cui:

  • Le dimensioni del record passano da 24 byte a 8 byte.
  • Il tipo di campo di mfoo è passato da foo a foo * (tutte le typedef sono state rimosse).

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

Applicare ABI e API

Per applicare in modo forzato l'ABI e l'API delle librerie condivise VNDK, i riferimenti ABI devono essere archiviati in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/. Per creare questi riferimenti, esegui il seguente comando:

${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 comporti una modifica ABI/API incompatibile in una libreria VNDK ora genera un errore di compilazione.

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

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

Ad esempio, per aggiornare i riferimenti ABI libbinder, esegui:

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