ABI-Stabilität

Die Stabilität der Application Binary Interface (ABI) ist eine Voraussetzung für reine Framework-Updates, da Anbietermodule möglicherweise von den gemeinsam genutzten Bibliotheken des Vendor Native Development Kit (VNDK) abhängen, die sich in der Systempartition befinden. Innerhalb einer Android-Version müssen neu erstellte gemeinsam genutzte VNDK-Bibliotheken ABI-kompatibel mit zuvor veröffentlichten gemeinsam genutzten VNDK-Bibliotheken sein, damit Herstellermodule mit diesen Bibliotheken ohne Neukompilierung und ohne Laufzeitfehler arbeiten können. Zwischen Android-Releases können VNDK-Bibliotheken geändert werden und es gibt keine ABI-Garantien.

Um die ABI-Kompatibilität sicherzustellen, enthält Android 9 einen Header-ABI-Checker, wie in den folgenden Abschnitten beschrieben.

Informationen zur VNDK- und ABI-Konformität

Das VNDK ist ein restriktiver Satz von Bibliotheken, mit denen Anbietermodule verknüpft werden können und die reine Framework-Updates ermöglichen. ABI-Konformität bezieht sich auf die Fähigkeit einer neueren Version einer gemeinsam genutzten Bibliothek, wie erwartet mit einem Modul zu arbeiten, das dynamisch damit verknüpft ist (dh so funktioniert, wie es eine ältere Version der Bibliothek tun würde).

Informationen zu exportierten Symbolen

Ein exportiertes Symbol (auch bekannt als globales Symbol ) bezieht sich auf ein Symbol, das alle folgenden Bedingungen erfüllt:

  • Exportiert von den öffentlichen Headern einer gemeinsam genutzten Bibliothek.
  • Erscheint in der .dynsym Tabelle der .so -Datei, die der gemeinsam genutzten Bibliothek entspricht.
  • Hat WEAK oder GLOBAL Bindung.
  • Die Sichtbarkeit ist STANDARD oder GESCHÜTZT.
  • Abschnittsindex ist nicht UNDEFINED.
  • Typ ist entweder FUNC oder OBJECT.

Die öffentlichen Header einer gemeinsam genutzten Bibliothek sind als die Header definiert, die anderen Bibliotheken/Binärdateien über die export_include_dirs , export_header_lib_headers , export_static_lib_headers , export_shared_lib_headers und export_generated_headers in den Android.bp Definitionen des Moduls zur Verfügung stehen, das der gemeinsam genutzten Bibliothek entspricht.

Über erreichbare Typen

Ein erreichbarer Typ ist ein beliebiger in C/C++ integrierter oder benutzerdefinierter Typ, der direkt oder indirekt über ein exportiertes Symbol UND über öffentliche Header erreichbar ist. Zum Beispiel hat libfoo.so die Funktion Foo , die ein exportiertes Symbol ist, das in der .dynsym -Tabelle zu finden ist. Die Bibliothek libfoo.so enthält Folgendes:

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 : [
    "include"
  ],
}
.dynsym-Tabelle
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

Betrachtet man Foo , gehören zu den direkt/indirekt erreichbaren Typen:

Art Beschreibung
bool Rückgabetyp von Foo .
int Typ des ersten Foo -Parameters.
bar_t * Typ des zweiten Foo-Parameters. Mittels bar_t * wird bar_t durch foo_exported.h exportiert.

bar_t enthält ein Mitglied mfoo vom Typ foo_t , das über foo_exported.h exportiert wird, was dazu führt, dass mehr Typen exportiert werden:
  • int : ist der Typ von m1 .
  • int * : ist der Typ von m2 .
  • foo_private_t * : ist der Typ von mPfoo .

foo_private_t ist jedoch NICHT erreichbar, da es nicht über foo_exported.h exportiert wird. ( foot_private_t * ist undurchsichtig, daher sind Änderungen an foo_private_t zulässig.)

Eine ähnliche Erklärung kann auch für Typen gegeben werden, die über Basisklassenbezeichner und Vorlagenparameter erreichbar sind.

Gewährleistung der ABI-Konformität

Die ABI-Konformität muss für die Bibliotheken mit der Kennzeichnung vendor_available: true “ und vndk.enabled: true in den entsprechenden Android.bp Dateien sichergestellt werden. Zum Beispiel:

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

