Android 核心 ABI 監控

您可以使用應用程式二進位介面 (ABI) 監控工具 (適用於 Android 11 以上版本),讓 Android 核心內的 ABI 穩定。工具會收集並比較現有核心二進位檔 (vmlinux+ GKI 模組) 中的 ABI 表示法。這些 ABI 表示法是 .stg 檔案和符號清單。提供檢視畫面的表示法介面稱為「核心模組介面」(KMI)。您可以使用這些工具追蹤並減輕 KMI 的變更。

ABI 監控工具是在 AOSP 中開發,並使用 STG (或 Android 13 以下版本中的 libabigail) 產生及比較表示法。

本頁說明工具、收集及分析 ABI 表示法的方式,以及使用這類表示法為核心內部 ABI 提供穩定性。本頁面也提供有關為 Android 核心提供變更的資訊。

程序

分析核心的 ABI 需要執行多個步驟,其中大多數可自動化:

  1. 建構核心及其 ABI 表示法
  2. 分析建構版本與參考版本之間的 ABI 差異
  3. 更新 ABI 表示法 (如有必要)
  4. 使用符號清單

以下操作說明適用於您使用支援的工具鍊 (例如預先建構的 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 通用核心 (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 符號清單。許多合作夥伴會為每個確認回應提交一個符號清單,但這並非強制規定。如有助於維護,您可以提交多個符號清單。

擴充 KMI

雖然 KMI 符號和相關結構體會維持穩定狀態 (也就是說,如果在已凍結 KMI 的核心中進行變更,會導致穩定介面中斷,因此無法接受),但 GKI 核心仍可接受擴充功能,因此在 KMI 凍結前,今年稍晚出貨的裝置不必定義所有依附元件。如要擴充 KMI,您可以為新的或現有的匯出核心函式,在 KMI 中新增符號,即使 KMI 已凍結也一樣。如果新的核心修補程式不會破壞 KMI,也可能會接受。

關於 KMI 損壞

核心有來源,二進位檔則是從這些來源建構。ABI 監控的核心分支包含目前 GKI ABI 的 ABI 表示法 (以 .stg 檔案的形式)。建構二進位檔 (vmlinuxImage 和任何 GKI 模組) 後,即可從二進位檔中擷取 ABI 表示法。對核心來源檔案所做的任何變更都可能影響二進位檔,進而影響擷取的 .stgAbiAnalyzer 分析器會比較已提交的 .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 表示法。舉例來說,下列變更會將新的增量 FS 功能新增至 android-12-5.10 分支,包括更新符號清單和 ABI 表示法。

如果您匯出符號 (或先前匯出),但沒有其他驅動程式使用,您可能會收到類似以下的建構錯誤。

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 損壞問題。請參考下表,找出最適合您情況的做法。

ABI 中斷流程圖

圖 1. ABI 損壞解決方案

重構程式碼以避免 ABI 變更

請盡量避免修改現有的 ABI。在許多情況下,您可以重構程式碼,以移除會影響 ABI 的變更。

  • 重構結構體欄位變更。如果變更會修改偵錯功能的 ABI,請在欄位 (在結構體和來源參照中) 周圍新增 #ifdef,並確保用於 #ifdefCONFIG 已為實際的 defconfig 和 gki_defconfig 停用。如需如何在未破壞 ABI 的情況下,將偵錯設定新增至結構體的範例,請參閱這個修補程式集

  • 重構功能,以免變更核心核心。如果需要在 ACK 中新增新功能來支援合作夥伴模組,請嘗試重構 ABI 部分的變更,以免修改核心 ABI。如需使用現有核心 ABI 新增其他功能而不變更核心 ABI 的範例,請參閱 aosp/1312213

修正 Android Gerrit 上的 ABI 問題

如果您並未故意破壞核心 ABI,則需要根據 ABI 監控工具提供的指引進行調查。發生錯誤最常見的原因是變更資料結構和相關符號 CRC 變更,或是因為變更設定選項而導致上述任何情況。首先解決工具發現的問題。

您可以在本機重現 ABI 發現事項,請參閱「建構核心及其 ABI 表示法」。

關於 Lint-1 標籤

如果您將變更上傳至包含已凍結或已定案 KMI 的分支,則變更必須通過 AbiAnalyzer,確保變更不會以不相容的方式影響穩定的 ABI。在這項程序中,AbiAnalyzer 會尋找在建構期間建立的 ABI 報表 (執行一般建構作業的擴充建構作業,然後執行一些 ABI 擷取和比較步驟)。

如果 AbiAnalyzer 找到非空白的報表,就會設定 Lint-1 標籤,並禁止變更提交,直到解決問題為止;直到修補程式集收到 Lint+1 標籤為止。

更新核心 ABI

如果無法避免修改 ABI,則必須將程式碼變更、ABI 表示法和符號清單套用至 ACK。如要讓 Lint 移除 -1 且不會破壞 GKI 相容性,請按照下列步驟操作:

  1. 將程式碼變更內容上傳至 ACK

  2. 等待收到修補程式集的 Code-Review +2。

  3. 更新參考 ABI 表示法

  4. 合併程式碼變更和 ABI 更新變更。

將 ABI 程式碼變更上傳至 ACK

更新 ACK ABI 取決於所做的變更類型。

  • 如果 ABI 變更與影響 CTS 或 VTS 測試的功能相關,通常可以將變更選取為 ACK。例如:

  • 如果 ABI 變更適用於可與 ACK 共用的功能,則可將該變更挑選為 ACK。舉例來說,下列變更不必用於 CTS 或 VTS 測試,但可與 ACK 共用:

  • 如果 ABI 變更引入的新功能不需要納入 ACK,您可以使用 Stub 將符號引入 ACK,如後續章節所述。

使用 ACK 的虛設常式

只有對 ACK 無益的核心核心變更 (例如效能和電源變更) 才需要使用 Stub。下列清單列出 ACK for GKI 中有關 Stub 和部分精選項目的詳細範例。

  • 核心隔離功能存根 (aosp/1284493)。您不需要使用 ACK 中的功能,但 ACK 中必須包含符號,您的模組才能使用這些符號。

  • 供應商模組的預留位置符號 (aosp/1288860)。

  • 僅針對每個程序的 mm 事件追蹤功能,挑選 ABI (aosp/1288454)。原始修補程式已挑選並納入 ACK,然後修剪,只保留解決 task_structmm_event_count 的 ABI 差異所需的必要變更。這個修正程式也更新了 mm_event_type 列舉,以便包含最終成員。

  • 部分溫度結構 ABI 變更的部分選取,需要新增新的 ABI 欄位。

    • 修補程式 aosp/1255544 解決了合作夥伴核心與 ACK 之間的 ABI 差異。

    • 修補程式 aosp/1291018 修正了先前修補程式在 GKI 測試期間發現的功能問題。修正內容包括初始化感應器參數結構體,將多個熱力區註冊到單一感應器。

  • CONFIG_NL80211_TESTMODE ABI 異動 (aosp/1344321)。這個修補程式為 ABI 新增必要的結構體變更,並確保額外欄位不會造成功能差異,讓合作夥伴可在實際工作環境的核心中加入 CONFIG_NL80211_TESTMODE,同時維持 GKI 相容性。

在執行階段強制執行 KMI

GKI 核心會使用 TRIM_UNUSED_KSYMS=yUNUSED_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->devdevice->fwnode->ops。這類情況會造成以下幾個問題:

    • 這可能會破壞核心核心程式碼對其內部資料結構的假設。

    • 如果日後的核心更新變更 struct fwnode_handle (fwnode 的資料類型),則模組就無法再與新核心搭配運作。此外,stgdiff 不會顯示任何差異,因為模組會直接操控內部資料結構,而這類操作無法透過檢查二進位檔呈現方式擷取,因此會導致 KMI 中斷。

  • 如果現有模組在日後由不相容的新核心載入,就會視為與 KMI 不相容。模組版本控制會新增執行階段檢查,避免誤載入與核心不相容的 KMI 模組。這項檢查可避免 KMI 中未偵測到的不相容性,進而導致難以偵錯的執行階段問題和核心當機。

啟用模組版本功能可避免發生上述問題。

在不啟動裝置的情況下檢查 CRC 不符情形

stgdiff 會比較並回報核心之間的 CRC 不相符情形,以及其他 ABI 差異。

此外,啟用 CONFIG_MODVERSIONS 的完整核心版本會在正常建構程序中產生 Module.symvers 檔案。這個檔案會針對核心 (vmlinux) 和模組匯出的每個符號,產生一行資料。每行內容包括 CRC 值、符號名稱、符號命名空間、匯出符號的 vmlinux 或模組名稱,以及匯出類型 (例如 EXPORT_SYMBOLEXPORT_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 不相符問題:

  1. 使用 --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 旗標。

  2. 使用下列指令,找出匯出 CRC 不相符符號的 .c 檔案:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. .c 檔案在 GKI 和裝置核心建構構件中都有對應的 .symtypes 檔案。使用下列指令找出 .symtypes 檔案:

    cd bazel-bin/common/kernel_aarch64/symtypes
    ls -1 kernel/module/version.symtypes

    在 Android 13 以下版本中,如果使用舊版建構指令碼,位置可能會是 out/$BRANCH/commonout_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 * )
      
  4. 比較兩個檔案,並修正所有差異。

建議您在發生問題的變更前,先使用建構作業產生 symtypes,然後再產生問題變更。儲存所有檔案,即可大量比較檔案。

例如:

for f in $(find good bad -name '*.symtypes' | sed -r 's;^(good|bad)/;;' | LANG=C sort -u); do
  diff -N -U0 --label good/"$f" --label bad/"$f" <(LANG=C sort good/"$f") <(LANG=C sort bad/"$f")
done

否則,請只比較特定檔案。

案例 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 通用核心併入核心,以便使用最新的 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,請按照下列步驟操作:

  1. 開啟定義符號或資料類型有此差異的標頭檔案。例如,編輯 struct fwnode_handleinclude/linux/fwnode.h

  2. 在標頭檔案頂端新增下列程式碼:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. 在 CRC 不相符的模組 .c 檔案中,將下列程式碼新增為任一 #include 行之前的第一行。

    #define CRC_CATCH 1
    
  4. 編譯模組。產生的建構時間錯誤會顯示導致 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 * ) ; ...

如要找出變更的類型,請按照下列步驟操作:

  1. 在原始碼中找出符號的定義 (通常位於 .h 檔案中)。

    • 如要查看核心與 GKI 核心之間的符號差異,請執行下列指令找出修訂版本:
    git blame
    • 對於已刪除的符號 (在樹狀結構中刪除符號,且您也想在其他樹狀結構中刪除該符號),您需要找出刪除該行所做的變更。在刪除該行所在的樹狀結構上使用下列指令:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. 查看傳回的修訂版本清單,找出變更或刪除的內容。第一個提交內容可能是您要尋找的內容。如果不是,請瀏覽清單,直到找到該提交內容為止。

  3. 找出相關的修訂版本後,請在核心中還原該版本,或更新該版本以抑制 CRC 變更,並將其上傳至 ACK 並進行合併。每個殘留的 ABI 中斷點都需要經過安全性審查,必要時可記錄允許的中斷點。

建議使用現有的邊框

GKI 中的部分結構會加上填充資料,以便擴充而不會中斷現有的供應商模組。舉例來說,如果上游提交內容將成員新增至這類結構體,則可以將其變更為使用部分邊框。這項變更會從 CRC 計算中隱藏。

標準化且可自行註解的巨集 ANDROID_KABI_RESERVE 會保留 u64 的 (對齊) 空間。可用來取代成員宣告。

例如:

struct data {
        u64 handle;
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
};

您可以使用 ANDROID_KABI_USE (或 ANDROID_KABI_USE2 或其他可能定義的變化版本),在不影響符號 CRC 的情況下使用填充資料。

成員 sekret 可供使用,就像是直接宣告一樣,但巨集實際上會展開為匿名聯集成員,其中包含 sekretgendwarfksyms 用來維持符號類型穩定性的項目。

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
 };

在全域範圍內使用巨集呼叫時,新增 TRY_HARDER 和變更為 OUTCOME_LIMIT 的動作可從 CRC 計算中隱藏:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

為了方便閱讀,請將這些元素放在 enum 定義後方。

新結構體成員占用現有空格

由於對齊,urgentscratch 之間會有未使用的位元組。

        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 可用,就像直接宣告一樣,但巨集實際上會展開為匿名聯集成員,其中包含 retrygendwarfksyms 用來維持符號類型穩定性的項目。

結構體擴充功能,新增成員

成員有時會加到結構體的結尾。這不會影響現有成員的偏移量,也不會影響只透過指標存取結構體的現有使用者。結構體的大小會影響其 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;
};