Android 核心 ABI 監控

您可使用 Android 11 以上版本,為核心穩定運作 Android 核心的 ABI。這項工具會收集和比較 ABI 表示法 擷取自現有核心二進位檔 (vmlinux+ GKI 模組)。這些 ABI 表示法為 .stg 檔案和符號清單。開啟的介面 表示法為檢視畫面提供的名稱稱為核心模組介面 (KMI)。您可以使用該工具追蹤及減輕 KMI 的變化。

ABI 監控工具 已在 Android 開放原始碼計畫中開發 以及用途 STG (或 libabigail 英吋 Android 13 以下版本) 產生及比較 表示法

本頁將說明這項工具,也就是收集及分析 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 介面進行任何變更。 這些元件的穩定性不過,供應商可以隨時在 KMI 中加入符號 ,前提是加入的內容不會影響現有 ABI 的穩定性。新增 但 KMI 符號清單引用符號時,這些符號會保持穩定。 除非可以確認,否則請勿從核心清單中移除符號 沒有人出貨時 隨時都會看到這個符號

您可以按照 如何使用符號清單。 許多合作夥伴針對每個 ACK 提交一份符號清單,但這並非硬性規定。 如果有助於維護,您可以提交多份符號清單。

擴充 KMI

KMI 符號和相關結構會保持穩定 ( 無法對含有凍結 KMI 的核心,破壞穩定介面的變更 GKI 核心會持續使用擴充功能,以便 您不需要在 KMI 首次實作前,先定義所有依附元件 已凍結。如要延長 KMI,你可以在 KMI 中加入新符號,或是 現有的匯出核心函式,即使 KMI 已凍結。新增核心 如果修補程式不會中斷 KMI,也可能被接受。

關於 KMI 服務中斷

核心有「來源」,而二進位檔是依據這些來源建構而成。 ABI 監控的核心分支包含目前 GKI 的 ABI 表示法 ABI (格式為 .stg 檔案)。二進位檔結束後 (vmlinuxImage 和 任何 GKI 模組) 都有建構的 ABI 表示法 二進位檔。任何對核心來源檔案所做的變更,都可能會影響二進位檔和 轉彎也會影響擷取的.stgAbiAnalyzer 分析工具會比較 已修訂的 .stg 檔案,其中包含從版本構件擷取的檔案,然後設定 如果 Gerrit 找到語意差異,則會針對 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 表示法和符號清單。舉例來說,下列變更 android-12-5.10 分支版本的全新「增量 FS」功能 包括更新符號清單和 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 表示法)。範例說明 要更新 ABI XML 和符號清單,請參閱 aosp/1367601

解決核心 ABI 中斷問題

如要處理核心 ABI 中斷情形,只要重構程式碼,使其不變更 ABI更新 ABI 表示法。使用下列參數 圖表,找出最適合您情況的做法。

ABI 中斷流程圖

圖 1. ABI 故障解析

重構程式碼以避免 ABI 變更

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

  • 重構結構欄位變更。如果變更會修改偵錯用的 ABI 在結構和來源中,在欄位周圍新增 #ifdef ),並確保已為 #ifdef 停用 CONFIG 實際工作環境中的定義和 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

只有在核心變更沒有實質用途變更時,才需要使用虛設常式 ,例如效能和電源變化。下列清單詳細資料範例 大量的根號和部分 Cherry- Picks。

  • 核心均分功能虛設常式 (aosp/1284493)。 不須具備 ACK 中的功能,但符號需要 ,讓模組使用這些符號。

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

  • 僅針對 ABI 選擇每個程序的 mm 事件追蹤功能 (aosp/1288454)。 原始修補程式經過精心挑選,進入 ACK 後經過剪輯,只包含 進行必要變更,以解決 task_structmm_event_count。此修補程式也會更新 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() 匯出的符號) 與 符號清單。所有其他符號都不會匯出,而且會載入需要 未匯出的符號遭到拒絕。這項限制會在建構期間強制執行, 就會標記遺漏的項目

基於開發目的,您可以使用不包括 符號。也就是說,您可以使用所有一般匯出的符號。定位 請在這些版本中尋找 kernel_debug_aarch64 版本 ci.android.com

使用模組版本管理功能強制執行 KMI

一般核心映像檔 (GKI) 核心會使用模組版本管理 (CONFIG_MODVERSIONS) 是針對以下幾方面強制執行 KMI 法規遵循的額外措施: 執行階段。模組版本管理可能會導致循環備援檢查 (CRC) 不符 導致模組載入失敗的失敗情形 (如果模組的預期 KMI 不符合 vmlinux KMI。範例如下: 導致模組載入時間,因為 CRC 符號不符 module_layout()

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,它們會在將來的結構變更之後損毀。

    舉例來說,請考慮使用 fwnode struct device 中的欄位。 模組必須「不透明」這個欄位,才能變更 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)。

您可以比較 Module.symvers 檔案和 GKI 版本 檢查 vmlinux 匯出的符號中是否有任何 CRC 差異。如果有 是 vmlinux「並且」匯出的任何符號中的 CRC 值差異 符號用於裝置載入的其中一個模組,但該模組並未 載入。

如果您沒有所有建構構件,但有 vmlinux 檔案 GKI 核心和您的核心,您可以比較特定物件的 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 檔案。詳情請見 克利夫:KBUILD_SYMTYPES

    針對 Android 13 以下版本建構 GKI 核心 在指令前加上 KBUILD_SYMTYPES=1 使用 來建立核心,如下列指令所示:

    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,但核心擁有 符號的內部詳細資料。這是 因為核心中的其中一個檔案新增的 #include 不存在於 GKI 核心

一般而言,解決方法只是隱藏 genksyms 中的新的 #include

#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif

否則,如要找出造成差異的 #include,請按照下列指示操作: 步驟:

  1. 開啟定義符號或資料類型的標頭檔案, 差異在於舉例來說,您可以將 include/linux/fwnode.h 修改為 struct fwnode_handle

  2. 在標頭檔案頂端加入下列程式碼:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. 在模組的 .c 檔案中,出現 CRC 不相符的內容,然後將 插入任何 #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 下載 合併的