應用程式二進位介面 (ABI) 穩定性是僅限架構更新的必要條件,因為供應商模組可能會依附位於系統分區的供應商原生開發套件 (VNDK) 共用資料庫。在 Android 版本中,新建的 VNDK 共用程式庫必須與先前發布的 VNDK 共用程式庫相容,以便供應商模組可與這些程式庫搭配運作,且不會發生重新編譯和執行階段錯誤。在 Android 版本之間,VNDK 程式庫可以變更,且沒有 ABI 保證。
為確保 ABI 相容性,Android 9 包含標頭 ABI 檢查工具,如以下各節所述。
關於 VNDK 和 ABI 相容性
VNDK 是一組受限制的程式庫,供應商模組可連結至這些程式庫,並啟用僅限架構的更新。ABI 相容性是指新版共用程式庫是否能與動態連結的模組正常運作 (也就是能否以舊版程式庫的方式運作)。
關於匯出的符號
「匯出的符號」 (又稱為「全域符號」) 是指符合以下所有條件的符號:
- 由共用資料庫的公用標頭匯出。
- 會顯示在
.so
檔案的.dynsym
表格中,對應至共用資料庫。 - 具有弱式或全域繫結。
- 可見度為「預設」或「受保護」。
- 區段索引並非 UNDEFINED。
- 類型為 FUNC 或 OBJECT。
共用程式庫的公開標頭定義為可透過 Android.bp
定義中 export_include_dirs
、export_header_lib_headers
、export_static_lib_headers
、export_shared_lib_headers
和 export_generated_headers
屬性,讓其他程式庫/二進位檔使用的標頭,這些屬性對應至共用程式庫的模組。
關於可到達的類型
可到達型別是指任何可透過匯出的符號直接或間接到達,並透過公開標頭匯出的 C/C++ 內建或使用者定義型別。舉例來說,libfoo.so
有 Foo
函式,這是 .dynsym
資料表中匯出的符號。libfoo.so
程式庫包含下列項目:
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" ], } |
.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
|
查看 Foo
,直接/間接可到達類型包括:
類型 | 說明 |
---|---|
bool
|
Foo 的傳回類型。 |
int
|
第一個 Foo 參數的類型。 |
bar_t *
|
第二個 Foo 參數的類型。bar_t * 會透過 foo_exported.h 匯出 bar_t 。bar_t 包含 foo_t 類型的成員 mfoo ,這是透過 foo_exported.h 匯出,因此會匯出更多類型:
不過,由於 foo_private_t 並未透過 foo_exported.h 匯出,因此無法存取。(foo_private_t * 是不可見的,因此允許對 foo_private_t 進行變更)。 |
針對可透過基本類別指定碼和範本參數可存取的類型,也可以提供類似的說明。
確保 ABI 相容性
請確保對應 Android.bp
檔案中標示 vendor_available: true
和 vndk.enabled: true
的程式庫符合 ABI 規定。例如:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
以匯出函式直接或間接存取的資料類型而言,以下對程式庫的變更會歸類為 ABI 破壞性資料:
資料類型 | 說明 |
---|---|
結構體和類別 |
|
聯合體 |
|
列舉 |
|
全域符號 |
|
* 公開和私人會員函式都不能變更或移除,因為公開內嵌函式可以參照私人會員函式。私人會員函式的符號參照可保留在呼叫端二進位檔中。從共用程式庫中變更或移除私人成員函式,可能會導致回溯不相容的二進位檔。
** 請勿變更公開或私人資料成員的偏移量,因為內嵌函式可在函式主體中參照這些資料成員。變更資料成員偏移量可能會導致回溯不相容的二進位檔。
*** 雖然這不會變更該類型的記憶體配置,但也有語意差異,可能導致程式庫無法正常運作。
使用 ABI 法規遵循工具
建構 VNDK 程式庫時,系統會將程式庫的 ABI 與所建構 VNDK 版本的對應 ABI 參照值進行比較。參考 ABI 傾印檔位於:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
舉例來說,在 API 級別 27 為 x86 建構 libfoo
時,libfoo
的推測 ABI 會與其參照項目進行比較:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
ABI 損壞錯誤
在 ABI 故障時,建構記錄會顯示警告類型警示類型以及 abi-diff 報表的路徑。舉例來說,如果 libbinder
的 ABI 發生不相容的變更,建構系統就會擲回錯誤,並顯示類似以下的訊息:
***************************************************** 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 ----
建構 VNDK 程式庫 ABI 檢查
建構 VNDK 程式庫時:
header-abi-dumper
會處理編譯用來建構 VNDK 程式庫的來源檔案 (程式庫本身的來源檔案,以及透過靜態傳遞依附元件繼承的來源檔案),產生與每個來源相對應的.sdump
檔案。
圖 1. 建立 .sdump
檔案header-abi-linker
接著會處理.sdump
檔案 (使用提供給它的版本指令碼,或與共用程式庫相對應的.so
檔案),產生.lsdump
檔案,記錄與共用程式庫相對應的所有 ABI 資訊。
圖 2. 建立 .lsdump
檔案header-abi-diff
會比較.lsdump
檔案與參考.lsdump
檔案,產生差異報告,概述兩個程式庫的 ABI 差異。
圖 3. 建立差異比較報表
標頭 -abi-dumper
header-abi-dumper
工具會剖析 C/C++ 來源檔案,並將從該來源檔案推斷的 ABI 轉儲至中繼檔案。建構系統會在所有已編譯的來源檔案上執行 header-abi-dumper
,同時建構程式庫,其中包含來自遞移依附元件的來源檔案。
輸入裝置 |
|
---|---|
輸出 | 描述來源檔案 ABI 的檔案 (例如,foo.sdump 代表 foo.cpp 的 ABI)。 |
目前 .sdump
檔案採用 JSON 格式,但無法保證在未來版本中仍能維持穩定。因此,.sdump
檔案格式應視為建構系統的實作細節。
例如,libfoo.so
有以下來源檔案 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; }
您可以使用 header-abi-dumper
產生中繼 .sdump
檔案,代表來源檔案提供的 ABI,使用方式如下:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
這個指令會指示 header-abi-dumper
使用 --
後面的編譯器標記剖析 foo.cpp
,並發出 exported
目錄中公開標頭匯出的 ABI 資訊。以下是 header-abi-dumper
產生的 foo.sdump
:
{ "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
包含來源檔案 foo.cpp
匯出的 ABI 資訊,以及公開標頭,例如:
record_types
:參照公開標頭中定義的結構、聯集或類別。每個記錄類型都包含其欄位、大小、存取指定符、定義所在的標頭檔案和其他屬性相關資訊。pointer_types
。在公開標頭中,參照已匯出記錄/函式直接/間接參照的指標類型,以及指標點的類型 (透過type_info
中的referenced_type
欄位)。類似資訊會記錄在.sdump
檔案中,用於符合條件的類型、內建 C/C++ 類型、陣列類型,以及 lvalue 和 rvalue 參照類型。這類資訊可用於遞迴差異比較。functions
。代表由公開標頭匯出的函式。這些資訊還包含函式的經過竄改的名稱、傳回類型、參數類型、存取指定符和其他屬性。
header-abi-linker
header-abi-linker
工具會將 header-abi-dumper
產生的中繼檔案做為輸入內容,然後連結這些檔案:
輸入裝置 |
|
---|---|
輸出 | 說明共用程式庫 ABI 的檔案 (舉例來說,libfoo.so.lsdump 代表 libfoo 的 ABI)。 |
這項工具會合併所有傳入的轉譯中間檔案中的類型圖表,並考量轉譯單元之間的差異,例如單一定義 (不同轉譯單元中具有相同完整名稱的使用者定義類型,可能在語意上有所不同)。接著,這項工具會剖析版本指令碼或共用程式庫的 .dynsym
表格 (.so
檔案),製作匯出符號的清單。
例如 libfoo
包含 foo.cpp
和 bar.cpp
。您可以叫用 header-abi-linker
來建立 libfoo
的完整連結 ABI 傾印內容,如下所示:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
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" : [] }
header-abi-linker
工具:
- 連結提供給它的
.sdump
檔案 (foo.sdump
和bar.sdump
),篩除目錄exported
中標頭中未出現的 ABI 資訊。 - 剖析
libfoo.so
,並收集透過.dynsym
資料表匯出的程式庫符號相關資訊。 - 新增
_Z3FooiP3bar
和_Z6FooBadiP3foo
。
libfoo.so.lsdump
是 libfoo.so
最終產生的 ABI 傾印檔案。
header-abi-diff
header-abi-diff
工具會比較兩個代表兩個程式庫 ABI 的 .lsdump
檔案,並產生差異報告,說明兩個 ABI 之間的差異。
輸入裝置 |
|
---|---|
輸出 | 差異報告,指出兩個比較中的共用程式庫提供的 ABI 差異。 |
ABI 差異檔案採用 protobuf 文字格式。格式可能會在日後的版本中有所變動。
舉例來說,您有兩個 libfoo
版本:libfoo_old.so
和 libfoo_new.so
。在 libfoo_new.so
的 bar_t
中,您將 mfoo
的類型從 foo_t
變更為 foo_t *
。由於 bar_t
是可連線的類型,因此應由 header-abi-diff
將其標記為 ABI 破壞性變更。
如要執行 header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
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
包含 libfoo
中所有 ABI 破壞變更的報告。record_type_diffs
訊息表示記錄已變更,並列出不相容的變更,包括:
- 記錄的大小從
24
位元組變更為8
位元組。 mfoo
的欄位類型從foo
變更為foo *
(所有 typedef 都會移除)。
type_stack
欄位會指出 header-abi-diff
如何取得已變更的類型 (bar
)。這個欄位可能會解讀為 Foo
是匯出的函式,會將 bar *
做為參數,並指向已匯出及變更的 bar
。
強制執行 ABI 和 API
如要強制執行 VNDK 共用程式庫的 ABI 和 API,必須將 ABI 參照項目簽入 ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
。如要建立這些參照,請執行下列指令:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
建立參照後,如果對原始碼進行任何變更,導致 VNDK 程式庫出現不相容的 ABI/API 變更,就會導致建構錯誤。
如要更新特定程式庫的 ABI 參照,請執行下列指令:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
例如,如要更新 libbinder
ABI 參照,請執行:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder