Android 內核 ABI 監控

您可以使用 Android 11 及更高版本中提供的應用程序二進制接口 (ABI) 監控工具來穩定 Android 內核的內核 ABI。該工具從現有內核二進製文件( vmlinux + 模塊)中收集和比較 ABI 表示。這些 ABI 表示是.xml文件和符號列表。表示提供視圖的接口稱為內核模塊接口 (KMI)。您可以使用該工具來跟踪和減輕對 KMI 的更改。

ABI 監控工具是在 AOSP 中開發的,並使用libabigail來生成和比較表示。

本頁描述了工具、收集和分析 ABI 表示的過程,以及使用這些表示來為內核 ABI 提供穩定性。此頁面還提供有關對 Android 內核進行更改的信息。

此目錄包含用於 ABI 分析的特定工具。將它與build_abi.sh提供的構建腳本一起使用。)

過程

分析內核的 ABI 需要多個步驟,其中大部分可以自動化:

  1. 通過repo獲取工具鏈、構建腳本和內核源
  2. 提供任何先決條件(例如libabigail庫和工具集)。
  3. 構建內核及其 ABI 表示
  4. 分析構建和參考之間的 ABI 差異
  5. 更新 ABI 表示(如果需要)
  6. 使用符號列表

以下說明適用於您可以使用支持的工具鏈(例如預構建的 Clang 工具鏈)構建的任何內核。存儲repo manifests可用於所有 Android 通用內核分支和幾個特定於設備的內核,它們確保在構建內核分發進行分析時使用正確的工具鏈。

使用 ABI 監控工具

1、通過repo獲取工具鏈、構建腳本、內核源碼

您可以使用repo獲取工具鏈、構建腳本(這些腳本)和內核源代碼。有關詳細文檔,請參閱構建 Android 內核的相應信息。

為了說明該過程,以下步驟使用了common-android12-5.10 ,這是一個 Android 內核分支,它是撰寫本文時最新發布的 GKI 內核。要通過repo獲取此分支,請執行以下命令:

repo init -u https://android.googlesource.com/kernel/manifest -b common-android12-5.10
repo sync

2. 提供先決條件

ABI 工具使用libabigail (一個庫和工具集合)來分析二進製文件。 kernel-build-tools附帶了一組合適的預構建二進製文件,並自動與build_abi.sh一起使用。

要利用較低級別的工具(例如dump_abi ),請將 kernel-build-tools 添加到PATH中。

3. 構建內核及其 ABI 表示

此時,您已準備好使用正確的工具鏈構建內核並從其二進製文件( vmlinux + 模塊)中提取 ABI 表示。

類似於通常的 Android 內核構建過程(使用build.sh ),此步驟需要運行build_abi.sh

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

這會構建內核並將 ABI 表示提取到out_abi子目錄中。在這種情況下out/android12-5.10/dist/abi.xml是指向out_abi/android12-5.10/dist/abi-<id>.xml的符號鏈接。 < id>是通過對內核源代碼樹執行git describe來計算的。

4. 分析構建和參考表示之間的 ABI 差異

當通過環境變量ABI_DEFINITION提供引用時, build_abi.sh分析並報告任何 ABI 差異。 ABI_DEFINITION必須指向相對於內核源代碼樹的參考文件,並且可以在命令行上指定,或者更常見的是,作為build.config中的值。下面提供了一個示例:

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

在上面的命令中, build.config.gki.aarch64定義了參考文件(如ABI_DEFINITION=android/abi_gki_aarch64.xml ), diff_abi調用abidiff將新生成的 ABI 表示與參考文件進行比較。 build_abi.sh打印報告的位置並針對任何 ABI 破損發出簡短報告。如果檢測到損壞, build_abi.sh將終止並返回非零退出代碼。

5. 更新 ABI 表示(如果需要)

要更新 ABI 表示,請使用--update標誌調用build_abi.sh 。它會更新build.config定義的相應abi.xml文件。要打印由於更新導致的 ABI 差異,請使用--print-report調用腳本。更新abi.xml文件時,請務必在提交消息中包含報告。

6. 使用符號列表

使用 KMI 符號列表參數化build_abi.sh以在 ABI 提取期間過濾符號。這些是列出相關 ABI 內核符號的純文本文件。例如,具有以下內容的符號列表文件將 ABI 分析限制為名稱symbol1symbol2的 ELF 符號:

[abi_symbol_list]
   symbol1
   symbol2

不考慮對其他 ELF 符號的更改。可以在相應的build.config配置文件中使用KMI_SYMBOL_LIST=將符號列表文件指定為相對於內核源目錄 ( $KERNEL_DIR ) 的文件。要提供一定程度的組織,您可以通過在build.config文件中使用ADDITIONAL_KMI_SYMBOL_LISTS=來指定其他符號列表文件。這指定了更多的符號列表文件,相對於$KERNEL_DIR ;用空格分隔多個文件名。

創建初始符號列表或更新現有符號列表,您必須使用帶有--update-symbol-list參數的build_abi.sh腳本。

當腳本以適當的配置運行時,它會構建內核並提取從vmlinux和 GKI 模塊導出的符號以及樹中任何其他模塊所需的符號。

考慮vmlinux導出以下符號(通常通過EXPORT_SYMBOL*宏完成):

  func1
  func2
  func3

此外,假設有兩個供應商模塊modA.komodB.ko ,它們需要以下符號(換句話說,它們在符號表中列出了undefined的符號條目):

 modA.ko:    func1 func2
 modB.ko:    func2

從 ABI 穩定性的角度來看, func1func2必須保持穩定,因為它們被外部模塊使用。相反,雖然func3被導出,但它並沒有被任何模塊主動使用(換句話說,它不是必需的)。因此,符號列表僅包含func1func2

要創建或更新現有符號列表, build_abi.sh必須按如下方式運行:

BUILD_CONFIG=path/to/build.config.device build/build_abi.sh --update-symbol-list

在此示例中, build.config.device必須包含幾個配置選項:

  • vmlinux必須在FILES列表中。
  • KMI_SYMBOL_LIST必須設置並指向要更新的 KMI 符號列表。
  • GKI_MODULES_LIST必須設置並指向 GKI 模塊列表。這個路徑通常是android/gki_aarch64_modules

使用較低級別的 ABI 工具

大多數用戶只需要使用build_abi.sh 。在某些情況下,可能需要直接使用較低級別的 ABI 工具。 dump_abi build_abi.sh diff_abi可用於提取和比較 ABI 文件。有關它們的用法,請參閱以下部分。

從內核樹創建 ABI 表示

提供帶有內置vmlinux和內核模塊的 linux 內核樹,工具dump_abi使用選定的 ABI 工具創建 ABI 表示。示例調用如下所示:

dump_abi --linux-tree path/to/out --out-file /path/to/abi.xml

文件abi.xml包含vmlinux的組合的、可觀察的 ABI 和給定目錄中的內核模塊的文本 ABI 表示。此文件可用於手動檢查、進一步分析或作為參考文件以強制執行 ABI 穩定性。

比較 ABI 表示

dump_abi創建的 ABI 表示可以與diff_abi進行比較。對dump_abidiff_abi使用相同的 abi-tool。示例調用如下所示:

diff_abi --baseline abi1.xml --new abi2.xml --report report.out

生成的報告列出了檢測到的影響 KMI 的 ABI 更改。指定為baselinenew的文件是使用dump_abi收集的 ABI 表示。 diff_abi傳播底層工具的退出代碼,因此當比較的 ABI 不兼容時返回非零值。

過濾 KMI 表示和符號

要過濾使用dump_abi創建的表示或過濾與diff_abi比較的符號,請使用參數--kmi-symbol-list ,該參數採用 KMI 符號列表文件的路徑:

dump_abi --linux-tree path/to/out --out-file /path/to/abi.xml --kmi-symbol-list /path/to/symbol_list_file

使用符號列表

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 符號列表引用,它們就會保持穩定。符號不應從內核列表中刪除,除非可以確認沒有任何設備曾經交付過對該符號的依賴。

您可以使用build/abi/extract_symbols實用程序為設備生成 KMI 符號列表,該實用程序從*.ko構建工件中提取符號依賴項。此實用程序以註釋的形式向輸出添加註釋,這對於識別符號的用戶很有用。在向 ACK 提交符號列表時,強烈建議保留這些評論以簡化審查過程。要省略註釋,請在運行extract_symbols腳本時傳遞--skip-module-grouping選項。許多合作夥伴為每個 ACK 提交一個符號列表,但這不是硬性要求。如果它有助於維護,您可以提交多個符號列表。

有關自定義符號列表和使用高級和低級 ABI 工具進行調試和詳細分析的幫助,請參閱ABI Monitoring for Android Kernels

擴展 KMI

雖然 KMI 符號和相關結構保持穩定(這意味著不能接受在 KMI 凍結的情況下破壞內核中穩定接口的更改),但 GKI 內核仍然對擴展開放,因此今年晚些時候發貨的設備不需要在凍結 KMI 之前定義它們的所有依賴項。要擴展 KMI,您可以為新的或現有的導出內核函數向 KMI 添加新符號,即使 KMI 已凍結。如果新內核補丁不破壞 KMI,也可以接受。

