應用程式二進位介面 (ABI) 穩定性是僅框架更新的先決條件,因為供應商模組可能依賴駐留在系統分區的供應商本機開發工具包 (VNDK) 共用程式庫。在 Android 版本中,新建的 VNDK 共享庫必須與先前發布的 VNDK 共享庫 ABI 相容,以便供應商模組可以使用這些庫,而無需重新編譯,也不會出現運行時錯誤。在 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 * , bar_t 透過foo_exported.h 導出。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
檔案進行比較,以產生一份 diff 報告,概述兩個函式庫的 ABI 差異。圖 3.建立差異報告
標頭 abi 轉儲器
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
欄位)。限定類型、內建 C/C++ 類型、陣列類型以及左值和右值參考類型的類似資訊記錄在.sdump
檔案中。此類資訊允許遞歸比較。 -
functions
。表示由公共標頭導出的函數。它們還具有有關函數的修飾名稱、傳回類型、參數類型、存取說明符和其他屬性的資訊。
頭 abi 連結器
header-abi-linker
工具將header-abi-dumper
產生的中間檔案作為輸入,然後連結這些檔案:
輸入 |
|
---|---|
輸出 | 描述共用程式庫 ABI 的檔案(例如, libfoo.so.lsdump 代表libfoo 的 ABI)。 |
此工具合併提供給它的所有中間文件中的類型圖,同時考慮到翻譯單元之間的單一定義(具有相同完全限定名稱的不同翻譯單元中的使用者定義類型,可能在語義上不同)差異。然後,該工具會解析版本腳本或共享庫( .so
檔案)的.dynsym
表,以產生導出符號的清單。
例如, 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
),過濾掉以下目錄中的標頭中不存在的 ABI 資訊:exported
。 - 解析
libfoo.so
,並透過其.dynsym
表收集有關庫導出的符號的資訊。 - 新增
_Z3FooiP3bar
和_Z6FooBadiP3foo
。
libfoo.so.lsdump
是libfoo.so
最終產生的 ABI 轉儲。
標頭 abi-diff
header-abi-diff
工具比較代表兩個函式庫的 ABI 的兩個.lsdump
文件,並產生一份 diff 報告,說明兩個 ABI 之間的差異。
輸入 |
|
---|---|
輸出 | 一份 diff 報告,說明所比較的兩個共享庫提供的 ABI 的差異。 |
ABI diff 檔案採用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