Stabilitas Antarmuka Biner Aplikasi (ABI) adalah prasyarat pembaruan kerangka saja karena modul vendor mungkin bergantung pada pustaka bersama Vendor Native Development Kit (VNDK) yang berada di partisi sistem. Dalam rilis Android, pustaka bersama VNDK yang baru dibuat harus kompatibel dengan ABI dengan pustaka bersama VNDK yang dirilis sebelumnya sehingga modul vendor dapat bekerja dengan pustaka tersebut tanpa kompilasi ulang dan tanpa kesalahan waktu proses. Di antara rilis Android, pustaka VNDK dapat diubah dan tidak ada jaminan ABI.
Untuk membantu memastikan kompatibilitas ABI, Android 9 menyertakan pemeriksa ABI header, seperti dijelaskan di bagian berikut.
Tentang kepatuhan VNDK dan ABI
VNDK adalah sekumpulan pustaka terbatas yang dapat ditautkan oleh modul vendor dan yang memungkinkan pembaruan kerangka kerja saja. Kepatuhan ABI mengacu pada kemampuan versi terbaru dari pustaka bersama untuk berfungsi seperti yang diharapkan dengan modul yang terhubung secara dinamis dengannya (yaitu berfungsi sebagaimana versi pustaka yang lebih 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 perpustakaan bersama.
- Muncul di tabel
.dynsym
dari file.so
yang sesuai dengan perpustakaan bersama. - Memiliki pengikatan LEMAH atau GLOBAL.
- Visibilitas adalah DEFAULT atau DILINDUNGI.
- Indeks bagian tidak TERDEFINISIKAN.
- Jenisnya adalah FUNC atau OBJECT.
Header publik dari pustaka bersama didefinisikan sebagai header yang tersedia untuk pustaka/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
dari modul yang terkait dengan pustaka bersama.
Tentang tipe yang dapat dijangkau
Tipe yang dapat dijangkau adalah tipe bawaan C/C++ atau 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 yang diekspor yang ditemukan di tabel .dynsym
. Pustaka libfoo.so
mencakup yang berikut ini:
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 meliputi:
Jenis | Keterangan |
---|---|
bool | Jenis pengembalian 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 , bertipe foo_t , yang diekspor melalui foo_exported.h , sehingga menghasilkan lebih banyak tipe yang diekspor:
Namun, foo_private_t TIDAK dapat dijangkau karena tidak diekspor melalui foo_exported.h . ( foo_private_t * buram, oleh karena itu perubahan yang dilakukan pada foo_private_t diperbolehkan.) |
Penjelasan serupa dapat diberikan untuk tipe yang dapat dijangkau melalui penentu kelas dasar dan parameter templat juga.
Memastikan kepatuhan ABI
Kepatuhan ABI harus dipastikan untuk pustaka yang ditandai vendor_available: true
dan vndk.enabled: true
di file Android.bp
yang sesuai. Misalnya:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Untuk tipe data yang dapat dijangkau secara langsung atau tidak langsung oleh fungsi yang diekspor, perubahan berikut pada pustaka diklasifikasikan sebagai pemecah ABI:
Tipe data | Keterangan |
---|---|
Struktur dan Kelas |
|
Serikat pekerja |
|
Pencacahan |
|
Simbol Global |
|
* Fungsi anggota publik dan privat tidak boleh diubah atau dihapus karena fungsi inline publik dapat merujuk pada fungsi anggota privat. Referensi simbol ke fungsi anggota privat dapat disimpan dalam biner pemanggil. Mengubah atau menghapus fungsi anggota pribadi dari perpustakaan bersama dapat mengakibatkan biner tidak kompatibel.
** Offset ke anggota data publik atau pribadi tidak boleh diubah karena fungsi inline dapat merujuk ke anggota data ini di isi fungsinya. Mengubah offset anggota data dapat mengakibatkan biner tidak kompatibel.
*** Meskipun hal ini tidak mengubah tata letak jenis memori, terdapat perbedaan semantik yang dapat menyebabkan perpustakaan tidak berfungsi seperti yang diharapkan.
Menggunakan alat kepatuhan ABI
Ketika perpustakaan VNDK dibangun, ABI perpustakaan dibandingkan dengan referensi ABI yang sesuai untuk versi VNDK yang sedang dibangun. Referensi dump ABI terletak di:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Misalnya, saat membuat 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
Kesalahan 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 kesalahan dengan pesan serupa 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 perpustakaan VNDK
Saat perpustakaan VNDK dibangun:
-
header-abi-dumper
memproses file sumber yang dikompilasi untuk membangun perpustakaan VNDK (file sumber milik perpustakaan serta file sumber yang diwarisi melalui dependensi transitif statis), untuk menghasilkan file.sdump
yang sesuai dengan setiap sumber.Gambar 1. Membuat file .sdump
-
header-abi-linker
kemudian memproses file.sdump
(menggunakan skrip versi yang disediakan atau file.so
yang sesuai dengan perpustakaan bersama) untuk menghasilkan file.lsdump
yang mencatat semua informasi ABI yang terkait dengan perpustakaan bersama.Gambar 2. Membuat file .lsdump
-
header-abi-diff
membandingkan file.lsdump
dengan file.lsdump
referensi untuk menghasilkan laporan perbedaan yang menguraikan perbedaan ABI kedua perpustakaan.Gambar 3. Membuat laporan perbedaan
header-abi-dumper
Alat header-abi-dumper
mem-parsing file sumber C/C++ dan membuang kesimpulan ABI dari file sumber tersebut ke file perantara. Sistem pembangunan menjalankan header-abi-dumper
pada semua file sumber yang dikompilasi sekaligus membangun perpustakaan yang menyertakan file sumber dari dependensi transitif.
masukan |
|
---|---|
Keluaran | File yang menjelaskan ABI file sumber (misalnya, foo.sdump mewakili ABI foo.cpp ). |
Saat ini file .sdump
dalam format JSON, yang tidak dijamin stabil pada rilis mendatang. Oleh karena itu, 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 menghasilkan file .sdump
perantara yang mewakili ABI yang disajikan oleh file sumber menggunakan:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Perintah ini memberitahu header-abi-dumper
untuk mengurai foo.cpp
dengan flag compiler berikut --
, dan mengeluarkan informasi ABI yang diekspor oleh header publik di direktori exported
. Berikut ini 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
. Merujuk pada struct, union, atau kelas yang ditentukan dalam header publik. Setiap jenis rekaman memiliki informasi tentang bidangnya, ukurannya, penentu akses, file header yang ditentukan, dan atribut lainnya. -
pointer_types
. Merujuk ke tipe penunjuk yang direferensikan secara langsung/tidak langsung oleh catatan/fungsi yang diekspor di header publik, bersama dengan tipe yang ditunjuk oleh penunjuk (melalui bidangreferenced_type
ditype_info
). Informasi serupa dicatat dalam file.sdump
untuk tipe yang memenuhi syarat, tipe C/C++ bawaan, tipe array, dan tipe referensi lvalue dan rvalue. Informasi tersebut memungkinkan diffing rekursif. -
functions
. Mewakili fungsi yang diekspor oleh header publik. Mereka juga memiliki informasi tentang nama fungsi yang rusak, tipe kembalian, tipe parameter, penentu akses, dan atribut lainnya.
header-abi-linker
Alat header-abi-linker
mengambil file perantara yang dihasilkan oleh header-abi-dumper
sebagai masukan lalu menautkan file-file tersebut:
masukan |
|
---|---|
Keluaran | File yang menjelaskan ABI perpustakaan bersama (misalnya, libfoo.so.lsdump mewakili ABI libfoo ). |
Alat ini menggabungkan grafik tipe di semua file perantara yang diberikan padanya, dengan mempertimbangkan perbedaan satu definisi (tipe yang ditentukan pengguna dalam unit terjemahan berbeda dengan nama yang sepenuhnya memenuhi syarat yang sama, mungkin berbeda secara semantik) di seluruh unit terjemahan. Alat tersebut kemudian mem-parsing skrip versi atau tabel .dynsym
dari perpustakaan 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 libfoo
tertaut lengkap sebagai berikut:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Contoh keluaran 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
:
- Tautkan file
.sdump
yang disediakan ke dalamnya (foo.sdump
danbar.sdump
), menyaring informasi ABI yang tidak ada di header yang berada di direktori:exported
. - Mengurai
libfoo.so
, dan mengumpulkan informasi tentang simbol yang diekspor oleh perpustakaan melalui tabel.dynsym
-nya. - Menambahkan
_Z3FooiP3bar
dan_Z6FooBadiP3foo
.
libfoo.so.lsdump
adalah dump ABI terakhir yang dihasilkan dari libfoo.so
.
header-abi-diff
Alat header-abi-diff
membandingkan dua file .lsdump
yang mewakili ABI dari dua perpustakaan dan menghasilkan laporan perbedaan yang menyatakan perbedaan antara kedua ABI.
masukan |
|
---|---|
Keluaran | Laporan perbedaan yang menyatakan perbedaan ABI yang ditawarkan oleh dua perpustakaan bersama dibandingkan. |
File diff ABI dalam format teks protobuf . Formatnya dapat berubah pada 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 tipe yang dapat dijangkau, ini harus ditandai sebagai perubahan yang dapat menyebabkan gangguan ABI dengan 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 keluaran 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 dapat menyebabkan gangguan ABI di libfoo
. Pesan record_type_diffs
menunjukkan bahwa record telah berubah dan mencantumkan perubahan yang tidak kompatibel, yang meliputi:
- Ukuran record berubah dari
24
byte menjadi8
byte. - Jenis bidang
mfoo
berubah darifoo
menjadifoo *
(semua typedef dihilangkan).
Bidang type_stack
menunjukkan bagaimana header-abi-diff
mencapai tipe yang diubah ( bar
). Bidang ini dapat diartikan sebagai Foo
adalah fungsi yang diekspor yang menggunakan bar *
sebagai parameter, yang menunjuk ke bar
, yang diekspor dan diubah.
Menerapkan ABI/API
Untuk menerapkan ABI/API perpustakaan 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 di pustaka VNDK kini mengakibatkan kesalahan build.
Untuk memperbarui referensi ABI untuk perpustakaan 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