關於 KMI 破損

內核有源代碼,二進製文件是基於這些代碼構建的。 ABI 監控的內核分支包括abi.xml ,它是當前 GKI ABI 的表示。構建二進製文件(內核二進製文件、 vmlinuxImage plus 內核模塊)後,可以從二進製文件中提取abi.xml文件。對內核源代碼所做的任何更改都可能會更改二進製文件,也可能會更改提取的abi.xml (應用更改並構建內核後提取的文件)。 AbiAnalyzer分析器在語義上比較兩個abi.xml文件,並在發現問題時在更改上設置 Lint-1 標籤。

處理 ABI 破損

例如,以下補丁引入了一個非常明顯的 ABI 損壞:

 diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
  index 5ed8f6292a53..f2ecb34c7645 100644
  --- a/include/linux/mm_types.h
  +++ b/include/linux/mm_types.h
  @@ -339,6 +339,7 @@ struct core_state {
   struct kioctx_table;
   struct mm_struct {
      struct {
  +       int dummy;
          struct vm_area_struct *mmap;            /* list of VMAs */
          struct rb_root mm_rb;
          u64 vmacache_seqnum;                   /* per-thread vmacache */

當您在應用此補丁的情況下再次運行build_abi.sh時,工具會以非零錯誤代碼退出並報告類似於此的 ABI 差異:

 Leaf changes summary: 1 artifact changed
  Changed leaf types summary: 1 leaf type changed
  Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
  Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable

  'struct mm_struct at mm_types.h:372:1' changed:
    type size changed from 6848 to 6912 (in bits)
    there are data member changes:
  [...]

在構建時檢測到 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 (在結構和源引用中),並確保為生產 defconfig 和gki_defconfig禁用用於#ifdefCONFIG 。有關如何在不破壞 ABI 的情況下將調試配置添加到結構的示例,請參閱此補丁集

  • 重構功能以不更改核心內核。如果需要在 ACK 中添加新功能以支持夥伴模塊,請嘗試重構更改的 ABI 部分,以避免修改內核 ABI。有關使用現有內核 ABI 添加附加功能而不更改內核 ABI 的示例,請參閱aosp/1312213

修復 Android Gerrit 上損壞的 ABI

如果您不是故意破壞內核 ABI,那麼您需要使用 ABI 監控工具提供的指導進行調查。最常見的損壞原因是添加或刪除函數、更改數據結構或通過添加導致上述任何情況的配置選項導致的 ABI 更改。首先解決該工具發現的問題。

您可以使用與運行build/build.sh相同的參數運行以下命令,從而在本地重現 ABI 測試:

這是 GKI 內核的示例命令:

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

關於 Lint-1 標籤

如果您將更改上傳到包含凍結或最終 KMI 的分支,則更改必須通過ABIAnalyzer以確保更改不會以不兼容的方式影響穩定的 ABI。在此過程中, ABIAnalyzer查找在構建期間創建的 ABI 報告(執行正常構建的擴展構建,然後執行一些 ABI 提取和比較步驟。如果ABIAnalyzer找到非空報告,它將設置 Lint-1 標籤並且更改被阻止提交直到解決;直到補丁集收到 Lint+1 標籤。

更新內核 ABI

如果需要更新內核 ABI 表示,則必須更新內核源代碼樹中相應的abi.xml文件。最方便的方法是使用build/build_abi.sh ,如下所示:

build/build_abi.sh --update --print-report

使用與運行build/build.sh相同的參數。這將更新源樹中正確的abi.xml並打印檢測到的差異。作為實踐,在提交消息中包含打印的(簡短的)報告(至少部分)。

更新 ABI 表示

如果修改 ABI 是不可避免的,那麼您的代碼會更改並且 ABI XML 和符號列表需要應用於 ACK。要讓 Lint 刪除 -1 並且不破壞 GKI 兼容性,請執行以下步驟:

  1. 將 ABI 代碼更改上傳到 ACK

  2. 更新 ACK ABI 文件

  3. 合併您的代碼更改和 ABI 更新更改。

將 ABI 代碼更改上傳到 ACK

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

  • 如果 ABI 更改與影響 CTS 或 VTS 測試的功能相關,則通常可以將更改按原樣挑選到 ACK。例如:

  • 如果 ABI 更改是針對可與 ACK 共享的功能,則該更改可以按原樣挑選到 ACK。例如,CTS 或 VTS 測試不需要以下更改,但可以與 ACK 共享:

  • 如果 ABI 更改引入了不需要包含在 ACK 中的新功能,您可以使用存根將符號引入 ACK,如下節所述。

為 ACK 使用存根

只有對不利於 ACK 的核心內核更改(例如性能和功率更改)才需要存根。以下列表詳細說明了 GKI 的 ACK 中的存根和部分精選示例。

  • 核心隔離功能存根 ( aosp/1284493 )。 ACK 中的功能不是必需的,但符號需要出現在 ACK 中,您的模塊才能使用這些符號。

  • 供應商模塊 ( aosp/1288860 ) 的佔位符符號。

  • 每個進程mm事件跟踪功能 ( aosp/1288454 ) 的 ABI-only 精選。原始補丁被精心挑選為 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 合規性。

更新 ACK ABI 文件

要更新 ACK ABI 文件:

  1. 上傳 ABI 更改並等待接收補丁集的 Code-Review +2。

  2. 更新 ACK ABI 文件。

    cp partner kernel/android/abi_gki_aarch64_partner ACK kernel/abi_gki_aarch64_partner
    BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh --update
    
  3. 提交 ABI 更新:

    cd common
    git add android/abi*
    git commit -s -F $DIST_DIR/abi.report.short
    <push to gerrit>
    

    $DIST_DIR/abi.report.short包含更改的簡短報告。將-F標誌與git commit一起使用會自動使用提交文本的報告,然後您可以對其進行編輯以添加主題行(如果消息太長,則修剪)。

具有預定義 ABI 的 Android 內核分支

一些內核分支帶有預定義的 Android ABI 表示,作為其源代碼分發的一部分。這些 ABI 表示旨在準確,並反映build_abi.sh的結果,就像您自己執行它一樣。由於 ABI 受各種內核配置選項的影響很大,這些.xml文件通常屬於某個配置。例如, common-android12-5.10分支包含一個abi_gki_aarch64.xml ,對應於使用build.config.gki.aarch64時的構建結果。特別是build.config.gki.aarch64也通過ABI_DEFINITION引用這個文件。

在與diff_abi進行比較時,此類預定義的 ABI 表示被用作基線定義。例如,要針對 ABI 的任何更改驗證內核補丁,請創建應用了補丁的 ABI 表示,並使用diff_abi將其與該特定源樹或配置的預期 ABI 進行比較。如果設置了ABI_DEFINITION ,則相應地運行build_abi.sh

在運行時強制執行 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_handlefwnode的數據類型),則該模塊不再適用於新內核。此外, abidiff不會顯示任何差異,因為該模塊通過以僅檢查二進製表示無法捕獲的方式直接操作內部數據結構來破壞 KMI。

  • 當前模塊在稍後由不兼容的新內核加載時被視為 KMI 不兼容。模塊版本控制添加了運行時檢查,以避免意外加載與內核不兼容 KMI 的模塊。此檢查可防止由於未檢測到的 KMI 不兼容而導致的難以調試的運行時問題和內核崩潰。

  • abidiff在識別CONFIG_MODVERSIONS可以捕獲的某些複雜情況下的 ABI 差異方面存在局限性。

啟用模塊版本控制可以防止所有這些問題。

在不啟動設備的情況下檢查 CRC 不匹配

abidiff比較並報告內核之間的 CRC 不匹配。此工具使您能夠在捕獲其他 ABI 差異的同時捕獲不匹配的 CRC。

此外,啟用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=1到您用於構建內核的命令之前,構建 GKI 內核和您的設備內核,如以下命令所示:

    KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
    

    此命令為每個.o文件生成一個.symtypes文件。使用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 Common Kernel 合併到您的內核中,以便您使用最新的 GKI 內核庫。

在大多數情況下,GKI 內核的值為UNKNOWN ,但由於對內核所做的更改,您的內核具有符號的內部詳細信息。這是因為內核中的一個文件添加了 GKI 內核中不存在的#include

要識別導致差異的#include ,請執行以下步驟:

  1. 打開定義具有這種差異的符號或數據類型的頭文件。例如,為struct fwnode_handle編輯include/linux/fwnode.h 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 不匹配不是由於可見性不同,那麼它是由於數據類型本身的實際更改(添加、刪除或更改)造成的。通常, abidiff捕捉到這一點,但如果它由於已知的檢測差距而錯過任何一個, MODVERSIONS機制可以捕捉它們。

例如,在內核中進行以下更改會導致多個 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 並合併