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 多個匯出的符號。相反地,供應商模組可使用的符號會明確列在在核心樹狀結構的根層級上維護的一組符號清單檔案。所有符號清單檔案中所有符號的聯集,定義了維持穩定的 KMI 符號組合。符號清單檔案的範例為 abi_gki_aarch64_db845c,用於宣告 DragonBoard 845c 所需的符號。

只有符號清單中列出的符號及其相關結構和定義才會視為 KMI 的一部分。如果沒有所需的符號,您可以發布符號清單變更。新介面加入符號清單並成為 KMI 說明的一部分後,就會維持穩定狀態,且在分支凍結後,不得從符號清單中移除或修改。

每個 Android 通用核心 (ACK) KMI 核心分支都有自己的符號清單。我們並未嘗試在不同 KMI 核心分支之間提供 ABI 穩定性。舉例來說,android12-5.10 的 KMI 與 android13-5.10 的 KMI 完全無關。

ABI 工具會使用 KMI 符號清單,限制必須監控哪些介面以確保穩定性。主要符號清單包含 GKI 核心模組所需的符號。供應商應提交及更新其他符號清單,確保其所依賴的介面維持 ABI 相容性。舉例來說,如要查看 android13-5.15 的符號清單,請參閱 https://android.googlesource.com/kernel/common/+/refs/heads/android13-5.15/android

符號清單包含特定供應商或裝置所需的符號。工具使用的完整清單是所有 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 差異

發生錯誤最常見的原因,是駕駛人使用核心中的新符號,而未列在任何符號清單中。

如果符號清單 (android/abi_gki_aarch64) 內未包含該符號,則需要先透過 EXPORT_SYMBOL_GPL(symbol_name) 驗證該符號是否已匯出,再更新 ABI XML 表示法和符號清單。舉例來說,下列變更會將新的增量 FS 功能新增至 android-12-5.10 分支,包括更新符號清單和 ABI XML 表示法。

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

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 XML 和符號清單的範例,請參閱 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 中的新功能,您可以引入符號以使用虛設常式進行 ACK,如下一節所述。

使用虛設常式做為 ACK

只有在核心核心變更不利於 ACK 時,才需要使用 Stub,例如效能和電源變更。下列清單列出 ACK for GKI 中有關 Stub 和部分精選項目的詳細範例。

  • 核心均分功能虛設常式 (aosp/1284493)。您不必具備 ACK 中的功能,但符號必須在 ACK 內顯示,模組才能使用這些符號。

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

  • 僅限 ABI 的個別程序 mm 事件追蹤功能精選 (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 中未偵測到的不相容性,進而導致難以偵錯的執行階段問題和核心當機。

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

在不啟動裝置的情況下檢查 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 檔案:

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

    cd out/$BRANCH/common && ls -1 kernel/module.*
    kernel/module.o
    kernel/module.o.symversions
    kernel/module.symtypes
    

    以下是 .c 檔案的特性:

    • .c 檔案的格式為每個符號一行 (可能非常長)。

    • 行開頭的 [s|u|e|etc]# 表示符號的類型為 [struct|union|enum|etc]。例如:

      t#bool typedef _Bool bool
      
    • 如果行首缺少 # 前置字元,表示該符號是函式。例如:

      find_module s#module * find_module ( const char * )
      
  4. 比較兩個檔案並修正所有差異。

案例 1:因資料類型顯示設定而產生的差異

如果其中一個核心將符號或資料類型對模組保持不透明,而另一個核心並未顯示,則兩個核心的 .symtypes 檔案之間會出現差異。其中一個核心的 .symtypes 檔案含有符號的 UNKNOWN,而另一個核心的 .symtypes 檔案則具有展開符號或資料類型的展開檢視畫面。

舉例來說,如果在核心的 include/linux/device.h 檔案中加入下列程式碼行,就會導致 CRC 不相符,其中一個是 module_layout()

 #include <linux/fwnode.h>

比較該符號的 module.symtypes,會發現下列差異:

 $ diff -u <GKI>/kernel/module.symtypes <your kernel>/kernel/module.symtypes
  --- <GKI>/kernel/module.symtypes
  +++ <your kernel>/kernel/module.symtypes
  @@ -334,12 +334,15 @@
  ...
  -s#fwnode_handle struct fwnode_handle { UNKNOWN }
  +s#fwnode_reference_args struct fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
  ...

如果核心的值為 UNKNOWN,而 GKI 核心則有符號的展開檢視畫面 (發生的機率極低),請將最新的 Android 通用核心合併至核心,以便使用最新的 GKI 核心基礎。

在大多數情況下,GKI 核心的值為 UNKNOWN,但由於核心已變更,因此您的核心會包含符號的內部詳細資料。這是因為核心中的其中一個檔案新增了 GKI 核心中不存在的 #include

一般而言,解決方法只是隱藏 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 核心。

  5. 找出變更項目,在核心中還原變更,或將其上傳至 ACK 並進行合併

案例 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 struct 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 struct 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. 找出變更後,請在核心中還原變更,或將其上傳至 ACK 並合併