Stabilitas ABI

Stabilitas Antarmuka Biner Aplikasi (ABI) adalah prasyarat dari update khusus framework karena modul vendor mungkin bergantung pada library bersama Vendor Native Development Kit (VNDK) yang berada di partisi sistem. Dalam rilis Android, library bersama VNDK yang baru dibuat harus kompatibel dengan ABI dengan library bersama VNDK yang dirilis sebelumnya, sehingga modul vendor dapat berfungsi dengan library tersebut tanpa rekompilasi dan tanpa error runtime. Di antara rilis Android, library VNDK dapat diubah dan tidak ada jaminan ABI.

Untuk membantu memastikan kompatibilitas ABI, Android 9 menyertakan pemeriksa ABI header, seperti yang dijelaskan di bagian berikut.

Tentang kepatuhan VNDK dan ABI

VNDK adalah kumpulan library yang membatasi yang dapat ditautkan ke modul vendor dan yang memungkinkan update khusus framework. Kepatuhan ABI mengacu pada kemampuan library bersama versi yang lebih baru untuk berfungsi seperti yang diharapkan dengan modul yang ditautkan secara dinamis ke library tersebut (yaitu berfungsi seperti library versi lama).

Tentang simbol yang diekspor

Simbol yang diekspor (juga dikenal sebagai simbol global) mengacu pada simbol yang memenuhi semua hal berikut:

  • Diekspor oleh header publik library bersama.
  • Muncul di tabel .dynsym file .so yang sesuai dengan library bersama.
  • Memiliki binding WEAK atau GLOBAL.
  • Visibilitas adalah DEFAULT atau PROTECTED.
  • Indeks bagian bukan UNDEFINED.
  • Jenisnya adalah FUNC atau OBJECT.

Header publik library bersama ditentukan sebagai header yang tersedia untuk library/biner lain melalui atribut export_include_dirs, export_header_lib_headers, export_static_lib_headers, export_shared_lib_headers, dan export_generated_headers dalam definisi Android.bp modul yang sesuai dengan library bersama.

Tentang jenis yang dapat dijangkau

Jenis yang dapat dijangkau adalah jenis bawaan C/C++ atau jenis yang ditentukan pengguna yang dapat dijangkau secara langsung atau tidak langsung melalui simbol yang diekspor DAN diekspor melalui header publik. Misalnya, libfoo.so memiliki fungsi Foo, yang merupakan simbol diekspor yang ditemukan dalam tabel .dynsym. Library libfoo.so mencakup hal berikut:

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"
  ],
}
Tabel .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

Melihat Foo, jenis yang dapat dijangkau langsung/tidak langsung mencakup:

Jenis Deskripsi
bool Jenis nilai yang ditampilkan Foo.
int Jenis parameter Foo pertama.
bar_t * Jenis parameter Foo kedua. Melalui bar_t *, bar_t diekspor melalui foo_exported.h.

bar_t berisi anggota mfoo, dari jenis foo_t, yang diekspor melalui foo_exported.h, yang menghasilkan lebih banyak jenis yang diekspor:
  • int : adalah jenis m1.
  • int * : adalah jenis m2.
  • foo_private_t * : adalah jenis mPfoo.

Namun, foo_private_t TIDAK dapat dijangkau karena tidak diekspor melalui foo_exported.h. (foo_private_t * buram sehingga perubahan yang dilakukan pada foo_private_t diizinkan.)

Penjelasan serupa juga dapat diberikan untuk jenis yang dapat dijangkau melalui penentu class dasar dan parameter template.

Memastikan kepatuhan ABI

Kepatuhan ABI harus dipastikan untuk library yang ditandai vendor_available: true dan vndk.enabled: true dalam file Android.bp yang sesuai. Contoh:

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

Untuk jenis data yang dapat dijangkau secara langsung atau tidak langsung oleh fungsi yang diekspor, perubahan berikut pada library diklasifikasikan sebagai pelanggaran ABI:

Jenis data Deskripsi
Struktur dan Class
  • Ubah ukuran jenis class atau jenis struct.
  • Class dasar
    • Menambahkan atau menghapus class dasar.
    • Menambahkan atau menghapus class dasar yang diwarisi secara virtual.
    • Ubah urutan class dasar.
  • Fungsi anggota
    • Menghapus fungsi anggota*.
    • Menambahkan atau menghapus argumen dari fungsi anggota.
    • Mengubah jenis argumen atau jenis nilai yang ditampilkan dari fungsi anggota*.
    • Mengubah tata letak tabel virtual.
  • Anggota data
    • Hapus anggota data statis.
    • Menambahkan atau menghapus anggota data non-statis.
    • Mengubah jenis anggota data.
    • Ubah offset menjadi anggota data non-statis**.
    • Ubah penentu const, volatile, dan/atau restricted anggota data***.
    • Downgrade penentu akses anggota data***.
  • Ubah argumen template.
Serikat
  • Tambahkan atau hapus anggota data.
  • Ubah ukuran jenis union.
  • Mengubah jenis anggota data.
Enumerasi
  • Ubah jenis dasar.
  • Mengubah nama penghitung.
  • Mengubah nilai penghitung.
Simbol Global
  • Hapus simbol yang diekspor oleh header publik.
  • Untuk simbol global jenis FUNC
    • Menambahkan atau menghapus argumen.
    • Mengubah jenis argumen.
    • Ubah jenis nilai yang ditampilkan.
    • Downgrade penentu akses***.
  • Untuk simbol global dari jenis OBJECT
    • Ubah jenis C/C++ yang sesuai.
    • Downgrade penentu akses***.

* Fungsi anggota publik dan pribadi tidak boleh diubah atau dihapus karena fungsi inline publik dapat merujuk ke fungsi anggota pribadi. Referensi simbol ke fungsi anggota pribadi dapat disimpan dalam biner pemanggil. Mengubah atau menghapus fungsi anggota pribadi dari library bersama dapat menghasilkan biner yang tidak kompatibel dengan versi sebelumnya.

** Offset ke anggota data publik atau pribadi tidak boleh diubah karena fungsi inline dapat merujuk ke anggota data ini dalam isi fungsinya. Mengubah offset anggota data dapat menyebabkan biner yang tidak kompatibel dengan versi sebelumnya.

*** Meskipun hal ini tidak mengubah tata letak memori jenis, ada perbedaan semantik yang dapat menyebabkan library tidak berfungsi seperti yang diharapkan.

Menggunakan alat kepatuhan ABI

Saat library VNDK dibuat, ABI library akan dibandingkan dengan referensi ABI yang sesuai untuk versi VNDK yang sedang dibuat. Dump ABI referensi berada di:

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

Misalnya, saat mem-build libfoo untuk x86 di API level 27, ABI yang disimpulkan libfoo dibandingkan dengan referensinya di:

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

Error kerusakan ABI

Pada kerusakan ABI, log build menampilkan peringatan dengan jenis peringatan dan jalur ke laporan abi-diff. Misalnya, jika ABI libbinder memiliki perubahan yang tidak kompatibel, sistem build akan menampilkan error dengan pesan yang mirip dengan berikut:

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

Membangun pemeriksaan ABI library VNDK

Saat library VNDK di-build:

  1. header-abi-dumper memproses file sumber yang dikompilasi untuk membangun library VNDK (file sumber library sendiri serta file sumber yang diwarisi melalui dependensi transitif statis), untuk menghasilkan file .sdump yang sesuai dengan setiap sumber.
    Pembuatan sdump
    Gambar 1. Membuat file .sdump
  2. header-abi-linker kemudian memproses file .sdump (menggunakan skrip versi yang disediakan untuknya atau file .so yang sesuai dengan library bersama) untuk menghasilkan file .lsdump yang mencatat semua informasi ABI yang sesuai dengan library bersama.
    Pembuatan lsdump
    Gambar 2. Membuat file .lsdump
  3. header-abi-diff membandingkan file .lsdump dengan file .lsdump referensi untuk menghasilkan laporan perbedaan yang menguraikan perbedaan dalam ABI dari kedua library.
    pembuatan perbedaan abi
    Gambar 3. Membuat laporan perbedaan

header-abi-dumper

Alat header-abi-dumper menguraikan file sumber C/C++ dan membuang ABI yang disimpulkan dari file sumber tersebut ke dalam file perantara. Sistem build akan menjalankan header-abi-dumper pada semua file sumber yang dikompilasi, sekaligus mem-build library yang menyertakan file sumber dari dependensi transitif.

Masukan
  • File sumber C/C++
  • Direktori include yang diekspor
  • Flag compiler
Output File yang mendeskripsikan ABI file sumber (misalnya, foo.sdump merepresentasikan ABI foo.cpp).

Saat ini file .sdump dalam format JSON, yang tidak dijamin stabil di seluruh rilis mendatang. Dengan demikian, pemformatan file .sdump harus dianggap sebagai detail implementasi sistem build.

Misalnya, libfoo.so memiliki file sumber berikut 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;
}

Anda dapat menggunakan header-abi-dumper untuk membuat file .sdump perantara yang mewakili ABI yang ditampilkan oleh file sumber menggunakan:

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

Perintah ini memberi tahu header-abi-dumper untuk mengurai foo.cpp dengan flag compiler setelah --, dan menampilkan informasi ABI yang diekspor oleh header publik di direktori exported. Berikut adalah foo.sdump yang dihasilkan oleh 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 berisi informasi ABI yang diekspor oleh file sumber foo.cpp dan header publik, misalnya,

  • record_types. Lihat struct, union, atau class yang ditentukan dalam header publik. Setiap jenis data memiliki informasi tentang kolomnya, ukurannya, penentu akses, file header tempatnya ditentukan, dan atribut lainnya.
  • pointer_types. Merujuk ke jenis pointer yang direferensikan secara langsung/tidak langsung oleh data/fungsi yang diekspor di header publik, beserta jenis yang ditunjuk pointer (melalui kolom referenced_type di type_info). Informasi serupa dicatat dalam file .sdump untuk jenis yang memenuhi syarat, jenis C/C++ bawaan, jenis array, dan jenis referensi lvalue dan rvalue. Informasi tersebut memungkinkan diffing rekursif.
  • functions. Merepresentasikan fungsi yang diekspor oleh header publik. Fungsi ini juga memiliki informasi tentang nama fungsi yang di-mangle, jenis nilai yang ditampilkan, jenis parameter, pengonfigurasi akses, dan atribut lainnya.

header-abi-linker

Alat header-abi-linker mengambil file perantara yang dihasilkan oleh header-abi-dumper sebagai input, lalu menautkan file tersebut:

Masukan
  • File perantara yang dihasilkan oleh header-abi-dumper
  • Skrip versi/File peta (opsional)
  • File .so library bersama
  • Direktori include yang diekspor
Output File yang mendeskripsikan ABI library bersama (misalnya, libfoo.so.lsdump mewakili ABI libfoo).

Alat ini menggabungkan grafik jenis dalam semua file perantara yang diberikan, dengan mempertimbangkan perbedaan definisi satu (jenis yang ditentukan pengguna dalam unit terjemahan berbeda dengan nama yang sepenuhnya memenuhi syarat, mungkin berbeda secara semantik) di seluruh unit terjemahan. Alat ini kemudian mengurai skrip versi atau tabel .dynsym library bersama (file .so) untuk membuat daftar simbol yang diekspor.

Misalnya, libfoo terdiri dari foo.cpp dan bar.cpp. header-abi-linker dapat dipanggil untuk membuat dump ABI tertaut lengkap dari libfoo sebagai berikut:

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

Contoh output perintah di 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" : []
}

Alat header-abi-linker:

  • Menautkan file .sdump yang disediakan untuknya (foo.sdump dan bar.sdump), memfilter informasi ABI yang tidak ada di header yang berada di direktori: exported.
  • Mengurai libfoo.so, dan mengumpulkan informasi tentang simbol yang diekspor oleh library melalui tabel .dynsym.
  • Menambahkan _Z3FooiP3bar dan _Z6FooBadiP3foo.

libfoo.so.lsdump adalah dump ABI akhir yang dihasilkan dari libfoo.so.

header-abi-diff

Alat header-abi-diff membandingkan dua file .lsdump yang mewakili ABI dari dua library dan menghasilkan laporan perbedaan yang menyatakan perbedaan antara kedua ABI.

Masukan
  • File .lsdump yang mewakili ABI library bersama lama.
  • File .lsdump yang mewakili ABI library bersama yang baru.
Output Laporan perbedaan yang menyatakan perbedaan dalam ABI yang ditawarkan oleh dua library bersama yang dibandingkan.

File perbedaan ABI dalam format teks protobuf. Format ini dapat berubah dalam rilis mendatang.

Misalnya, Anda memiliki dua versi libfoo: libfoo_old.so dan libfoo_new.so. Di libfoo_new.so, di bar_t, Anda mengubah jenis mfoo dari foo_t menjadi foo_t *. Karena bar_t adalah jenis yang dapat dijangkau, hal ini harus ditandai sebagai perubahan yang dapat menyebabkan gangguan pada ABI oleh header-abi-diff.

Untuk menjalankan header-abi-diff:

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

Contoh output perintah di 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 berisi laporan semua perubahan yang menyebabkan kerusakan ABI di libfoo. Pesan record_type_diffs menunjukkan bahwa data telah berubah dan mencantumkan perubahan yang tidak kompatibel, yang mencakup:

  • Ukuran data berubah dari 24 byte menjadi 8 byte.
  • Jenis kolom mfoo berubah dari foo menjadi foo * (semua typedef dihapus).

Kolom type_stack menunjukkan cara header-abi-diff mencapai jenis yang berubah (bar). Kolom ini dapat ditafsirkan sebagai Foo adalah fungsi yang diekspor yang menggunakan bar * sebagai parameter, yang mengarah ke bar, yang diekspor dan diubah.

Menerapkan ABI dan API

Untuk menerapkan ABI dan API library bersama VNDK, referensi ABI harus diperiksa ke ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/. Untuk membuat referensi ini, jalankan perintah berikut:

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

Setelah membuat referensi, setiap perubahan yang dilakukan pada kode sumber yang mengakibatkan perubahan ABI/API yang tidak kompatibel pada library VNDK kini akan menghasilkan error build.

Untuk memperbarui referensi ABI untuk library tertentu, jalankan perintah berikut:

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

Misalnya, untuk memperbarui referensi ABI libbinder, jalankan:

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