Android カーネル ABI のモニタリング

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

Android 11 以降で利用可能なアプリケーション バイナリ インターフェース(ABI)モニタリング ツールを使用して、Android カーネルのカーネル内 ABI を安定させることができます。このツールは、既存のカーネル バイナリ(vmlinux + モジュール)から ABI 表現を収集して比較します。これらの ABI 表現は、.xml ファイルとシンボルリストです。この表現がビューを提供するインターフェースを、カーネル モジュール インターフェース(KMI)といいます。このツールを使用して、KMI の変更を追跡し、軽減できます。

ABI モニタリング ツールは AOSP で開発されており、libabigail を使用して表現を生成し、比較します。

このページでは、このツールや、ABI 表現の収集と分析のプロセス、カーネル内 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 を使用しています。これは、このドキュメントの作成時点でリリースされている最新の GKI カーネルである Android カーネル ブランチです。repo を通じてこのブランチを取得するには、次のコマンドを実行します。

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

2. カーネルとその 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.xmlout_abi/android12-5.10/dist/abi-<id>.xml へのシンボリック リンクです。<id> は、カーネル ソースツリーに対して git describe を実行することで計算されます。

Android 13 ブランチ以降では、カーネルビルドで Bazel が有効になっています。上記のコマンドと同等の Bazel は次のとおりです。

tools/bazel run //common:kernel_aarch64_abi_dist

詳細については、Bazel(GKI)による ABI モニタリングのサポートBazel(デバイス カーネル)による ABI モニタリングのサポートをご覧ください。

3. ビルドと参照表現の間で ABI の違いを分析する

build_abi.sh は、環境変数 ABI_DEFINITION を介して参照が提供されると、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_abiabidiff を呼び出して新たに生成された ABI 表現を参照ファイルと比較します。build_abi.sh は、レポートの場所を出力し、ABI の互換性の問題がある場合は短いレポートを出力します。互換性の問題が検出されると、build_abi.sh は終了し、ゼロ以外の終了コードを返します。

Android 13 ブランチ以降では、カーネルビルドで Bazel が有効になっています。上記のコマンドと同等の Bazel は次のとおりです。

tools/bazel run //common:kernel_aarch64_abi_dist

詳細については、Bazel(GKI)による ABI モニタリングのサポートBazel(デバイス カーネル)による ABI モニタリングのサポートをご覧ください。

4. ABI 表現を更新する(必要な場合)

ABI 表現を更新するには、--update フラグを指定して build_abi.sh を呼び出します。これにより、build.config で定義された対応する abi.xml ファイルが更新されます。更新に起因する ABI の違いを出力するには、--print-report を指定してスクリプトを呼び出します。abi.xml ファイルを更新するときは、必ずコミット メッセージにレポートを含めてください。

Android 13 ブランチ以降では、カーネルビルドで Bazel が有効になっています。GKI の ABI 表現を更新する Bazel コマンドは次のとおりです。

tools/bazel run //common:kernel_aarch64_abi_update_kmi_symbol_list &&
tools/bazel run //common:kernel_aarch64_abi_update

詳細については、Bazel(GKI)による ABI モニタリングのサポートBazel(デバイス カーネル)による ABI モニタリングのサポートをご覧ください。

5. シンボルリストを使用する

ABI の抽出中にシンボルをフィルタするには、build_abi.sh を KMI シンボルリストでパラメータ化します。これらは、関連する 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

また、次のシンボルを必要とする 2 つのベンダー モジュール 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 に複数の構成オプションを含める必要があります。

  • vmlinuxFILES リストに含まれている必要があります。
  • KMI_SYMBOL_LIST が設定され、更新する KMI シンボルリストを指している必要があります。
  • GKI_MODULES_LIST が設定され、GKI モジュールのリストを指している必要があります。通常、このパスは android/gki_aarch64_modules です。

下位レベルの ABI ツールを使用する

ほとんどのユーザーは build_abi.sh を使用するだけで済みます。場合によっては、下位レベルの ABI ツールを直接使用することが必要になることがあります。build_abi.sh で使用される 2 つのコマンド dump_abidiff_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 ツールを使用します。呼び出しの例を次に示します。

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 インターフェースに変更を加えることはできません。これらは安定しています。ただし、既存の ABI の安定性に影響しない限り、KMI にはいつでも自由にシンボルを追加できます。新しく追加されたシンボルは、KMI シンボルリストで引用されるとすぐに安定版として維持されます。シンボルの依存関係は、出荷時にそのシンボルへの依存関係が存在するデバイスがないことを確認できない限り、カーネルのリストから削除しないでください。

build/abi/extract_symbols ユーティリティを使用してデバイスの KMI シンボルリストを生成できます。これにより、*.ko ビルド アーティファクトからシンボルの依存関係が抽出されます。このユーティリティは、コメント形式で出力にアノテーションを追加します。コメントはシンボルのユーザーを識別するのに便利です。シンボルリストを ACK に送信するとき、レビュー プロセスを簡略化するために、これらのコメントを残しておくことを強くおすすめします。コメントを省略するには、extract_symbols スクリプトの実行時に --skip-module-grouping オプションを渡します。多くのパートナーは、ACK ごとに 1 つのシンボルリストを送信しますが、これは厳格な要件ではありません。メンテナンスに役立つ場合は、複数のシンボルリストを送信できます。

シンボルリストのカスタマイズや、デバッグと詳細な分析のための高レベルおよび低レベルの ABI ツールの使用については、Android カーネルの ABI モニタリングをご覧ください。

KMI を拡張する

KMI シンボルと関連構造は安定版として維持されます(つまり、固定された KMI があるカーネルにおいて安定したインターフェースを破壊する変更は認められません)。その一方で、GKI カーネルは拡張可能なままであるため、年内後半に出荷されるデバイスでは、KMI が固定される前にすべての依存関係を定義する必要はありません。KMI を拡張するには、新規または既存のエクスポートされたカーネル関数の KMI に新しいシンボルを追加します。この処理は KMI が固定されている場合でも可能です。KMI の互換性が損なわれなければ、新しいカーネルパッチも使用できます。

KMI の互換性の問題について

カーネルにはソースがあり、それらのソースに基づいてバイナリがビルドされます。 ABI のモニタリング対象のカーネル ブランチには、現在の GKI ABI を表現する abi.xml が含まれています。バイナリをビルドすると(カーネル バイナリ、vmlinuxImage とカーネル モジュール)、バイナリから abi.xml ファイルを抽出できるようになります。カーネルソースに変更を加えるとバイナリが変更され、抽出された abi.xml(変更を適用してカーネルをビルドした後に抽出されるファイル)も変更される場合があります。AbiAnalyzer アナライザは、2 つの 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 を追加し、#ifdef に使用する CONFIG が本番環境の defconfig と gki_defconfig で無効になるようにします。ABI の互換性を損なうことなくデバッグ構成を構造体に追加する方法の例については、このパッチセットをご覧ください。

  • コアカーネルを変更しないように機能をリファクタリングする。パートナー モジュールをサポートするために新しい機能を ACK に追加する必要がある場合は、カーネル ABI の変更を回避するために、変更の ABI 部分をリファクタリングしてください。カーネル ABI を変更せずに既存のカーネル ABI を使用して機能を追加する例については、aosp/1312213 をご覧ください。

互換性が損なわれた ABI を Android Gerrit で修正する

カーネル ABI の互換性を意図的に損なったのでなければ、ABI モニタリング ツールで提供されるガイダンスに沿って調査する必要があります。互換性の問題の原因としてありがちなものは、関数の追加や削除、データ構造の変更、またはそのいずれかにつながる構成オプションの追加による ABI の変更です。まずは、ツールで見つかった問題に対処します。

ABI テストをローカルで再現するには、build/build.sh を実行する場合と同じ引数を指定して次のコマンドを実行します。

GKI カーネルのコマンド例を次に示します。

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

Lint-1 ラベルについて

固定 KMI または確定済み KMI を含むブランチに変更をアップロードする場合、その変更では、互換性のない形で安定版 ABI に影響しないように、ABIAnalyzer を渡す必要があります。このプロセスでは、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 で選べます。次に例を示します。

    • 音声が機能するには aosp/1289677 が必要です。
    • USB が機能するには、aosp/1295945 が必要です。
  • ABI の変更が ACK と共有可能な機能に対するものである場合、その変更をそのまま ACK で選べます。たとえば、以下の変更は CTS テストや VTS テストでは必要ありませんが、ACK で共有しても問題ありません。

  • ABI の変更により、ACK に含める必要のない新機能が導入された場合は、次のセクションで説明するように、スタブを使用してシンボルを ACK に導入できます。

ACK にスタブを使用する

