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
.dynsymcủa tệp.sotươ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-dumperxử 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.sdumptương ứng với từng nguồn.
Hình 1. Tạo tệp .sdumpheader-abi-linkersau đó 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.sotương ứng với thư viện dùng chung) để tạo ra tệp.lsdumpghi 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 .lsdumpheader-abi-diffso sánh tệp.lsdumpvớ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_typetrongtype_info). Thông tin tương tự được ghi vào tệp.sdumpcho 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.sdumpvà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.sovà 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
_Z3FooiP3barvà_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ừ
24byte thành8byte. - Loại trường của
mfoothay đổi từfoothà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