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 bản cập nhật chỉ dựa trên khung vì các mô-đun nhà cung cấp có thể phụ thuộc vào Quảng cáo gốc của nhà cung cấp Các thư viện dùng chung của Bộ phát triển (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 thư viện chia sẻ VNDK đã phát hành trước đây, vì vậy, các mô-đun của nhà cung cấp có thể làm việc 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, bạn có thể thay đổi thư viện VNDK và không có ABI nào bảo đảm.
Để giúp đảm bảo khả năng tương thích với ABI, Android 9 bao gồm 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ủ VNDK và ABI
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 phiên bản mới hơn của thư viện dùng chung hoạt động như mong đợi với được liên kết động với mô-đun đó (tức là hoạt động như một phiên bản cũ của thư viện sẽ).
Giới thiệu về biểu tượng đã xuất
Biểu tượng được xuất (còn được gọi là biểu tượng toàn cầu) đề cập đến một ký hiệu thoả mãn tất cả các điều kiện sau:
- Do tiêu đề công khai của thư viện chia sẻ xuất.
- Xuất hiện trong bảng
.dynsym
của tệp.so
tương ứng với thư viện chia sẻ. - Có ràng buộc WEAK hoặc TOÀN CẦU.
- Chế độ hiển thị là MẶC ĐỊNH hoặc ĐƯỢ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 chia sẻ đượ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
export_include_dirs
, export_header_lib_headers
export_static_lib_headers
,
export_shared_lib_headers
và
Thuộc tính export_generated_headers
trong Android.bp
các định nghĩa của mô-đun tương ứng với thư viện chia sẻ.
Giới thiệu về các loại có thể truy cập
Loại có thể truy cập là loại bất kỳ tích hợp C/C++ hoặc loại nào do người dùng xác định có thể
có thể truy cập trực tiếp hoặc gián tiếp thông qua biểu tượng được xuất VÀ được xuất
thông qua các tiêu đề công khai. Ví dụ: libfoo.so
có hàm
Foo
là một biểu tượng đã xuất trong
Bảng .dynsym
. Thư viện libfoo.so
bao gồm
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
|
Trong Foo
, các loại có thể tiếp cận 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. Bằng 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 phải là
được xuất thông qua foo_exported.h . (foo_private_t * )
không rõ ràng, do đó các thay đổi được thực hiện cho foo_private_t được cho phép.)
|
Bạn có thể xem nội dung giải thích tương tự cho các loại có thể truy cập thông qua lớp cơ sở tham số mẫu và thông số kỹ thuật.
Đả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
vendor_available: true
và vndk.enabled: true
trong
Android.bp
tệp 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 có thể truy cập trực tiếp hoặc gián tiếp bằng một hàm đã xuất, những thay đổi sau đây đối với thư viện được phân loại là lỗi ABI:
Loại dữ liệu | Mô tả |
---|---|
Cấu trúc và lớp |
|
Liên minh |
|
Liệt kê |
|
Biểu tượng chung |
|
* Cả hàm thành viên công khai và riêng tư đều phải không thể thay đổi hoặc xoá được vì các hàm cùng dòng công khai có thể tham chiếu đến hàm thành phần riêng tư. Tham chiếu biểu tượng đến các hàm thành phần riêng tư có thể được lưu trong tệp nhị phân của phương thức gọi. Thay đổi hoặc xoá các hàm thành viên riêng tư từ thư viện dùng chung có thể dẫn đến tệp nhị phân không tương thích ngược.
** Mức chênh lệch cho các thành viên có dữ liệu công khai hoặc riêng tư không được là vì các hàm cùng dòng có thể tham chiếu đến các thành phần dữ liệu này trong nội dung hàm. Việc thay đổi độ lệch của thành phần dữ liệu có thể dẫn đến tệp nhị phân không tương thích ngược.
*** Mặc dù các chế độ này không làm thay đổi bố cục bộ nhớ khác nhau về ngữ nghĩa có thể khiến thư viện không hoạt động như dự kiến.
Sử dụng các công cụ tuân thủ ABI
Khi xây dựng một 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 xây dựng. Tham chiếu Tệp kết xuất ABI nằm trong:
${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 theo dự đoán của libfoo
được so sánh với tệp tham chiếu tại:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Lỗi hỏng ABI
Khi bị lỗi ABI, nhật ký bản dựng sẽ hiện cảnh báo kèm theo 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ó
thay đổi không tương thích, hệ thống xây dựng sẽ gửi 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 ----
Xây dựng quy trình kiểm tra ABI thư viện VNDK
Khi xây dựng thư viện VNDK:
header-abi-dumper
sẽ xử lý các tệp nguồn đã biên dịch thành tạo thư viện VNDK (tệp nguồn riêng của thư viện cũng như tệp nguồn kế thừa thông qua các phần phụ thuộc bắc cầu tĩnh), để tạo ra Các tệp.sdump
tương ứng với mỗi nguồn.
Hình 1. Đang tạo .sdump
tệp- Sau đó,
header-abi-linker
sẽ xử lý.sdump
(bằng cách sử dụng tập lệnh phiên bản được cung cấp cho tệp đó hoặc.so
tương ứng với thư viện dùng chung) để tạo.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. Đang tạo .lsdump
tệp header-abi-diff
so sánh.lsdump
có tệp tham chiếu.lsdump
để tạo báo cáo điểm khác biệt chỉ ra 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à tệp kết xuất
ABI suy luận từ tệp nguồn đó thành tệp trung gian. Bản dựng
hệ thống chạy header-abi-dumper
trên tất cả các tệp nguồn được biên dịch trong khi
tạo một thư viện bao gồm các tệp nguồn từ bắc cầu
phần phụ thuộc.
Ngõ vào |
|
---|---|
Đầu ra | Một 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
đang ở định dạng JSON và bạn không phải
đảm bảo phiên bản ổn định trong các bản phát hành sau này. Do đó, .sdump
định dạng tệp nên được coi là chi tiết triển khai hệ thống xây dựng.
Ví dụ: libfoo.so
có tệp nguồn sau
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ể dùng header-abi-dumper
để tạo một trung gian
Tệp .sdump
đại diện cho ABI mà tệp nguồn cung cấp
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
với cờ của trình biên dịch theo sau --
và
phát đi thông tin ABI mà các tiêu đề công khai xuất trong
Thư mục exported
. Dưới đây là
foo.sdump
được tạo bởi
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
chứa thông tin ABI mà tệp nguồn xuất ra
foo.cpp
và tiêu đề công khai, ví dụ:
record_types
. Tham chiếu đến 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 có thông tin về các trường, kích thước, thông số truy cập, tệp tiêu đề mà thông số đó được xác định, và .pointer_types
. Tham khảo trực tiếp/gián tiếp các loại con trỏ được tham chiếu bởi các bản ghi/hàm đã xuất trong tiêu đề công khai, cùng với bằng loại mà con trỏ trỏ đến (thông quareferenced_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, loại C/C++ tích hợp sẵn, mảng và các kiểu tham chiếu lvalue và rvalue. Thông tin như vậy cho phép nhiễu xạ đệ quy.functions
. Biểu thị các hàm do tiêu đề công khai xuất. Chúng cũng có thông tin về tên bị cắt bớt của hàm, loại dữ liệu trả về, các loại tham số, thông số xác đị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 được tạo
bởi header-abi-dumper
làm đầ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 một định nghĩa (loại do người dùng xác định trong các
các đơn vị bản dịch có cùng tên đủ điều kiện, có thể được về mặt ngữ nghĩa
khác nhau) giữa các đơn vị bản dịch. Sau đó, công cụ này 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 biểu tượng đã xuất.
Ví dụ: libfoo
bao gồm foo.cpp
và
bar.cpp
. header-abi-linker
có thể được gọi tớ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
Ví dụ về kết quả của lệnh 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 tới đó (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 được thư viện xuất thông 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 2 tệp .lsdump
đại diện cho ABI của hai thư viện và tạo một báo cáo điểm khác biệt nêu rõ
sự khác biệt giữa 2 ABI.
Ngõ vào |
|
---|---|
Đầu ra | Báo cáo điểm khác biệt nêu rõ sự khác biệt về ABI do 2 công cụ này cung cấp thư viện dùng chung. |
Tệp điểm khác biệt về ABI nằm trong protobuf. Định dạng 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
. Sau libfoo_new.so
nữa, sau
bar_t
, bạn thay đổi loại mfoo
từ
foo_t
đến foo_t *
. Vì bar_t
là một
có thể truy cập được, thì loại có thể truy cập này phải được gắn cờ là thay đổi có thể gây lỗi ABI bằng cách
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ả lỗi ABI
các thay đổi trong libfoo
. Tin nhắn 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ả typedef đều bị loại bỏ).
Trường type_stack
cho biết cách header-abi-diff
đạt đến loại đã thay đổi (bar
). Trường này có thể
Foo
là một hàm được xuất và nhận
bar *
làm tham số, trỏ đến bar
, tức là
đượ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, các tệp tham chiếu ABI phải
được đăng ký trong ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
.
Để tạo các tham 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 tham chiếu, mọi thay đổi đối với mã nguồn dẫn đến việc thay đổi ABI/API không tương thích trong thư viện VNDK nay dẫn đến lỗi bản dựng.
Để cập nhật 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