Tính ổn định của Giao diện nhị phân của ứng dụng (ABI) là điều kiện tiên quyết của việc cập nhật chỉ khung, vì các mô-đun của nhà cung cấp có thể phụ thuộc vào các thư viện dùng chung của Bộ phát triển gốc dành cho nhà cung cấp (VNDK) nằm trong phân vùng hệ thống. Trong bản phát hành Android, các thư viện chia sẻ VNDK mới tạo phải tương thích với ABI với các thư viện chia sẻ VNDK đã phát hành trước đó để các mô-đun nhà cung cấp có thể hoạt động với các thư viện đó mà không cần biên dịch lại và không gặp lỗi thời gian chạy. Giữa các bản phát hành Android, thư viện VNDK có thể thay đổi và không có gì đảm bảo về ABI.
Để giúp đảm bảo khả năng tương thích với ABI, Android 9 cung cấp trình kiểm tra ABI tiêu đề, như mô tả trong các phần sau.
Giới thiệu về việc tuân thủ ABI và VNDK
VNDK là một tập hợp thư viện hạn chế mà các mô-đun nhà cung cấp có thể liên kết đến và cho phép cập nhật chỉ khung. Tuân thủ ABI đề cập đến khả năng một phiên bản thư viện dùng chung mới hơn hoạt động như mong đợi với một mô-đun được liên kết động với mô-đun đó (tức là hoạt động như phiên bản thư viện cũ).
Giới thiệu về biểu tượng đã xuất
Ký hiệu được xuất (còn gọi là ký hiệu toàn cục) là một ký hiệu đáp ứng tất cả các điều kiện sau:
- Được xuất bởi tiêu đề công khai của thư viện dùng chung.
- Xuất hiện trong bảng
.dynsym
của tệp.so
tương ứng với thư viện chia sẻ. - Có liên kết WEAK hoặc GLOBAL.
- Chế độ hiển thị là MẶC ĐỊNH hoặc BẢO VỆ.
- Chỉ mục của mục không ĐƯỢC XÁC ĐỊNH.
- Loại là FUNC hoặc OBJECT.
Tiêu đề công khai của thư viện dùng chung được xác định là tiêu đề có sẵn cho các thư viện/tệp nhị phân khác thông qua các thuộc tính export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
và export_generated_headers
trong định nghĩa Android.bp
của mô-đun tương ứng với thư viện dùng chung.
Giới thiệu về các loại có thể truy cập
Loại có thể truy cập là bất kỳ loại C/C++ tích hợp hoặc do người dùng xác định nào có thể truy cập trực tiếp hoặc gián tiếp thông qua một biểu tượng đã xuất VÀ được xuất thông qua tiêu đề công khai. Ví dụ: libfoo.so
có hàm Foo
, đây là một biểu tượng đã xuất có trong bảng .dynsym
. Thư viện libfoo.so
bao gồm các thành phần sau:
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" ], } |
bảng .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
|
Khi xem xét Foo
, các loại có thể truy cập trực tiếp/gián tiếp bao gồm:
Loại | Mô tả |
---|---|
bool
|
Loại dữ liệu trả về của Foo .
|
int
|
Loại tham số Foo đầu tiên.
|
bar_t *
|
Loại tham số Foo thứ hai. Thông qua bar_t * , bar_t được xuất thông qua foo_exported.h .
bar_t chứa một thành phần mfoo , thuộc loại
foo_t , được xuất thông qua foo_exported.h ,
dẫn đến việc xuất nhiều loại hơn:
Tuy nhiên, foo_private_t KHÔNG truy cập được vì không được
xuất thông qua foo_exported.h . (foo_private_t * là mờ, do đó, các thay đổi đối với foo_private_t được cho phép.)
|
Bạn cũng có thể đưa ra giải thích tương tự cho các loại có thể truy cập thông qua chỉ định lớp cơ sở và tham số mẫu.
Đảm bảo tuân thủ ABI
Bạn phải đảm bảo tuân thủ ABI cho các thư viện được đánh dấu là vendor_available: true
và vndk.enabled: true
trong các tệp Android.bp
tương ứng. Ví dụ:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Đối với các loại dữ liệu mà hàm đã xuất có thể truy cập trực tiếp hoặc gián tiếp, các thay đổi sau đây đối với thư viện được phân loại là vi phạm ABI:
Kiểu dữ liệu | Mô tả |
---|---|
Cấu trúc và lớp |
|
Liên minh |
|
Liệt kê |
|
Biểu tượng chung |
|
* Bạn không được thay đổi hoặc xoá cả hàm thành viên công khai và hàm thành viên riêng tư vì các hàm nội tuyến công khai có thể tham chiếu đến các hàm thành viên riêng tư. Bạn có thể giữ lại ký hiệu tham chiếu đến các hàm thành phần riêng tư trong tệp nhị phân của phương thức gọi. Việc thay đổi hoặc xoá các hàm thành viên riêng tư khỏi thư viện dùng chung có thể dẫn đến các tệp nhị phân không tương thích ngược.
** Bạn không được thay đổi độ dời đối với thành phần dữ liệu công khai hoặc riêng tư vì các hàm nội tuyến có thể tham chiếu đến các thành phần dữ liệu này trong phần nội dung hàm. Việc thay đổi độ dời thành phần dữ liệu có thể dẫn đến các tệp nhị phân không tương thích ngược.
*** Mặc dù những điều này không thay đổi bố cục bộ nhớ của loại, nhưng có sự khác biệt về ngữ nghĩa có thể khiến thư viện không hoạt động như mong đợi.
Sử dụng các công cụ tuân thủ ABI
Khi tạo thư viện VNDK, ABI của thư viện sẽ được so sánh với tài liệu tham khảo ABI tương ứng cho phiên bản VNDK đang được tạo. Tệp báo lỗi tham chiếu ABI nằm ở:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Ví dụ: khi tạo libfoo
cho x86 ở API cấp 27, ABI suy luận của libfoo
được so sánh với tệp tham chiếu của ABI đó tại:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Lỗi hỏng ABI
Khi xảy ra lỗi ABI, nhật ký bản dựng sẽ hiển thị cảnh báo cùng với loại cảnh báo và đường dẫn đến báo cáo abi-diff. Ví dụ: nếu ABI của libbinder
có một thay đổi không tương thích, hệ thống xây dựng sẽ gửi một lỗi kèm theo thông báo tương tự như sau:
***************************************************** 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 ----
Tạo các bước kiểm tra ABI thư viện VNDK
Khi thư viện VNDK được tạo:
header-abi-dumper
xử lý các tệp nguồn được biên dịch để tạo thư viện VNDK (các tệp nguồn của thư viện cũng như các tệp nguồn được kế thừa thông qua các phần phụ thuộc bắc cầu tĩnh) để tạo các tệp.sdump
tương ứng với từng nguồn.
Hình 1. Tạo các tệp .sdump
- Sau đó,
header-abi-linker
sẽ xử lý các tệp.sdump
(bằng cách sử dụng tập lệnh phiên bản được cung cấp cho thư viện dùng chung hoặc tệp.so
tương ứng với thư viện dùng chung) để tạo một tệp.lsdump
ghi nhật ký tất cả thông tin ABI tương ứng với thư viện dùng chung.
Hình 2. Tạo tệp .lsdump
header-abi-diff
so sánh tệp.lsdump
với tệp.lsdump
đối chiếu để tạo báo cáo so sánh nêu rõ sự khác biệt trong ABI của hai thư viện.
Hình 3. Tạo báo cáo điểm khác biệt
header-abi-dumper
Công cụ header-abi-dumper
phân tích cú pháp tệp nguồn C/C++ và kết xuất ABI suy ra từ tệp nguồn đó vào một tệp trung gian. Hệ thống xây dựng chạy header-abi-dumper
trên tất cả các tệp nguồn đã biên dịch, đồng thời tạo một thư viện bao gồm các tệp nguồn từ các phần phụ thuộc bắc cầu.
Ngõ vào |
|
---|---|
Đầu ra | Tệp mô tả ABI của tệp nguồn (ví dụ: foo.sdump đại diện cho ABI của foo.cpp ).
|
Hiện tại, các tệp .sdump
ở định dạng JSON và không được đảm bảo sẽ ổn định trong các bản phát hành trong tương lai. Do đó, việc định dạng tệp .sdump
nên được coi là một chi tiết triển khai hệ thống xây dựng.
Ví dụ: libfoo.so
có tệp nguồn sau đây 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; }
Bạn có thể sử dụng header-abi-dumper
để tạo một tệp .sdump
trung gian đại diện cho ABI do tệp nguồn trình bày bằng cách sử dụng:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Lệnh này yêu cầu header-abi-dumper
phân tích cú pháp foo.cpp
bằng các cờ trình biên dịch theo --
và phát thông tin ABI do các tiêu đề công khai xuất trong thư mục exported
. Sau đây là foo.sdump
do header-abi-dumper
tạo:
{ "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
chứa thông tin ABI do tệp nguồn foo.cpp
xuất và các tiêu đề công khai, chẳng hạn như
record_types
. Tham khảo các cấu trúc, liên kết hoặc lớp được xác định trong tiêu đề công khai. Mỗi loại bản ghi đều có thông tin về các trường, kích thước, thông số xác định quyền truy cập, tệp tiêu đề được xác định trong đó và các thuộc tính khác.pointer_types
. Tham chiếu đến các loại con trỏ được tham chiếu trực tiếp/gián tiếp bởi các bản ghi/hàm đã xuất trong tiêu đề công khai, cùng với loại con trỏ trỏ đến (thông qua trườngreferenced_type
trongtype_info
). Thông tin tương tự được ghi lại trong tệp.sdump
cho các loại đủ điều kiện, các loại C/C++ tích hợp, các loại mảng và các loại tham chiếu lvalue và rvalue. Thông tin đó cho phép so sánh đệ quy.functions
. Biểu thị các hàm do tiêu đề công khai xuất. Các tệp này cũng có thông tin về tên bị xáo trộn của hàm, loại dữ liệu trả về, loại tham số, chỉ định quyền truy cập và các thuộc tính khác.
trình liên kết tiêu đề
Công cụ header-abi-linker
lấy các tệp trung gian do header-abi-dumper
tạo làm dữ liệu đầu vào, sau đó liên kết các tệp đó:
Ngõ vào |
|
---|---|
Đầu ra | Tệp mô tả ABI của một thư viện dùng chung (ví dụ: libfoo.so.lsdump đại diện cho ABI của libfoo ).
|
Công cụ này hợp nhất các biểu đồ loại trong tất cả các tệp trung gian được cung cấp cho công cụ,
có tính đến sự khác biệt về định nghĩa một (các loại do người dùng xác định trong các đơn vị dịch khác nhau
có cùng tên đủ điều kiện, có thể khác về ngữ nghĩa) giữa các đơn vị dịch. Sau đó, công cụ này sẽ phân tích cú pháp tập lệnh phiên bản hoặc bảng .dynsym
của thư viện dùng chung (tệp .so
) để tạo danh sách các ký hiệu đã xuất.
Ví dụ: libfoo
bao gồm foo.cpp
và bar.cpp
. header-abi-linker
có thể được gọi để tạo tệp kết xuất ABI được liên kết hoàn chỉnh của libfoo
như sau:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Kết quả lệnh mẫu trong 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" : [] }
Công cụ header-abi-linker
:
- Liên kết các tệp
.sdump
được cung cấp cho nó (foo.sdump
vàbar.sdump
), lọc ra thông tin ABI không có trong các tiêu đề nằm trong thư mục:exported
. - Phân tích cú pháp
libfoo.so
và thu thập thông tin về các ký hiệu mà thư viện xuất qua bảng.dynsym
. - Thêm
_Z3FooiP3bar
và_Z6FooBadiP3foo
.
libfoo.so.lsdump
là tệp kết xuất ABI được tạo cuối cùng của libfoo.so
.
header-abi-diff
Công cụ header-abi-diff
so sánh hai tệp .lsdump
đại diện cho ABI của hai thư viện và tạo một báo cáo so sánh nêu rõ sự khác biệt giữa hai ABI.
Ngõ vào |
|
---|---|
Đầu ra | Báo cáo so sánh cho biết sự khác biệt trong ABI do hai thư viện dùng chung được so sánh cung cấp. |
Tệp so sánh ABI ở định dạng văn bản protobuf. Định dạng này có thể thay đổi trong các bản phát hành sau này.
Ví dụ: bạn có hai phiên bản libfoo
: libfoo_old.so
và libfoo_new.so
. Trong libfoo_new.so
, trong bar_t
, bạn sẽ thay đổi loại mfoo
từ foo_t
thành foo_t *
. Vì bar_t
là một loại có thể tiếp cận, nên loại thay đổi này phải được gắn cờ là thay đổi có thể gây lỗi ABI trước header-abi-diff
.
Cách chạy header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Ví dụ về kết quả của lệnh trong 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
chứa báo cáo về tất cả các thay đổi phá vỡ ABI trong libfoo
. Thông báo record_type_diffs
cho biết một bản ghi đã thay đổi và liệt kê các thay đổi không tương thích, bao gồm:
- Kích thước của bản ghi thay đổi từ
24
byte thành8
byte. - Loại trường của
mfoo
thay đổi từfoo
thànhfoo *
(tất cả các typedef đều bị xoá).
Trường type_stack
cho biết cách header-abi-diff
truy cập vào loại đã thay đổi (bar
). Trường này có thể được diễn giải là Foo
là một hàm đã xuất, lấy bar *
làm tham số, trỏ đến bar
đã được xuất và thay đổi.
Thực thi ABI và API
Để thực thi ABI và API của thư viện dùng chung VNDK, bạn phải kiểm tra các tệp tham chiếu ABI vào ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
.
Để tạo các tệp đối chiếu này, hãy chạy lệnh sau:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Sau khi tạo các tệp tham chiếu, mọi thay đổi đối với mã nguồn dẫn đến thay đổi ABI/API không tương thích trong thư viện VNDK hiện sẽ dẫn đến lỗi bản dựng.
Để cập nhật các tệp tham chiếu ABI cho các thư viện cụ thể, hãy chạy lệnh sau:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Ví dụ: để cập nhật các tệp tham chiếu ABI libbinder
, hãy chạy:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder