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 Bộ công cụ phát triển gốc của nhà cung cấp (VNK) 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ẻ VNĐK mới được xây dựng phải tương thích ABI với các thư viện chia sẻ VNĐK đã phát hành trước đó để các mô-đun của 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 có lỗi thời gian chạy. Giữa các bản phát hành Android, thư viện VNĐK có thể được thay đổi và không có đảm bảo ABI.
Để giúp đảm bảo khả năng tương thích ABI, Android 9 bao gồm trình kiểm tra ABI tiêu đề, như được mô tả trong các phần sau.
Về việc tuân thủ VNDK và ABI
VNĐK là một bộ thư viện hạn chế mà các mô-đun của nhà cung cấp có thể liên kết tới và cho phép cập nhật chỉ dành cho 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 mô-đun được liên kết động với 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 đã xuất
Biểu tượng được xuất (còn được gọi là biểu tượng chung ) đề cập đến biểu tượng đáp ứng tất cả các điều sau:
- Được xuất theo tiêu đề công khai của thư viện dùng chung.
- Xuất hiện trong bảng
.dynsym
của file.so
tương ứng với thư viện dùng chung. - Có ràng buộc YẾU hoặc TOÀN CẦU.
- Khả năng hiển thị là MẶC ĐỊNH hoặc ĐƯỢC BẢO VỆ.
- Chỉ mục phần không được xác định.
- Loại là FUNC hoặc ĐỐI TƯỢNG.
Các tiêu đề công khai của thư viện dùng chung được định nghĩa là các tiêu đề có sẵn cho các thư viện/tệp nhị phân khác thông qua các export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
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 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à biểu tượng được xuất trong bảng .dynsym
. Thư viện libfoo.so
bao gồm:
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:
Kiểu | Sự miêu tả |
---|---|
bool | Kiể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 cách bar_t * , bar_t được xuất qua foo_exported.h .bar_t chứa thành viên mfoo , thuộc loại foo_t , được xuất qua foo_exported.h , dẫn đến nhiều loại được xuất hơn:
Tuy nhiên, foo_private_t KHÔNG thể truy cập được vì nó không được xuất qua foo_exported.h . ( foo_private_t * không rõ ràng, do đó những thay đổi được thực hiện đối với foo_private_t được cho phép.) |
Một lời giải thích tương tự cũng có thể được đưa ra cho các loại có thể truy cập thông qua các bộ xác định lớp cơ sở và các tham số mẫu.
Đảm bảo tuân thủ ABI
Phải đảm bảo tuân thủ ABI đối với 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 có thể truy cập trực tiếp hoặc gián tiếp bằng hàm xuất, những thay đổi sau đây đối với thư viện được phân loại là vi phạm ABI:
Loại dữ liệu | Sự miêu tả |
---|---|
Cấu trúc và lớp học |
|
Công đoàn |
|
Bảng liệt kê |
|
Biểu tượng toàn cầu |
|
* Không được thay đổi hoặc xóa cả hàm thành viên công khai và 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ư. Tham chiếu biểu tượng đến các hàm thành viên riêng tư có thể được lưu giữ trong tệp nhị phân của người gọi. Việc thay đổi hoặc xóa các hàm thành viên riêng 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.
** Không được thay đổi độ lệch đối với các thành viê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 viên dữ liệu này trong nội dung hàm của chúng. Việc thay đổi độ lệch thành viê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ó những khác biệt về ngữ nghĩa có thể dẫn đế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 thư viện VNĐK được xây dựng, ABI của thư viện được so sánh với tham chiếu ABI tương ứng cho phiên bản VNĐK đang được xây dựng. Các bãi chứa ABI tham chiếu được đặt tại:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Ví dụ: khi xây dựng libfoo
cho x86 ở API cấp 27, ABI suy ra của libfoo
được so sánh với tham chiếu của nó tại:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Lỗi gãy ABI
Khi xảy ra sự cố ABI, nhật ký bản dựng hiển thị các cảnh báo có 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ẽ đưa ra lỗi với 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 thư viện VNĐK kiểm tra ABI
Khi thư viện VNĐK được xây dựng:
-
header-abi-dumper
xử lý các file nguồn được biên dịch để xây dựng thư viện VNĐK (các file nguồn riêng của thư viện cũng như các file nguồn được kế thừa thông qua các phụ thuộc bắc cầu tĩnh), để cho ra các file.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
(sử dụng tập lệnh phiên bản được cung cấp cho nó hoặc tệp.so
tương ứng với thư viện dùng chung) để tạo 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
tham chiếu để tạo báo cáo khác biệt 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 khác biệt
tiêu đề-abi-dumper
Công cụ header-abi-dumper
phân tích cú pháp tệp nguồn C/C++ và chuyển ABI được 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 được 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.
Đầu 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
có định dạng JSON, không đảm bảo tính ổn định trong các bản phát hành trong tương lai. Do đó, định dạng tệp .sdump
phải đượ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ể sử dụng header-abi-dumper
để tạo 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 sau --
và phát ra thông tin ABI được xuất bởi các tiêu đề công khai trong thư mục exported
. Sau đâ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 được xuất bởi tệp nguồn foo.cpp
và các tiêu đề công khai, ví dụ:
-
record_types
. Tham khảo các cấu trúc, liên kết hoặc các lớp được xác định trong các tiêu đề chung. Mỗi loại bản ghi có thông tin về các trường, kích thước, bộ chỉ định truy cập, tệp tiêu đề được xác định và các thuộc tính khác. -
pointer_types
. Tham khảo 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 đề chung, cùng với loại mà con trỏ trỏ tới (thông qua trườngreferenced_type
trongtype_info
). Thông tin tương tự được ghi vào tệp.sdump
cho các loại đủ điều kiện, loại C/C++ tích hợp sẵn, loại mảng và các loại tham chiếu lvalue và rvalue. Thông tin như vậy cho phép khác biệt đệ quy. -
functions
. Đại diện cho các hàm được xuất bởi các tiêu đề công khai. Chúng cũng có thông tin về tên được đọc sai của hàm, kiểu trả về, kiểu tham số, bộ chỉ định truy cập và các thuộc tính khác.
tiêu đề-abi-liên kết
Công cụ header-abi-linker
lấy các tệp trung gian do header-abi-dumper
tạo ra làm đầu vào rồi liên kết các tệp đó:
Đầu vào |
|
---|---|
đầu ra | Một tệp mô tả ABI của 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 nó, có tính đến sự khác biệt về một định nghĩa (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 phân tích 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 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
Đầu ra lệnh ví dụ 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
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
của nó. - Thêm
_Z3FooiP3bar
và_Z6FooBadiP3foo
.
libfoo.so.lsdump
là kết xuất ABI được tạo cuối cùng của libfoo.so
.
tiêu đề-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 khác biệt nêu rõ sự khác biệt giữa hai ABI.
Đầu vào |
|
---|---|
đầu ra | Một báo cáo khác nêu rõ sự khác biệt trong ABI do hai thư viện dùng chung cung cấp được so sánh. |
Tệp khác biệt ABI có định dạng văn bản protobuf . Định dạng này có thể thay đổi trong các phiên bản tương lai.
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 thay đổi loại mfoo
từ foo_t
thành foo_t *
. Vì bar_t
là loại có thể truy cập nên điều này phải được gắn cờ là thay đổi vi phạm ABI bởi header-abi-diff
.
Để 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
Đầu ra lệnh ví dụ 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 vi phạm ABI trong libfoo
. Thông báo record_type_diffs
cho biết một bản ghi đã thay đổi và liệt kê những 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
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
tiếp cận loại đã thay đổi ( bar
). Trường này có thể được hiểu là Foo
là một hàm được xuất lấy bar *
làm tham số, trỏ đến bar
, đã được xuất và thay đổi.
Thực thi ABI/API
Để thực thi ABI/API của thư viện chia sẻ VNDK, các tham chiếu ABI phải được kiểm tra trong ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
. Để tạo các tài liệu tham khảo 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, bất kỳ thay đổi nào được thực hiện đố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 dẫn đến lỗi xây dựng.
Để cập nhật 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