Stabilność ABI

Stabilność interfejsu binarnego aplikacji (ABI) jest warunkiem wstępnym aktualizacji tylko dla platformy, ponieważ moduły dostawców mogą zależeć od bibliotek współdzielonych Vendor Native Development Kit (VNDK), które znajdują się na partycji systemowej. W wydaniu systemu Android nowo utworzone biblioteki współdzielone VNDK muszą być zgodne z ABI z wcześniej wydanymi bibliotekami współdzielonymi VNDK, aby moduły dostawców mogły współpracować z tymi bibliotekami bez ponownej kompilacji i bez błędów w czasie wykonywania. Pomiędzy wydaniami Androida biblioteki VNDK można zmieniać i nie ma gwarancji ABI.

Aby zapewnić zgodność z ABI, system Android 9 zawiera nagłówek sprawdzania ABI, zgodnie z opisem w poniższych sekcjach.

O zgodności z VNDK i ABI

VNDK to restrykcyjny zestaw bibliotek, z którymi mogą się łączyć moduły dostawcy i które umożliwiają aktualizacje tylko w ramach struktury. Zgodność z ABI odnosi się do zdolności nowszej wersji biblioteki współdzielonej do pracy zgodnie z oczekiwaniami z modułem, który jest z nią dynamicznie połączony (tj. działa tak, jak starsza wersja biblioteki).

O eksportowanych symbolach

Wyeksportowany symbol (znany również jako symbol globalny ) odnosi się do symbolu, który spełnia wszystkie poniższe kryteria:

  • Wyeksportowane przez publiczne nagłówki biblioteki współdzielonej.
  • Pojawia się w tabeli .dynsym pliku .so odpowiadającego bibliotece współdzielonej.
  • Ma wiązanie SŁABE lub GLOBALNE.
  • Widoczność jest DOMYŚLNA lub ZABEZPIECZONA.
  • Indeks sekcji nie jest NIEZDEFINIOWANY.
  • Typ to FUNC lub OBJECT.

Publiczne nagłówki biblioteki współdzielonej są zdefiniowane jako nagłówki dostępne dla innych bibliotek/plików binarnych za pośrednictwem export_include_dirs , export_header_lib_headers , export_static_lib_headers , export_shared_lib_headers i export_generated_headers w definicjach modułu Android.bp odpowiadającego bibliotece współdzielonej.

O osiągalnych typach

Typ osiągalny to dowolny wbudowany lub zdefiniowany przez użytkownika typ C/C++, który jest osiągalny bezpośrednio lub pośrednio przez eksportowany symbol ORAZ eksportowany przez publiczne nagłówki. Na przykład libfoo.so ma funkcję Foo , która jest wyeksportowanym symbolem znajdującym się w tabeli .dynsym . Biblioteka libfoo.so zawiera:

foo_exported.h foo.prywatny.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 tabela
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

W przypadku Foo typy osiągalne bezpośrednio/pośrednio obejmują:

Rodzaj Opis
bool Typ zwrotu Foo .
int Typ pierwszego parametru Foo .
bar_t * Typ drugiego parametru Foo. Za pomocą bar_t * , bar_t jest eksportowany przez foo_exported.h .

bar_t zawiera element mfoo , typu foo_t , który jest eksportowany przez foo_exported.h , co skutkuje eksportowaniem większej liczby typów:
  • int : jest typem m1 .
  • int * : to typ m2 .
  • foo_private_t * : to typ mPfoo .

Jednak foo_private_t NIE jest osiągalny, ponieważ nie jest eksportowany przez foo_exported.h . ( foot_private_t * jest nieprzezroczysty, dlatego zmiany wprowadzone w foo_private_t są dozwolone.)

Podobne wyjaśnienie można podać również dla typów osiągalnych przez specyfikatory klasy bazowej i parametry szablonu.

Zapewnienie zgodności z ABI

Zgodność z ABI musi być zapewniona dla bibliotek oznaczonych vendor_available: true i vndk.enabled: true w odpowiednich plikach Android.bp . Na przykład:

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

W przypadku typów danych osiągalnych bezpośrednio lub pośrednio przez wyeksportowaną funkcję następujące zmiany w bibliotece są klasyfikowane jako łamiące ABI:

Typ danych Opis
Struktury i klasy
  • Zmień rozmiar typu klasy lub typu struktury.
  • Klasy bazowe
    • Dodaj lub usuń klasy bazowe.
    • Dodaj lub usuń wirtualnie dziedziczone klasy podstawowe.
    • Zmień kolejność klas bazowych.
  • Funkcje członkowskie
    • Usuń funkcje członkowskie*.
    • Dodaj lub usuń argumenty z funkcji członkowskich.
    • Zmień typy argumentów lub typy zwracane funkcji członkowskich*.
    • Zmień układ tabeli wirtualnej.
  • Członkowie danych
    • Usuń statyczne składowe danych.
    • Dodaj lub usuń niestatyczne elementy danych.
    • Zmień typy członków danych.
    • Zmień przesunięcia na niestatyczne składowe danych**.
    • Zmień kwalifikatory const , volatile i/lub restricted elementy członkowskie danych***.
    • Zdegraduj specyfikatory dostępu członków danych***.
  • Zmień argumenty szablonu.
Związki
  • Dodaj lub usuń członków danych.
  • Zmień rozmiar typu unii.
  • Zmień typy członków danych.
  • Zmień kolejność członków danych.
Wyliczenia
  • Zmień typ bazowy.
  • Zmień nazwy enumeratorów.
  • Zmień wartości modułów wyliczających.
Symbole globalne
  • Usuń symbole wyeksportowane przez publiczne nagłówki.
  • Dla symboli globalnych typu FUNC
    • Dodaj lub usuń argumenty.
    • Zmień typy argumentów.
    • Zmień typ zwrotu.
    • Zmniejsz specyfikację dostępu***.
  • Dla symboli globalnych typu OBJECT
    • Zmień odpowiedni typ C/C++.
    • Zmniejsz specyfikację dostępu***.

* Zarówno publiczne, jak i prywatne funkcje członkowskie nie mogą być zmieniane ani usuwane, ponieważ publiczne funkcje wbudowane mogą odwoływać się do prywatnych funkcji członkowskich. Odwołania symboli do prywatnych funkcji składowych mogą być przechowywane w plikach binarnych wywołujących. Zmiana lub usunięcie prywatnych funkcji składowych z bibliotek współdzielonych może spowodować powstanie plików binarnych niezgodnych z poprzednimi wersjami.

** Przesunięcia do publicznych lub prywatnych członków danych nie mogą być zmieniane, ponieważ funkcje wbudowane mogą odwoływać się do tych członków danych w ich treści funkcji. Zmiana przesunięć elementów danych może spowodować, że pliki binarne będą niezgodne z poprzednimi wersjami.

*** Chociaż nie zmieniają one układu pamięci typu, istnieją różnice semantyczne, które mogą prowadzić do tego, że biblioteki nie będą działać zgodnie z oczekiwaniami.

Korzystanie z narzędzi zgodności ABI

Po skompilowaniu biblioteki VNDK, ABI biblioteki jest porównywane z odpowiednim odniesieniem ABI dla kompilowanej wersji VNDK. Referencyjne zrzuty ABI znajdują się w:

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

Na przykład podczas budowania libfoo dla poziomu API 27 VNDK, wywnioskowany ABI libfoo jest porównywany z jego referencją pod adresem:

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

Błąd zerwania ABI

W przypadku awarii ABI dziennik kompilacji wyświetla ostrzeżenia z typem ostrzeżenia i ścieżką do raportu abi-diff. Na przykład, jeśli ABI libbinder zawiera niezgodną zmianę, system kompilacji zgłasza błąd z komunikatem podobnym do następującego:

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

Budowanie kontroli ABI biblioteki VNDK

