應用程式二進位介面 (ABI) 穩定性是僅限架構更新的先決條件,因為供應商模組可能依附於系統分割區中的供應商原生開發套件 (VNDK) 共用程式庫。在 Android 版本中,新建立的 VNDK 共用程式庫必須與先前發布的 VNDK 共用程式庫 ABI 相容,這樣供應商模組才能使用這些程式庫,不必重新編譯,也不會發生執行階段錯誤。在 Android 版本之間,VNDK 程式庫可能會變更,且不保證 ABI。
為確保 ABI 相容性,Android 9 包含標頭 ABI 檢查工具,詳情請參閱下列章節。
關於 VNDK 和 ABI 相容性
VNDK 是一組受限的程式庫,供應商模組可連結至這些程式庫,並啟用僅限架構的更新。ABI 相容性是指新版共用程式庫是否能與動態連結至該程式庫的模組正常運作 (也就是說,是否能像舊版程式庫一樣運作)。
關於匯出的符號
匯出的符號 (也稱為全域符號) 是指符合下列所有條件的符號:
- 由共用程式庫的公開標頭匯出。
- 顯示在與共用程式庫對應的
.so
檔案.dynsym
表格中。 - 具有 WEAK 或 GLOBAL 繫結。
- 可見度為「DEFAULT」或「PROTECTED」。
- 區段索引不是 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 * 」,bar_t 會透過「foo_exported.h 」匯出。bar_t 包含 mfoo 成員 (類型為 foo_t ),透過 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. 建立差異報表
header-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 資訊。以下是 foo.sdump
由 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
包含來源檔案 foo.cpp
匯出的 ABI 資訊和公開標頭,例如:
record_types
。請參閱公用標頭中定義的結構體、聯集或類別。每種記錄類型都有欄位、大小、存取指定符、定義所在的標頭檔案,以及其他屬性的相關資訊。pointer_types
。請參閱公開標頭中匯出的記錄/函式直接/間接參照的指標型別,以及指標指向的型別 (透過type_info
中的referenced_type
欄位)。對於合格型別、內建 C/C++ 型別、陣列型別,以及左值和右值參照型別,系統會在.sdump
檔案中記錄類似資訊。這類資訊可進行遞迴差異比較。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
工具會比較兩個 .lsdump
檔案,這兩個檔案代表兩個程式庫的 ABI,並產生差異報表,說明兩個 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