スタブは、パフォーマンスや電源の変更など、ACK にメリットがないコアカーネルの変更にのみ必要です。以下では、GKI の ACK でのスタブと部分的な選択の例を示します。

  • コア分離機能スタブ(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 のコンプライアンスを維持できるようになりました。

ACK ABI ファイルを更新する

ACK ABI ファイルを更新するには:

  1. ABI の変更をアップロードし、パッチセットのコードレビュー +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
    # Or, with Bazel,
    tools/bazel run //common:kernel_aarch64_abi_update_symbol_list &&
    tools/bazel run //common:kernel_aarch64_abi_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 ブランチには、build.config.gki.aarch64 を使用したときのビルド結果に対応する abi_gki_aarch64.xml が含まれています。特に build.config.gki.aarch64 は、ABI_DEFINITION を通じてこのファイルも参照します。

このような定義済み ABI 表現は、diff_abi と比較するときにベースライン定義として使用されます。たとえば、ABI の変更に関するカーネルパッチを検証するには、パッチを適用して ABI 表現を作成し、diff_abi を使用して、その特定のソースツリーまたは構成に対して予想される ABI と比較します。ABI_DEFINITION が設定されている場合は、それに応じて build_abi.sh を実行するだけで十分です。

実行時に KMI を適用する

GKI カーネルは TRIM_UNUSED_KSYMS=y 構成オプションと UNUSED_KSYMS_WHITELIST=<union of all symbol lists> 構成オプションを使用して、エクスポートされるシンボル(EXPORT_SYMBOL_GPL() を使用してエクスポートされるシンボルなど)をシンボルリストにあるものに限定します。その他のシンボルはすべてエクスポートされません。また、エクスポートされていないシンボルを必要とするモジュールの読み込みは拒否されます。この制限はビルド時に適用され、欠落しているエントリにはフラグが設定されます。

開発には、シンボルをカットしない GKI カーネルビルドを使用できます(つまり、通常エクスポートされるシンボルはすべて使用できます)。これらのビルドを見つけるには、ci.android.comkernel_debug_aarch64 ビルドを探します。

モジュール バージョニングを使用して KMI を適用する

汎用カーネル イメージ(GKI)カーネルは、実行時に KMI コンプライアンスを適用するための追加の手段として、モジュール バージョニングCONFIG_MODVERSIONS)を使用します。モジュール バージョニングでは、モジュールの予想される 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 devicefwnode フィールドについて考えてみましょう。このフィールドは、device->fw_node のフィールドを変更したり、そのサイズについて推測したりできないように、モジュールに対して不透明でなければなりません。

    ただし、モジュールに <linux/fwnode.h> が(直接的または間接的に)含まれている場合、struct devicefwnode フィールドは不透明でなくなります。すると、モジュールは device->fwnode->dev または device->fwnode->ops を変更できます。このシナリオには次のような理由から、問題があります。

    • コアカーネル コードで内部データ構造に対して行っている仮定が破られる可能性があります。

    • 将来のカーネル更新で struct fwnode_handlefwnode のデータ型)が変更された場合、モジュールが新しいカーネルで機能しなくなります。さらに、バイナリ表現の検査だけでは捕捉できない方法で内部データ構造を直接操作して KMI の互換性を破るため、abidiff は違いを示しません。

  • 現在のモジュールが後日、互換性のない新しいカーネルによって読み込まれた場合、KMI 互換でないとみなされます。カーネルと KMI 互換でないモジュールを誤って読み込まないように、モジュール バージョニングではランタイム チェックが追加されます。このチェックは、KMI で検出されない非互換性によって発生する、デバッグが困難なランタイムの問題やカーネル クラッシュを防ぎます。

  • abidiff には、CONFIG_MODVERSIONS が捕捉できる特定の複雑なケースで、ABI の違いを特定する際に制限があります。

モジュール バージョニングを有効にすると、このような問題をすべて回避できます。

デバイスを起動せずに CRC 不一致を確認する

abidiff は、カーネル間の CRC 不一致を比較して報告します。このツールを使用すると、他の ABI の違いと同時に、CRC 不一致を検出できます。

また、CONFIG_MODVERSIONS が有効になっている完全なカーネルビルドは、通常のビルドプロセスの一環として Module.symvers ファイルを生成します。このファイルの行は、カーネル(vmlinux)とモジュールによってエクスポートされるシンボルごとに 1 行ずつです。各行は、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 フラグはすでに暗黙的に設定されています。

    Android 13 ブランチ以降では、カーネルビルドで Bazel が有効になっています。上記のコマンドと同等の Bazel は次のとおりです。

    tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist
    

    詳しくは、 Kleaf のKBUILD_SYMTYPES をご覧ください。

  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 ファイルの形式は、シンボルごとに 1 行です(非常に長い場合もあります)。

    • 行頭の [s|u|e|etc]# は、シンボルがデータ型 [struct|union|enum|etc] であることを示しています。次に例を示します。

      t#bool typedef _Bool bool
      
    • 行頭に # プレフィックスがない場合は、シンボルが関数であることを示しています。次に例を示します。

      find_module s#module * find_module ( const char * )
      
  4. 2 つのファイルを比較し、違いをすべて修正します。

ケース 1: データ型の表示による違い

一方のカーネルがモジュールに対してシンボルまたはデータ型を不透明にし、他方のカーネルはそうでない場合、その違いは 2 つのカーネルの .symtypes ファイルの間に現れます。一方のカーネルの .symtypes ファイルにはシンボルの UNKNOWN があり、他方のカーネルの .symtypes ファイルにはシンボルまたはデータ型の展開ビューがあります。

たとえば、カーネルの include/linux/device.h ファイルに次の行を追加すると、CRC 不一致が発生します。そのうちの 1 つは 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 を追加したためです。

違いの原因となっている #include を特定する手順は次のとおりです。

  1. 違いがあるシンボルまたはデータ型を定義しているヘッダー ファイルを開きます。たとえば struct fwnode_handle の場合は include/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 のチェーン内のリンクの 1 つは、カーネルで行われた変更によるものであり、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);

そのうちの 1 つの 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 にアップロードして統合します