Po zbudowaniu biblioteki VNDK:

  1. header-abi-dumper przetwarza pliki źródłowe skompilowane w celu zbudowania biblioteki VNDK (własne pliki źródłowe biblioteki, a także pliki źródłowe dziedziczone przez statyczne zależności przechodnie), w celu utworzenia plików .sdump , które odpowiadają każdemu źródłu.
    sdump creation
    Rysunek 1. Tworzenie plików .sdump
  2. header-abi-linker następnie przetwarza pliki .sdump (przy użyciu dostarczonego skryptu wersji lub pliku .so odpowiadającego bibliotece współdzielonej) w celu utworzenia pliku .lsdump , który rejestruje wszystkie informacje ABI odpowiadające bibliotece współdzielonej.
    lsdump creation
    Rysunek 2. Tworzenie pliku .lsdump
  3. header-abi-diff porównuje plik .lsdump z plikiem referencyjnym .lsdump w celu wygenerowania raportu różnicy, który przedstawia różnice w ABI dwóch bibliotek.
    abi diff creation
    Rysunek 3. Tworzenie raportu różnicowego

nagłówek-abi-dumper

Narzędzie header-abi-dumper analizuje plik źródłowy C/C++ i zrzuca ABI wywnioskowane z tego pliku źródłowego do pliku pośredniego. System kompilacji uruchamia header-abi-dumper na wszystkich skompilowanych plikach źródłowych, jednocześnie budując bibliotekę zawierającą pliki źródłowe z zależności przechodnich.

Obecnie pliki .sdump są sformatowane jako Protobuf TextFormatted , co nie gwarantuje stabilności w przyszłych wydaniach. W związku z tym formatowanie pliku .sdump należy traktować jako szczegół implementacji systemu kompilacji.

Na przykład libfoo.so zawiera następujący plik źródłowy 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;
}

Możesz użyć header-abi-dumper do wygenerowania pośredniego pliku .sdump , który reprezentuje ABI prezentowany przez plik źródłowy, używając:

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

To polecenie mówi header-abi-dumper , aby przeanalizował foo.cpp i wyemitował informacje ABI, które są ujawnione w publicznych nagłówkach w exported katalogu. To jest fragment (nie pełna reprezentacja) z foo.sdump wygenerowanego przez 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 zawiera informacje ABI ujawnione przez plik źródłowy foo.cpp , np.:

  • record_types . Odwołaj się do struktur, związków lub klas uwidocznionych przez publiczne nagłówki. Każdy typ rekordu zawiera informacje o swoich polach, jego rozmiarze, specyfikatorze dostępu, pliku nagłówkowym, w którym został ujawniony itp.
  • pointer_types . Odwołaj się do typów wskaźników bezpośrednio/pośrednio, do których odwołują się rekordy/funkcje uwidocznione przez publiczne nagłówki, wraz z typem, na który wskazuje wskaźnik (za pośrednictwem pola referenced_type w type_info ). Podobne informacje są rejestrowane w pliku .sdump dla kwalifikowanych typów, wbudowanych typów C/C++, typów tablic oraz typów odwołań lvalue i rvalue (takie rejestrowanie informacji o typach umożliwia porównywanie rekurencyjne).
  • functions . Reprezentuj funkcje udostępniane przez publiczne nagłówki. Zawierają również informacje o zniekształconej nazwie funkcji, typie zwracanym, typach parametrów, specyfikatorze dostępu itp.

header-abi-linker

Narzędzie header-abi-linker pobiera pliki pośrednie utworzone przez header-abi-dumper jako dane wejściowe, a następnie łączy te pliki:

Wejścia
  • Pliki pośrednie tworzone przez header-abi-dumper
  • Skrypt wersji/plik mapy (opcjonalnie)
  • .so plik biblioteki współdzielonej
Wyjście Plik, który rejestruje ABI biblioteki współdzielonej (np libfoo.so.lsdump reprezentuje ABI libfoo ).

Narzędzie łączy grafy typów we wszystkich przekazanych mu plikach pośrednich, biorąc pod uwagę jedną definicję (typy zdefiniowane przez użytkownika w różnych jednostkach tłumaczeniowych o tej samej w pełni kwalifikowanej nazwie mogą być semantycznie różne) różnice między jednostkami tłumaczeniowymi. Następnie narzędzie analizuje skrypt wersji lub tabelę .dynsym biblioteki współdzielonej (plik .so ), aby utworzyć listę eksportowanych symboli.

Na przykład, gdy libfoo doda plik bar.cpp (który uwidacznia bar funkcji C) do swojej kompilacji, można wywołać header-abi-linker w celu utworzenia pełnego połączonego zrzutu ABI libfoo w następujący sposób:

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

Przykładowe dane wyjściowe polecenia w 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"
}

Narzędzie header-abi-linker :

  • Łączy dostarczone do niego pliki .sdump ( foo.sdump i bar.sdump ), odfiltrowując informacje ABI nieobecne w nagłówkach znajdujących się w katalogu: exported .
  • Analizuje libfoo.so i zbiera informacje o symbolach wyeksportowanych przez bibliotekę poprzez jej tabelę .dynsym .
  • Dodaje _Z3FooiP3bar i Bar .

libfoo.so.lsdump to ostateczny wygenerowany zrzut ABI libfoo.so .

nagłówek-abi-diff

Narzędzie header-abi-diff porównuje dwa pliki .lsdump reprezentujące ABI dwóch bibliotek i tworzy raport różnicy określający różnice między dwoma ABI.

Wejścia
  • Plik .lsdump reprezentujący ABI starej biblioteki współdzielonej.
  • Plik .lsdump reprezentujący ABI nowej biblioteki współdzielonej.
Wyjście Raport diff określający różnice w ABI oferowanych przez dwie porównywane biblioteki współdzielone.

Plik diff ABI został zaprojektowany tak, aby był jak najbardziej szczegółowy i czytelny. Format może ulec zmianie w przyszłych wydaniach. Na przykład masz dwie wersje libfoo : libfoo_old.so i libfoo_new.so . W libfoo_new.so , w bar_t , zmieniasz typ mfoo z foo_t na foo_t * . Ponieważ bar_t jest typem bezpośrednio osiągalnym, powinno być oznaczone jako zmiana przerywająca ABI przez header-abi-diff .

Aby uruchomić header-abi-diff :

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

Przykładowe wyjście polecenia w 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 zawiera raport o wszystkich zmianach ABI w libfoo . Komunikat record_type_diffs wskazuje, że rekord uległ zmianie i zawiera listę niezgodnych zmian, które obejmują:

  • Rozmiar rekordu zmieniający się z 24 bajtów na 8 bajtów.
  • Typ pola mfoo zmieniający się z foo na foo * (wszystkie typy typedef są usuwane).

Pole type_stack wskazuje, w jaki sposób header-abi-diff osiągnął zmieniony typ ( bar ). To pole może być interpretowane jako Foo jest funkcją eksportowaną, która przyjmuje bar * jako parametr wskazujący na bar , który został wyeksportowany i zmieniony.

Wymuszanie ABI/API

Aby wymusić stosowanie ABI/API bibliotek współdzielonych VNDK i LLNDK, odwołania ABI muszą zostać zaewidencjonowane w ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ . Aby utworzyć te odniesienia, uruchom następujące polecenie:

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

Po utworzeniu odwołań każda zmiana dokonana w kodzie źródłowym, która powoduje niezgodną zmianę ABI/API w bibliotece VNDK lub LLNDK, powoduje teraz błąd kompilacji.

Aby zaktualizować odwołania ABI dla określonych podstawowych bibliotek VNDK, uruchom następujące polecenie:

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

Na przykład, aby zaktualizować libbinder ABI, uruchom:

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

Aby zaktualizować odwołania ABI dla określonych bibliotek LLNDK, uruchom następujące polecenie:

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

Na przykład, aby zaktualizować odniesienia libm ABI, uruchom:

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