Für Datentypen, die direkt oder indirekt durch eine exportierte Funktion erreichbar sind, werden die folgenden Änderungen an einer Bibliothek als ABI-brechend eingestuft:

Datentyp Beschreibung
Strukturen und Klassen
  • Ändern Sie die Größe des Klassentyps oder des Strukturtyps.
  • Basisklassen
    • Basisklassen hinzufügen oder entfernen.
    • Hinzufügen oder Entfernen von virtuell geerbten Basisklassen.
    • Ändern Sie die Reihenfolge der Basisklassen.
  • Mitgliedsfunktionen
    • Elementfunktionen entfernen*.
    • Hinzufügen oder Entfernen von Argumenten aus Elementfunktionen.
    • Ändern Sie die Argumenttypen oder die Rückgabetypen von Elementfunktionen*.
    • Ändern Sie das Layout des virtuellen Tisches.
  • Datenmitglieder
    • Entfernen Sie statische Datenmember.
    • Fügen Sie nicht statische Datenelemente hinzu oder entfernen Sie sie.
    • Ändern Sie die Typen von Datenelementen.
    • Ändern Sie die Offsets in nicht statische Datenmember**.
    • Ändern Sie die Qualifizierer const , volatile und/oder limited von restricted ***.
    • Stufen Sie die Zugriffsspezifizierer von Datenelementen herab***.
  • Ändern Sie die Vorlagenargumente.
Gewerkschaften
  • Datenelemente hinzufügen oder entfernen.
  • Ändern Sie die Größe des Union-Typs.
  • Ändern Sie die Typen von Datenelementen.
  • Ändern Sie die Reihenfolge der Datenelemente.
Aufzählungen
  • Ändern Sie den zugrunde liegenden Typ.
  • Ändern Sie die Namen der Enumeratoren.
  • Ändern Sie die Werte von Enumeratoren.
Globale Symbole
  • Entfernen Sie die von öffentlichen Headern exportierten Symbole.
  • Für globale Symbole vom Typ FUNC
    • Argumente hinzufügen oder entfernen.
    • Ändern Sie die Argumenttypen.
    • Ändern Sie den Rückgabetyp.
    • Herunterstufen des Zugriffsbezeichners***.
  • Für globale Symbole vom Typ OBJECT
    • Ändern Sie den entsprechenden C/C++-Typ.
    • Herunterstufen des Zugriffsbezeichners***.

* Sowohl öffentliche als auch private Elementfunktionen dürfen nicht geändert oder entfernt werden, da öffentliche Inline-Funktionen auf private Elementfunktionen verweisen können. Symbolverweise auf private Elementfunktionen können in Caller-Binärdateien gespeichert werden. Das Ändern oder Entfernen privater Elementfunktionen aus gemeinsam genutzten Bibliotheken kann zu abwärtsinkompatiblen Binärdateien führen.

** Die Offsets zu öffentlichen oder privaten Datenmembern dürfen nicht geändert werden, da Inline-Funktionen in ihrem Funktionsrumpf auf diese Datenmember verweisen können. Das Ändern von Datenelement-Offsets kann zu abwärtsinkompatiblen Binärdateien führen.

*** Während diese das Speicherlayout des Typs nicht ändern, gibt es semantische Unterschiede, die dazu führen können, dass Bibliotheken nicht wie erwartet funktionieren.

Verwenden von ABI-Compliance-Tools

Wenn eine VNDK-Bibliothek erstellt wird, wird die ABI der Bibliothek mit der entsprechenden ABI-Referenz für die Version des erstellten VNDK verglichen. Referenz-ABI-Dumps befinden sich in:

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

Beim Erstellen von libfoo für API-Ebene 27 des VNDK wird libfoo die abgeleitete ABI von libfoo mit ihrer Referenz verglichen unter:

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

ABI-Bruchfehler

Bei ABI-Fehlern zeigt das Build-Protokoll Warnungen mit dem Warnungstyp und einem Pfad zum abi-diff-Bericht an. Wenn beispielsweise die ABI von libbinder eine inkompatible Änderung aufweist, gibt das Build-System einen Fehler mit einer Meldung ähnlich der folgenden aus:

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

Erstellen von ABI-Prüfungen der VNDK-Bibliothek

