Tính ổn định của Giao diện nhị phân ứng dụng (ABI) là điều kiện tiên quyết của các bản cập nhật chỉ dành cho 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 Vendor Native Development Kit (VNDK) nằm trong phân vùng hệ thống. Trong một bản phát hành Android, các thư viện dùng chung VNDK mới tạo phải tương thích với ABI của các thư viện dùng chung VNDK đã phát hành trước đó để các mô-đun của nhà cung cấp có thể hoạt động với những 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, các thư viện VNDK có thể thay đổi và không có đảm bảo ABI.
Để đảm bảo khả năng tương thích ABI, Android 9 có một trình kiểm tra ABI tiêu đề, như mô tả trong các phần sau.
Giới thiệu về VNDK và việc tuân thủ ABI
VNDK là một tập hợp hạn chế các thư viện mà các mô-đun của nhà cung cấp có thể liên kết đến và cho phép chỉ cập nhật khung. Khả năng tuân thủ ABI đề cập đến khả năng của phiên bản mới hơn của một thư viện dùng chung hoạt động như dự kiến với một mô-đun được liên kết động với thư viện đó (tức là hoạt động như phiên bản cũ hơn của thư viện).
Giới thiệu về các biểu tượng được xuất
Biểu tượng được xuất (còn gọi là biểu tượng chung) đề cập đến một biểu tượng đáp ứng tất cả các điều kiện sau:
- Được xuất bởi tiêu đề công khai của một 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 dùng chung. - Có liên kết YẾU hoặc TOÀN CỤC.
- Chế độ hiển thị là MẶC ĐỊNH hoặc ĐƯỢC BẢO VỆ.
- Chỉ mục phần không phải là UNDEFINED.
- Loại là FUNC hoặc OBJECT.
Tiêu đề công khai của một thư viện dùng chung được xác định là các tiêu đề có sẵn cho các thư viện/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 các đị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 tích hợp sẵn hoặc do người dùng xác định nào của C/C++ 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 các tiêu đề công khai. Ví dụ: libfoo.so
có hàm Foo
. Đây là một biểu tượng được xuất có trong bảng .dynsym
. Thư viện libfoo.so
bao gồm những nội dung 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
|
Nhìn vào 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ố đầu tiên Foo .
|
bar_t *
|
Loại tham số Foo thứ hai. Thông qua bar_t * , bar_t được xuất 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 có nhiều loại được xuất hơn:
Tuy nhiên, foo_private_t KHÔNG thể truy cập được vì không được xuất thông qua foo_exported.h . (foo_private_t * là một đối tượng mờ, do đó, bạn được phép thay đổi foo_private_t .)
|
Bạn cũng có thể đưa ra lời giải thích tương tự cho các loại có thể truy cập thông qua các bộ 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 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 kiểu 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 một thư viện được phân loại là thay đổi làm gián đoạn ABI:
Loại dữ liệu | Mô tả |
---|---|
Cấu trúc và lớp học |
|
Liên minh |
|
Bảng liệt kê |
|
Biểu tượng toàn cầu |
|
* Cả hàm thành viên công khai và riêng tư đều không được thay đổi hoặc xoá 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ư. Các tham chiếu biểu tượng đến các hàm thành viên riêng tư có thể được giữ trong các tệp nhị phân của người 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 độ lệch của các 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 độ lệch của 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 thay đổi này không làm thay đổi bố cục bộ nhớ của loại, nhưng có những khác biệt về ngữ nghĩa có thể khiến các thư viện không hoạt động như mong đợi.
Sử dụng các công cụ tuân thủ ABI
Khi một thư viện VNDK được tạo, 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 kết xuất ABI tham chiếu 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 suy luận của libfoo
sẽ được so sánh với ABI tham chiếu tại:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Lỗi ABI bị hỏng
Khi ABI bị gián đoạn, nhật ký bản dựng sẽ hiển thị 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ó một thay đổi không tương thích, hệ thống bản dựng sẽ phát sinh 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 một 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 riêng 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 tĩnh bắc cầu), để tạo ra các tệp.sdump
tương ứng với từng nguồn.
Hình 1. Tạo tệp .sdump
header-abi-linker
sau đó 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 tệp đó hoặc tệp.so
tương ứng với thư viện dùng chung) để tạo ra tệp.lsdump
ghi lại 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 ra một báo cáo chênh lệch 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 chênh lệch
header-abi-dumper
Công cụ header-abi-dumper
phân tích cú pháp một tệp nguồn C/C++ và kết xuất ABI suy luận 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 xây dựng 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 | 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
ở định dạng JSON. Định dạng này không đảm bảo tính ổn định trong các bản phát hành sau này. Do đó, việc định dạng tệp .sdump
cần được coi là một chi tiết triển khai hệ thống bản 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ể 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 sau --
và phát ra 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
và các tiêu đề công khai xuất, ví dụ:
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, chỉ định truy cập, tệp tiêu đề mà loại bản ghi đó được xác định và các thuộc tính khác.pointer_types
. Tham chiếu trực tiếp/gián tiếp đến các kiểu 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 kiểu mà con trỏ trỏ đến (thông qua trườngreferenced_type
trongtype_info
). Thông tin tương tự được ghi vào tệp.sdump
cho các kiểu đủ điều kiện, kiểu C/C++ tích hợp, kiểu mảng và kiểu tham chiếu lvalue và rvalue. Thông tin như vậy cho phép so sánh đệ 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 xén của hàm, loại dữ liệu trả về, các loại tham số, bộ chỉ định quyền truy cập và các thuộc tính khác.
header-abi-linker
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 | Một 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ề một định nghĩa (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 nhau về mặt 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 một 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 được xuất.
Ví dụ: libfoo
bao gồm foo.cpp
và bar.cpp
. header-abi-linker
có thể được gọi để tạo bản 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ề đầu ra 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 cho tệp đó (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 biểu tượng do 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 hai tệp .lsdump
đại diện cho ABI của hai thư viện và tạo ra một báo cáo chênh lệch nêu rõ sự khác biệt giữa hai ABI.
Ngõ vào |
|
---|---|
Đầu ra | Báo cáo chênh lệch nêu rõ sự khác biệt trong ABI do hai thư viện dùng chung được so sánh cung cấp. |
Tệp chênh lệch ABI có đị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 của libfoo
: libfoo_old.so
và libfoo_new.so
. Trong libfoo_new.so
, trong bar_t
, bạn thay đổi loại mfoo
từ foo_t
thành foo_t *
. Vì bar_t
là một kiểu có thể truy cập, nên header-abi-diff
sẽ gắn cờ đây là một thay đổi có thể gây lỗi ABI.
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ề đầu ra 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 làm gián đoạn 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ị 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ể được diễn giải là Foo
là một hàm được xuất nhận 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 các thư viện dùng chung VNDK, bạn phải kiểm tra các tham chiếu ABI vào ${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 các 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 một thư viện VNDK hiện sẽ dẫn đến lỗi bản dựng.
Để cập nhật các 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 tham chiếu ABI libbinder
, hãy chạy:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder