您可以使用 Android 11 以上版本提供的應用程式二進位介面 (ABI) 監控工具,穩定 Android 核心的內核心 ABI。這項工具會收集並比較現有核心二進位檔 (vmlinux
+ GKI 模組) 的 ABI 表示法。這些 ABI 代表項目是 .stg
檔案和符號清單。表示法提供檢視區塊的介面稱為「核心模組介面」(KMI)。您可以使用工具追蹤及減輕 KMI 變更的影響。
ABI 監控工具在 AOSP 中開發,並使用 STG (或 Android 13 以下版本的 libabigail
) 生成及比較表示法。
本頁說明工具、收集及分析 ABI 表示法的程序,以及如何使用這類表示法,確保核心內 ABI 的穩定性。本頁面也提供資訊,說明如何為 Android 核心貢獻變更。
程序
分析核心的 ABI 需要多個步驟,其中大部分可以自動化:
下列操作說明適用於任何可使用支援的工具鍊 (例如預先建構的 Clang 工具鍊) 建構的核心。repo manifests
適用於所有 Android 通用核心分支版本和多個裝置專屬核心,可驗證您在建構核心發行版本以供分析時,是否使用正確的工具鍊。
符號清單
KMI 不包含核心中的所有符號,甚至不包含所有匯出的 30,000 多個符號。而是明確列在核心樹狀結構中公開維護的一組符號清單檔案中 (Android 15 以下版本為 gki/{ARCH}/symbols/*
或 android/abi_gki_{ARCH}_*
)。所有符號清單檔案中的所有符號聯集,定義了維持穩定的 KMI 符號集。符號清單檔案範例為 gki/aarch64/symbols/db845c
,其中宣告了 DragonBoard 845c 所需的符號。
只有符號清單中列出的符號,以及相關結構和定義,才屬於 KMI。如果沒有所需的符號,可以發布符號清單的變更。新介面加入符號清單並成為 KMI 說明的一部分後,就會維持穩定狀態,且分支版本凍結後,不得從符號清單中移除或修改。
每個 Android Common Kernel (ACK) KMI 核心分支都有自己的符號清單。不同 KMI 核心分支之間不會嘗試提供 ABI 穩定性。舉例來說,android12-5.10
的 KMI 完全獨立於 android13-5.10
的 KMI。
ABI 工具會使用 KMI 符號清單,限制必須監控穩定性的介面。供應商應提交及更新自己的符號清單,以驗證所依賴的介面是否維持 ABI 相容性。舉例來說,如要查看 android16-6.12
核心的符號清單,請參閱 https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols
符號清單包含據報特定供應商或裝置需要使用的符號。工具使用的完整清單是所有 KMI 符號清單檔案的聯集。ABI 工具會判斷每個符號的詳細資料,包括函式簽章和巢狀資料結構。
KMI 凍結後,現有的 KMI 介面就無法再變更,不過,只要新增的符號不會影響現有 ABI 的穩定性,供應商隨時都能將符號新增至 KMI。只要 KMI 符號清單引用新加入的符號,這些符號就會維持穩定狀態。除非可以確認沒有任何裝置依附於該符號,否則不應從核心的清單中移除符號。
如要為裝置產生 KMI 符號清單,請按照「如何使用符號清單」一文中的操作說明進行。許多合作夥伴會為每個 ACK 提交一份符號清單,但這並非必要條件。 如果這樣有助於維護,您可以提交多個符號清單。
延長 KMI
KMI 符號和相關結構會維持穩定 (也就是說,如果核心中的 KMI 已凍結,就無法接受會破壞穩定介面的變更),但 GKI 核心仍可擴充,因此在 KMI 凍結前,今年稍晚出貨的裝置不必定義所有依附元件。如要擴充 KMI,您可以為新匯出或現有的匯出核心函式,在 KMI 中新增符號,即使 KMI 已凍結也沒問題。如果新核心修補程式不會破壞 KMI,也可能會被接受。
關於 KMI 中斷
核心有來源,而二進位檔是從這些來源建構而來。
受 ABI 監控的 Kernel 分支包含目前 GKI ABI 的 ABI 表示法 (以 .stg
檔案的形式)。建構二進位檔 (vmlinux
、Image
和任何 GKI 模組) 後,即可從二進位檔中擷取 ABI 表示法。對核心來源檔案所做的任何變更,都可能影響二進位檔,進而影響擷取的 .stg
。ABI 遵循情況分析會比較已提交的 .stg
檔案與從建構構件中擷取的檔案,並在 Gerrit 中對變更設定 Lint-1 標籤 (如果發現語意差異)。
處理 ABI 中斷
舉例來說,下列修補程式會造成非常明顯的 ABI 損壞:
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
ANDROID_KABI_RESERVE(1);
} __randomize_layout;
+ int tickle_count;
/*
* The mm_cpumask needs to be at the end of mm_struct, because it
* is dynamically sized based on nr_cpu_ids.
套用這個修補程式後,如果您執行建構 ABI,工具會以非零錯誤碼結束,並回報類似以下的 ABI 差異:
function symbol 'struct block_device* I_BDEV(struct inode*)' changed
CRC changed from 0x8d400dbd to 0xabfc92ad
function symbol 'void* PDE_DATA(const struct inode*)' changed
CRC changed from 0xc3c38b5c to 0x7ad96c0d
function symbol 'void __ClearPageMovable(struct page*)' changed
CRC changed from 0xf489e5e8 to 0x92bd005e
... 4492 omitted; 4495 symbols have only CRC changes
type 'struct mm_struct' changed
byte size changed from 992 to 1000
member 'int tickle_count' was added
member 'unsigned long cpu_bitmap[0]' changed
offset changed by 64
在建構期間偵測到 ABI 差異
最常見的錯誤原因是驅動程式使用了核心中的新符號,但符號清單中沒有該符號。
如果符號未列在符號清單中,請先確認符號是否已透過 EXPORT_SYMBOL_GPL(symbol_name)
匯出,然後更新符號清單和 ABI 表示法。舉例來說,下列變更會將新的 Incremental FS 功能新增至 android-12-5.10
分支,包括更新符號清單和 ABI 表示法。
- 如需功能變更範例,請參閱 aosp/1345659。
- 符號清單範例位於 aosp/1346742。
- ABI 表示法變更範例位於 aosp/1349377。
如果符號已匯出 (由您匯出或先前已匯出),但沒有其他驅動程式使用該符號,您可能會收到類似下列內容的建構錯誤。
Comparing the KMI and the symbol lists:
+ build/abi/compare_to_symbol_list out/$BRANCH/common/Module.symvers out/$BRANCH/common/abi_symbollist.raw
ERROR: Differences between ksymtab and symbol list detected!
Symbols missing from ksymtab:
Symbols missing from symbol list:
- simple_strtoull
如要解決這個問題,請更新核心和 ACK 中的 KMI 符號清單 (請參閱「更新 ABI 表示法」)。如需更新符號清單和 ACK 中 ABI 表示法的範例,請參閱 aosp/1367601。
解決核心 ABI 中斷問題
您可以重構程式碼,避免變更 ABI,或更新 ABI 表示法,處理核心 ABI 中斷問題。請參考下表,找出最適合您情況的做法。
圖 1. 解決 ABI 中斷問題
重構程式碼,避免 ABI 變更
請盡量避免修改現有 ABI。在許多情況下,您可以重構程式碼,移除會影響 ABI 的變更。
重構結構欄位變更。如果變更會修改偵錯功能的 ABI,請在欄位 (結構體和來源參照中) 周圍新增
#ifdef
,並確保用於#ifdef
的CONFIG
已針對正式版 defconfig 和gki_defconfig
停用。如需瞭解如何將偵錯設定新增至結構體,且不會破壞 ABI 的範例,請參閱這個修補程式集。重構功能,避免變更核心核心。如果需要將新功能新增至 ACK,以支援合作夥伴模組,請嘗試重構變更的 ABI 部分,避免修改核心 ABI。如要瞭解如何使用現有核心 ABI 新增其他功能,而不變更核心 ABI,請參閱 aosp/1312213。
修正 Android Gerrit 上無效的 ABI
如果不是有意中斷核心 ABI,則需要使用 ABI 監控工具提供的指引進行調查。最常見的故障原因包括資料結構變更和相關符號 CRC 變更,或是因設定選項變更而導致上述情況。首先,請解決工具發現的問題。
您可以在本機重現 ABI 發現項目,詳情請參閱「建構核心及其 ABI 表示法」。
關於 Lint-1 標籤
如果您將變更上傳至含有凍結或最終 KMI 的分支,變更必須通過 ABI 相容性分析,確保 ABI 代表的變更反映實際 ABI,且不含任何不相容項目 (符號移除或型別變更)。
這些 ABI 分析可能會設定 Lint-1 標籤,並封鎖變更提交作業,直到所有問題都解決或標籤遭到覆寫為止。
更新核心 ABI
如果無法避免修改 ABI,則必須將程式碼變更、ABI 表示法和符號清單套用至 ACK。如要讓 Lint 移除 -1,且不破壞 GKI 相容性,請按照下列步驟操作:
等待接收修補程式集的 Code-Review +2。
合併程式碼變更和 ABI 更新變更。
將 ABI 程式碼變更上傳至 ACK
更新 ACK ABI 的方式取決於所做的變更類型。
如果 ABI 變更與影響 CTS 或 VTS 測試的功能相關,通常可以將變更直接挑選至 ACK。舉例來說:
- 音訊功能需要 aosp/1289677 才能運作。
- USB 運作需要 aosp/1295945。
如果 ABI 變更適用於可與 ACK 共用的功能,則可直接將該變更挑選至 ACK。舉例來說,下列變更不需要 CTS 或 VTS 測試,但可與 ACK 共用:
- aosp/1250412 是熱能功能變更。
- aosp/1288857
是
EXPORT_SYMBOL_GPL
變更。
如果 ABI 變更導入的新功能不需要納入 ACK,您可以使用存根將符號導入 ACK,詳情請參閱下一節。
使用 ACK 的存根
只有在核心核心變更對 ACK 沒有好處 (例如效能和電力變更) 時,才需要存根。以下清單詳細列出 ACK 中 GKI 的存根和部分 Cherry-pick 範例。
核心隔離功能存根 (aosp/1284493)。ACK 中不一定要有這些功能,但 ACK 中必須有這些符號,模組才能使用。
供應商模組的預留位置符號 (aosp/1288860)。
僅限 ABI 的每程序
mm
事件追蹤功能 Cherry-pick (aosp/1288454)。原始修補程式已透過 Cherry-pick 方式套用至 ACK,然後經過修剪,只保留解決task_struct
和mm_event_count
ABI 差異的必要變更。這個修補程式也會更新mm_event_type
列舉,以包含最終成員。部分熱結構 ABI 變更的 Cherry-pick,需要的不只是新增 ABI 欄位。
修補程式 aosp/1255544 解決了合作夥伴核心和 ACK 之間的 ABI 差異。
修補程式 aosp/1291018 修正了先前修補程式的 GKI 測試期間發現的功能問題。 修正內容包括初始化感應器參數結構,以便將多個熱區登錄至單一感應器。
CONFIG_NL80211_TESTMODE
ABI 異動 (aosp/1344321)。這個修補程式為 ABI 新增必要的結構體變更,並確保額外欄位不會造成功能差異,讓合作夥伴能在生產核心中加入CONFIG_NL80211_TESTMODE
,同時維持 GKI 相容性。
在執行階段強制執行 KMI
GKI 核心使用 TRIM_UNUSED_KSYMS=y
和 UNUSED_KSYMS_WHITELIST=<union
of all symbol lists>
設定選項,將匯出的符號 (例如使用 EXPORT_SYMBOL_GPL()
匯出的符號) 限制為符號清單中列出的符號。所有其他符號都不會匯出,且系統會拒絕載入需要未匯出符號的模組。這項限制會在建構時強制執行,並標示缺少的項目。
為進行開發,您可以使用不含符號修剪的 GKI 核心建構版本 (也就是說,可以使用所有通常匯出的符號)。如要尋找這些版本,請前往 ci.android.com 尋找 kernel_debug_aarch64
版本。
使用模組版本控管強制執行 KMI
通用核心映像檔 (GKI) 核心會使用模組版本控管 (CONFIG_MODVERSIONS
) 做為額外措施,在執行階段強制執行 KMI 規範。如果模組的預期 KMI 與 vmlinux
KMI 不符,模組版本控管可能會在模組載入時導致循環冗餘檢查 (CRC) 不符失敗。舉例來說,以下是因符號 module_layout()
的 CRC 不符,而導致模組載入時發生的典型失敗:
init: Loading module /lib/modules/kernel/.../XXX.ko with args ""
XXX: disagrees about version of symbol module_layout
init: Failed to insmod '/lib/modules/kernel/.../XXX.ko' with args ''
模組版本管理功能用途
模組版本控管有下列好處:
模組版本控管會偵測資料結構瀏覽權限的變更。如果模組變更不透明的資料結構 (即不屬於 KMI 的資料結構),結構日後變更時就會中斷。
舉例來說,請考慮
struct device
中的fwnode
欄位。這個欄位「必須」對模組不透明,這樣模組才無法變更device->fw_node
的欄位,或對其大小做出假設。不過,如果模組包含
<linux/fwnode.h>
(直接或間接),則struct device
中的fwnode
欄位對該模組而言就不再是不透明。模組隨後可以變更device->fwnode->dev
或device->fwnode->ops
。這個情況會造成一些問題,原因如下:這可能會破壞核心核心程式碼對內部資料結構所做的假設。
如果日後的核心更新變更了
struct fwnode_handle
(fwnode
的資料型別),模組就無法再搭配新核心運作。此外,由於模組直接操控內部資料結構,因此stgdiff
不會顯示任何差異,因為只有檢查二進位表示法,無法擷取這類操控行為。
如果新核心不相容,且在稍後載入時,系統會將目前的模組視為 KMI 不相容。模組版本控管會新增執行階段檢查,避免意外載入與核心不相容的 KMI 模組。這項檢查可避免難以偵錯的執行階段問題和核心當機,這些問題可能源自 KMI 中未偵測到的不相容性。
啟用模組版本控管功能即可避免上述所有問題。
檢查 CRC 不符情形,不必啟動裝置
stgdiff
會比較並回報核心之間的 CRC 不符情形,以及其他 ABI 差異。
此外,啟用 CONFIG_MODVERSIONS
的完整核心建構作業會產生 Module.symvers
檔案,這是正常建構程序的一部分。這個檔案的每一行都代表核心 (vmlinux
) 和模組匯出的符號。每一行都包含 CRC 值、符號名稱、符號命名空間、匯出符號的 vmlinux
或模組名稱,以及匯出類型 (例如 EXPORT_SYMBOL
與 EXPORT_SYMBOL_GPL
)。
您可以比較 GKI 建構版本和您建構版本之間的 Module.symvers
檔案,檢查 vmlinux
匯出的符號是否有任何 CRC 差異。如果 vmlinux
匯出的任何符號有 CRC 值差異,且該符號是由您在裝置中載入的其中一個模組使用,則該模組不會載入。
如果您沒有所有建構構件,但有 GKI 核心和您核心的 vmlinux
檔案,可以對兩個核心執行下列指令,並比較輸出內容,藉此比較特定符號的 CRC 值:
nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>
舉例來說,下列指令會檢查 module_layout
符號的 CRC 值:
nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout
解決 CRC 不符問題
載入模組時發生 CRC 不符問題,請按照下列步驟解決:
使用
--kbuild_symtypes
選項建構 GKI 核心和裝置核心,如下列指令所示:tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist
這個指令會為每個
.o
檔案產生.symtypes
檔案。詳情請參閱 Kleaf 中的KBUILD_SYMTYPES
。如果是 Android 13 以下版本,請在用於建構核心的指令前面加上
KBUILD_SYMTYPES=1
,建構 GKI 核心和裝置核心,如下列指令所示:KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
使用
build_abi.sh,
時,系統會隱含設定KBUILD_SYMTYPES=1
旗標。使用下列指令,找出匯出 CRC 不符符號的
.c
檔案:git -C common grep EXPORT_SYMBOL.*module_layout kernel/module/version.c:EXPORT_SYMBOL(module_layout);
.c
檔案在 GKI 中有對應的.symtypes
檔案,以及裝置核心建構構件。使用下列指令找出.symtypes
檔案:cd bazel-bin/common/kernel_aarch64/symtypes ls -1 kernel/module/version.symtypes
在 Android 13 以下版本中,使用舊版建構指令碼時,位置可能為
out/$BRANCH/common
或out_abi/$BRANCH/common
。每個
.symtypes
檔案都是純文字檔案,包含型別和符號說明:每行都採用
key description
格式,說明可以參照同一檔案中的其他鍵。[s|u|e|t]#foo
等按鍵是指[struct|union|enum|typedef] foo
。 例如:t#bool typedef _Bool bool
沒有
x#
前置字串的鍵只是符號名稱。例如:find_module s#module * find_module ( const char * )
比較這兩個檔案,並修正所有差異。
建議您在發生問題的變更前,以及發生問題的變更時,分別產生 symtypes
。儲存所有檔案後,即可大量比較檔案。
例如:
diff -r -N -U0 good bad
否則,只要比較感興趣的特定檔案即可。
案例 1:資料類型瀏覽權限造成的差異
新的 #include
可將新的型別定義 (例如 struct foo
) 提取至來源檔案。在這些情況下,對應 .symtypes
檔案中的說明會從空白的 structure_type foo { }
變更為完整定義。
這會影響 .symtypes
檔案中所有符號的 CRC,這些符號的說明直接或間接依附於型別定義。
舉例來說,在核心的 include/linux/device.h
檔案中加入下列程式碼,會導致 CRC 不符,其中一個是 module_layout()
:
#include <linux/fwnode.h>
比較該符號的 module/version.symtypes
,會發現以下差異:
$ diff -u <GKI>/kernel/module/version.symtypes <your kernel>/kernel/module/version.symtypes
--- <GKI>/kernel/module/version.symtypes
+++ <your kernel>/kernel/module/version.symtypes
@@ -334,12 +334,15 @@
...
-s#fwnode_handle structure_type fwnode_handle { }
+s#fwnode_reference_args structure_type fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
...
如果 GKI 核心具有完整型別定義,但您的核心缺少該定義 (機率很低),請將最新的 Android Common Kernel 合併至您的核心,確保您使用的是最新的 GKI 核心基礎。
在大多數情況下,GKI 核心缺少 .symtypes
中的完整型別定義,但您的核心有這些定義,因為有額外的 #include
指令。
Android 16 以上版本的解決方案
確認受影響的來源檔案包含 Android KABI 穩定性標頭:
#include <linux/android_kabi.h>
針對每個受影響的型別,在受影響的來源檔案中,於全域範圍新增 ANDROID_KABI_DECLONLY(name);
。
舉例來說,如果 symtypes
差異如下:
--- good/drivers/android/vendor_hooks.symtypes
+++ bad/drivers/android/vendor_hooks.symtypes
@@ -1051 +1051,2 @@
-s#ubuf_info structure_type ubuf_info { }
+s#ubuf_info structure_type ubuf_info { member pointer_type { const_type { s#ubuf_info_ops } } ops data_member_location(0) , member t#refcount_t refcnt data_member_location(8) , member t#u8 flags data_member_location(12) } byte_size(16)
+s#ubuf_info_ops structure_type ubuf_info_ops { member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } , formal_parameter t#bool ) -> base_type void } complete data_member_location(0) , member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } ) -> base_type int byte_size(4) encoding(5) } link_skb data_member_location(8) } byte_size(16)
問題在於 struct ubuf_info
現在在 symtypes
中有完整定義。解決方法是在 drivers/android/vendor_hooks.c
中新增一行:
ANDROID_KABI_DECLONLY(ubuf_info);
這會指示 gendwarfksyms
將具名型別視為檔案中未定義的型別。
更複雜的情況是,新的 #include
本身位於標頭檔案中。在這種情況下,您可能需要在來源檔案中分配不同的 ANDROID_KABI_DECLONLY
巨集調用集,間接引入額外的型別定義,因為部分型別定義可能已存在。
為方便閱讀,請將這類巨集調用放在來源檔案開頭附近。
Android 15 以下版本的解決方法
通常只要從 genksyms
隱藏新的 #include
即可修正問題。
#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif
否則,如要找出造成差異的 #include
,請按照下列步驟操作:
開啟定義符號或資料類型的標頭檔案,這些符號或資料類型有此差異。例如,編輯
struct fwnode_handle
的include/linux/fwnode.h
。在標頭檔案頂端新增下列程式碼:
#ifdef CRC_CATCH #error "Included from here" #endif
在 CRC 不符的模組
.c
檔案中,將下列內容新增為第一行,放在任何#include
行之前。#define CRC_CATCH 1
編譯模組。產生的建構時間錯誤會顯示導致 CRC 不相符的標頭檔案
#include
鏈結。例如:In file included from .../drivers/clk/XXX.c:16:` In file included from .../include/linux/of_device.h:5: In file included from .../include/linux/cpu.h:17: In file included from .../include/linux/node.h:18: .../include/linux/device.h:16:2: error: "Included from here" #error "Included from here"
這個
#include
鏈結中的其中一個連結是因核心變更而產生,但 GKI 核心中缺少該連結。
案例 2:因資料類型變更而出現差異
如果符號或資料類型的 CRC 不符並非因顯示狀態差異所致,而是因為資料類型本身有實際變更 (新增、移除或變更)。
舉例來說,在核心中進行下列變更會導致多個 CRC 不符,因為許多符號會間接受到這類變更的影響:
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -259,7 +259,7 @@ struct iommu_ops {
void (*iotlb_sync)(struct iommu_domain *domain);
phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
- dma_addr_t iova);
+ dma_addr_t iova, unsigned long trans_flag);
int (*add_device)(struct device *dev);
void (*remove_device)(struct device *dev);
struct iommu_group *(*device_group)(struct device *dev);
其中一個 CRC 不符的項目是 devm_of_platform_populate()
。
如果比較該符號的 .symtypes
檔案,可能會看到類似下列的內容:
$ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
--- <GKI>/drivers/of/platform.symtypes
+++ <your kernel>/drivers/of/platform.symtypes
@@ -399,7 +399,7 @@
...
-s#iommu_ops structure_type iommu_ops { ... ; t#phy
s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
( * add_device ) ( s#device * ) ; ...
+s#iommu_ops structure_type iommu_ops { ... ; t#phy
s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...
如要找出變更的類型,請按照下列步驟操作:
在原始碼中找出符號的定義 (通常位於
.h
檔案中)。- 如要瞭解核心和 GKI 核心之間的符號差異,請執行下列指令來找出提交內容:
git blame
- 如要刪除符號 (在樹狀結構中刪除符號,同時也想在其他樹狀結構中刪除),請找出刪除該行的變更。在刪除行的樹狀結構中,使用下列指令:
git log -S "copy paste of deleted line/word" -- <file where it was deleted>
查看傳回的提交清單,找出變更或刪除作業。第一個提交內容可能就是您要找的內容。如果不是,請瀏覽清單,直到找到該提交內容為止。
找出提交內容後,請在核心中還原該內容,或更新該內容以抑制 CRC 變更,然後上傳至 ACK 並合併。 每個殘餘 ABI 中斷都需要經過安全審查,必要時可記錄允許的中斷。
偏好使用現有邊框間距
GKI 中的部分結構會經過填補,以便擴充,不會中斷現有的供應商模組。舉例來說,如果上游提交 (例如) 將成員新增至這類結構,則可能可以變更該結構,改為使用部分填補。這項變更隨後會從 CRC 計算中隱藏。
標準化的自我記錄巨集 ANDROID_KABI_RESERVE
會保留 u64
價值的 (對齊) 空間。用來取代成員聲明。
例如:
struct data {
u64 handle;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
可使用填補內容,不會影響符號 CRC,ANDROID_KABI_USE
(或 ANDROID_KABI_USE2
或其他可能定義的變體)。
成員 sekret
可供使用,就像是直接宣告一樣,但巨集實際上會展開為包含 sekret
的匿名聯集成員,以及 gendwarfksyms
用於維持符號型別穩定性的項目。
struct data {
u64 handle;
ANDROID_KABI_USE(1, void *sekret);
ANDROID_KABI_RESERVE(2);
};
Android 16 以上版本的解決方案
CRC 是由 gendwarfksyms
計算,使用 DWARF 除錯資訊,因此支援 C 和 Rust 類型。解決方式會因變更類型而異。以下是幾個例子。
新增或修改列舉值
有時會新增列舉值,有時 MAX
或類似的列舉值也會受到影響。如果這些變更不會「逸出」GKI,或是我們能確定供應商模組不會在意這些值,那麼這些變更就是安全的。
例如:
enum outcome {
SUCCESS,
FAILURE,
RETRY,
+ TRY_HARDER,
OUTCOME_LIMIT
};
在全域範圍中透過巨集調用,可以從 CRC 計算中隱藏 TRY_HARDER
的新增內容和 OUTCOME_LIMIT
的變更:
ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);
為方便閱讀,請將這些項目放在 enum
定義之後。
新結構體構件佔用現有孔洞
由於對齊,urgent
和 scratch
之間會有未使用的位元組。
void *data;
bool urgent;
+ bool retry;
void *scratch;
新增 retry
不會影響現有的成員位移或結構大小。不過,這可能會影響符號 CRC 或 ABI 表示法,或兩者皆受影響。
這會將其從 CRC 計算中隱藏:
void *data;
bool urgent;
+ ANDROID_KABI_IGNORE(1, bool retry);
void *scratch_space;
成員 retry
可供使用,就像是直接宣告一樣,但巨集實際上會展開為包含 retry
的匿名聯集成員,以及 gendwarfksyms
用於維持符號型別穩定性的項目。
擴大結構,納入新成員
有時成員會加到結構的結尾。這不會影響現有成員的位移,也不會影響僅透過指標存取結構體的現有使用者。結構體的大小會影響 CRC,如要避免發生這種情況,可以在全域範圍內額外叫用巨集,如下所示:
struct data {
u64 handle;
u64 counter;
ANDROID_KABI_IGNORE(1, void *sekret);
};
ANDROID_KABI_BYTE_SIZE(data, 16);
為了方便閱讀,請將這個項目放在 struct
定義之後。
對型別或符號型別的所有其他變更
有時可能會發生不屬於上述任一類別的變更,導致 CRC 變更無法使用先前的巨集抑制。
在這些情況下,您可以在全域範圍內呼叫 ANDROID_KABI_TYPE_STRING
,提供型別或符號的原始 symtypes
說明。
struct data {
/* extensive changes */
};
ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");
為方便閱讀,請將此項目放在型別或符號定義之後。
Android 15 以下版本的解決方法
類型和符號類型變更必須從 genksyms
隱藏。方法是使用 __GENKSYMS__
控制前處理作業。
任意程式碼轉換都可以用這種方式表示。
舉例來說,如要隱藏現有結構中的新成員,請按照下列步驟操作:
struct parcel {
void *data;
bool urgent;
#ifndef __GENKSYMS__
bool retry;
#endif
void *scratch_space;
};