Wenn eine VNDK-Bibliothek erstellt wird:

  1. header-abi-dumper verarbeitet die Quelldateien, die kompiliert wurden, um die VNDK-Bibliothek zu erstellen (die eigenen Quelldateien der Bibliothek sowie Quelldateien, die durch statische transitive Abhängigkeiten geerbt wurden), um .sdump Dateien zu erzeugen, die jeder Quelle entsprechen.
    sdump creation
    Abbildung 1. Erstellen der .sdump Dateien
  2. header-abi-linker verarbeitet dann die .sdump -Dateien (unter Verwendung entweder eines bereitgestellten Versionsskripts oder der .so -Datei, die der gemeinsam genutzten Bibliothek entspricht), um eine .lsdump -Datei zu erzeugen, die alle ABI-Informationen protokolliert, die der gemeinsam genutzten Bibliothek entsprechen.
    lsdump creation
    Abbildung 2. Erstellen der .lsdump -Datei
  3. header-abi-diff vergleicht die .lsdump -Datei mit einer .lsdump -Referenzdatei, um einen Vergleichsbericht zu erstellen, der die Unterschiede in den ABIs der beiden Bibliotheken umreißt.
    abi diff creation
    Abbildung 3. Erstellen des Diff-Berichts

header-abi-dumper

Das Tool header-abi-dumper analysiert eine C/C++-Quelldatei und speichert die von dieser Quelldatei abgeleitete ABI in einer Zwischendatei. Das Build-System führt header-abi-dumper für alle kompilierten Quelldateien aus und erstellt gleichzeitig eine Bibliothek, die die Quelldateien aus transitiven Abhängigkeiten enthält.

Derzeit sind .sdump Dateien als Protobuf TextFormatted formatiert, was für zukünftige Versionen nicht garantiert stabil ist. Daher sollte die Formatierung der .sdump -Datei als Detail der Implementierung des Build-Systems betrachtet werden.

Beispielsweise hat libfoo.so die folgende Quelldatei 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;
}

Sie können header-abi-dumper dumper verwenden, um eine .sdump -Zwischendatei zu generieren, die die von der Quelldatei präsentierte ABI darstellt, indem Sie Folgendes verwenden:

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

Dieser Befehl weist header-abi-dumper dumper an, foo.cpp zu parsen und die ABI-Informationen auszugeben, die in den öffentlichen Headern im exported Verzeichnis verfügbar gemacht werden. Dies ist ein Auszug (keine vollständige Darstellung) aus foo.sdump , der von header-abi-dumper dumper generiert wurde:

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 enthält ABI-Informationen, die von der Quelldatei foo.cpp werden, z.

  • record_types . Verweisen Sie auf Strukturen, Vereinigungen oder Klassen, die von den öffentlichen Headern verfügbar gemacht werden. Jeder Datensatztyp enthält Informationen zu seinen Feldern, seiner Größe, seiner Zugriffskennung, der Header-Datei, in der er angezeigt wurde, usw.
  • pointer_types . Verweisen Sie auf Zeigertypen, auf die direkt/indirekt von Datensätzen/Funktionen verwiesen wird, die von öffentlichen Headern verfügbar gemacht werden, zusammen mit dem Typ, auf den der Zeiger zeigt (über das Feld referenced_type in type_info ). Ähnliche Informationen werden in der .sdump -Datei für qualifizierte Typen, integrierte C/C++-Typen, Array-Typen und lvalue- und rvalue-Referenztypen protokolliert (solche Protokollierungsinformationen zu Typen ermöglichen rekursiven Vergleich).
  • functions . Stellen Sie Funktionen dar, die durch öffentliche Header verfügbar gemacht werden. Sie haben auch Informationen über den beschädigten Namen der Funktion, den Rückgabetyp, die Typen der Parameter, den Zugriffsbezeichner usw.

header-abi-linker

Das Tool header-abi-linker nimmt die von header-abi-dumper erzeugten Zwischendateien als Eingabe und verknüpft dann diese Dateien:

Eingänge
  • Von header-abi-dumper erzeugte Zwischendateien
  • Versionsskript/Zuordnungsdatei (optional)
  • .so-Datei der gemeinsam genutzten Bibliothek
Ausgabe Eine Datei, die die ABI einer gemeinsam genutzten Bibliothek protokolliert (z. B. repräsentiert libfoo.so.lsdump die ABI von libfoo ).

Das Tool führt die Typendiagramme in allen ihm übergebenen Zwischendateien zusammen, wobei es Unterschiede zwischen Übersetzungseinheiten berücksichtigt (benutzerdefinierte Typen in verschiedenen Übersetzungseinheiten mit demselben vollständig qualifizierten Namen können semantisch unterschiedlich sein). Das Tool analysiert dann entweder ein Versionsskript oder die .dynsym Tabelle der gemeinsam genutzten Bibliothek ( .so -Datei), um eine Liste der exportierten Symbole zu erstellen.

Wenn beispielsweise libfoo die Datei bar.cpp (die eine C-Funktionsleiste bar macht) zu seiner Kompilierung hinzufügt, könnte header-abi-linker aufgerufen werden, um den vollständig verknüpften ABI-Dump von libfoo wie folgt zu erstellen:

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

Beispielbefehlsausgabe 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"
}

Das header-abi-linker Tool:

  • Verknüpft die bereitgestellten .sdump Dateien ( foo.sdump und bar.sdump ) und filtert die ABI-Informationen heraus, die nicht in den Headern vorhanden sind, die sich im Verzeichnis befinden: exported .
  • Analysiert libfoo.so und sammelt Informationen über die Symbole, die von der Bibliothek über ihre .dynsym -Tabelle exportiert werden.
  • Fügt _Z3FooiP3bar und Bar hinzu.

libfoo.so.lsdump ist der endgültig generierte ABI-Dump von libfoo.so .

header-abi-diff

Das Tool header-abi-diff vergleicht zwei .lsdump -Dateien, die die ABI zweier Bibliotheken darstellen, und erstellt einen Diff-Bericht, der die Unterschiede zwischen den beiden ABIs angibt.

Eingänge
  • .lsdump -Datei, die die ABI einer alten gemeinsam genutzten Bibliothek darstellt.
  • .lsdump -Datei, die die ABI einer neuen gemeinsam genutzten Bibliothek darstellt.
Ausgabe Ein Diff-Bericht, der die Unterschiede in den ABIs angibt, die von den beiden verglichenen gemeinsam genutzten Bibliotheken angeboten werden.

Die ABI-Diff-Datei soll so ausführlich und lesbar wie möglich sein. Das Format kann sich in zukünftigen Versionen ändern. Beispielsweise haben Sie zwei Versionen von libfoo : libfoo_old.so und libfoo_new.so . In libfoo_new.so ändern Sie in bar_t den Typ von mfoo von foo_t in foo_t * . Da bar_t ein direkt erreichbarer Typ ist, sollte dies durch header-abi-diff als ABI Breaking Change gekennzeichnet werden.

So führen header-abi-diff aus:

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

Beispielbefehlsausgabe 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
    }
  }
}

Die libfoo.so.abidiff enthält einen Bericht aller ABI Breaking Changes in libfoo . Die Nachricht record_type_diffs zeigt an, dass sich ein Datensatz geändert hat, und listet die inkompatiblen Änderungen auf, darunter:

  • Die Größe des Datensatzes ändert sich von 24 Byte auf 8 Byte.
  • Der Feldtyp von mfoo ändert sich von foo zu foo * (alle typedefs werden entfernt).

Das Feld type_stack gibt an, wie header-abi-diff den Typ erreicht hat, der sich geändert hat ( bar ). Dieses Feld kann so interpretiert werden, dass Foo eine exportierte Funktion ist, die bar * als Parameter akzeptiert, die auf bar zeigt, die exportiert und geändert wurde.

Durchsetzung von ABI/API

Um die ABI/API von gemeinsam genutzten VNDK- und LLNDK-Bibliotheken zu erzwingen, müssen ABI-Referenzen in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ . Führen Sie zum Erstellen dieser Referenzen den folgenden Befehl aus:

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

Nach dem Erstellen der Referenzen führt jede Änderung am Quellcode, die zu einer inkompatiblen ABI/API-Änderung in einer VNDK- oder LLNDK-Bibliothek führt, jetzt zu einem Build-Fehler.

Führen Sie den folgenden Befehl aus, um ABI-Referenzen für bestimmte VNDK-Kernbibliotheken zu aktualisieren:

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

Um beispielsweise libbinder ABI-Referenzen zu aktualisieren, führen Sie Folgendes aus:

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

Führen Sie den folgenden Befehl aus, um ABI-Referenzen für bestimmte LLNDK-Bibliotheken zu aktualisieren:

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

Um beispielsweise libm ABI-Referenzen zu aktualisieren, führen Sie Folgendes aus:

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