ABI の安定性

ベンダー モジュールは、システム パーティション内にある Vendor Native Development Kit(VNDK)共有ライブラリに依存する可能性があるため、アプリケーション バイナリ インターフェース(ABI)の安定性は、フレームワークのみの更新の前提条件です。1 つの Android リリース内では、新しくビルドされた VNDK 共有ライブラリが、以前にリリースされた VNDK 共有ライブラリと ABI の互換性を持っている必要があります。それにより、ベンダー モジュールが、再コンパイルすることも実行時にエラーが発生することもなく、これらのライブラリを利用できます。ただし、複数の Android リリース間では、VNDK ライブラリが変更になっている可能性があり、ABI 保証もありません。

ABI の互換性を確保するために、Android 9 には、以下のセクションで説明するように、ヘッダー ABI チェッカーが含まれています。

VNDK および ABI コンプライアンスについて

VNDK とは、ベンダー モジュールがリンクする可能性のあるライブラリの制限付きセットのことで、フレームワークのみの更新を有効にします。ABI コンプライアンスとは、新しいバージョンの共有ライブラリが、動的にリンクされたモジュールで想定どおりに(つまり、古いバージョンのライブラリと同様に)動作するようにする機能のことを指します。

エクスポート済みシンボルについて

エクスポート済みシンボル(グローバル シンボルとも呼ばれます)は、以下のすべてを満たすシンボルのことを指します。

  • 共有ライブラリの公開ヘッダーによってエクスポートされている。
  • 共有ライブラリに対応する .so ファイルの .dynsym の表に表示されている。
  • WEAK または GLOBAL なバインディングがある。
  • 公開設定が DEFAULT または PROTECTED になっている。
  • セクション インデックスが UNDEFINED ではない。
  • 型が FUNC または OBJECT である。

共有ライブラリの公開ヘッダーは、共有ライブラリに対応するモジュールの Android.bp 定義にある export_include_dirsexport_header_lib_headersexport_static_lib_headersexport_shared_lib_headersexport_generated_headers の属性を通じて他のライブラリやバイナリで使用できるヘッダーとして定義されます。

到達可能タイプについて

到達可能タイプとは、エクスポート済みシンボルを通じて直接的または間接的に到達可能で、かつ公開ヘッダーを通じてエクスポートされた C / C++ ビルトインまたはユーザーが定義したタイプのことです。たとえば、libfoo.so には、.dynsym の表にあるエクスポート済みシンボルである関数 Foo があります。libfoo.so ライブラリには、以下が含まれます。

foo_exported.h foo.private.h
typedef struct foo_private foo_private_t;

typedef struct foo {
  int m1;
  int *m2;
  foo_private_t *mPfoo;
} foo_t;

typedef struct bar {
  foo_t mfoo;
} bar_t;

bool Foo(int id, bar_t *bar_ptr);
typedef struct foo_private {
  int m1;
  float mbar;
} foo_private_t;
Android.bp
cc_library {
  name : libfoo,
  vendor_available: true,
  vndk {
    enabled : true,
  }
  srcs : ["src/*.cpp"],
  export_include_dirs : [
    "exported"
  ],
}
.dynsym table
Num Value Size Type Bind Vis Ndx Name
1 0 0 FUNC GLOB DEF UND dlerror@libc
2 1ce0 20 FUNC GLOB DEF 12 Foo

Foo の場合、直接的または間接的に到達可能なタイプは次のとおりです。

説明
bool Foo の戻り値の型。
int 最初の Foo パラメータの型。
bar_t * 2 番目の Foo パラメータの型。bar_t * のために、foo_exported.h を通じて bar_t をエクスポートします。

bar_t には foo_t 型であるメンバー mfoo が含まれ、この型が foo_exported.h を通じてエクスポートされます。その結果、さらに次の型がエクスポートされます。
  • int :m1 の型です。
  • int * :m2 の型です。
  • foo_private_t * : mPfoo の型です。

ただし、foo_private_t は、foo_exported.h を通してエクスポートされていないため、到達できません(foo_private_t * は不透明であるため、foo_private_t への変更は許可されます)。

基本クラス指定子やテンプレート パラメータを通して到達可能なタイプにも同様の説明ができます。

ABI コンプライアンスの確保

対応する Android.bp ファイルで vendor_available: true および vndk.enabled: true とマーク付けられたライブラリは、確実に ABI に準拠している必要があります。次に例を示します。

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}

エクスポート済みの関数によって直接的または間接的に到達できるデータ型の場合、ライブラリに対する次の変更は、ABI-breaking として分類されます。

データのタイプ 説明
構造体とクラス
  • クラスタイプまたは構造体タイプのサイズを変更します。
  • 基本クラス
    • 基本クラスを追加または削除します。
    • 仮想化して継承した基本クラスを追加または削除します。
    • 基本クラスの順序を変更します。
  • メンバー関数
    • メンバー関数を削除します*。
    • メンバー関数から引数を追加または削除します。
    • 引数の型またはメンバー関数の戻り値の型を変更します*。
    • 仮想テーブルのレイアウトを変更します。
  • データメンバー
    • 静的なデータメンバーを削除します。
    • 静的でないデータメンバーを追加または削除します。
    • データメンバーのタイプを変更します。
    • オフセットを静的でないデータメンバーに変更します**。
    • データメンバーの constvolatile、または restricted の修飾子を変更します***。
    • データメンバーのアクセス指定子をダウングレードします***。
  • テンプレート引数を変更します。
共用体
  • データメンバーを追加または削除します。
  • 共用体タイプのサイズを変更します。
  • データメンバーのタイプを変更します。
列挙型
  • 基になる型を変更します。
  • 列挙子の名前を変更します。
  • 列挙子の値を変更します。
グローバル シンボル
  • 公開ヘッダーによってエクスポートされたシンボルを削除します。
  • FUNC 型のグローバル シンボルの場合
    • 引数を追加または削除します。
    • 引数の型を変更します。
    • 戻り値の型を変更します。
    • アクセス指定子をダウングレードします***。
  • OBJECT 型のグローバル シンボルの場合
    • 対応する C / C++ の型を変更します。
    • アクセス指定子をダウングレードします***。

* 公開インライン関数は非公開メンバー関数を参照できるため、公開メンバー関数と非公開メンバー関数は両方とも変更または削除しないでください。非公開メンバー関数へのシンボル参照は、呼び出し元のバイナリに保持できます。共有ライブラリから非公開メンバー関数を変更または削除すると、下位互換性のないバイナリが作成されます。

** インライン関数は関数本体でデータメンバーを参照できるため、公開データメンバーまたは非公開データメンバーへのオフセットは変更できません。データメンバーのオフセットを変更すると、下位互換性のないバイナリが作成される可能性があります。

*** これらによって型のメモリ レイアウトが変わることはありませんが、意味論的な違いにより、ライブラリが期待どおりに機能しなくなる可能性があります。

ABI コンプライアンス ツールの使用

VNDK ライブラリがビルドされると、ライブラリの ABI は、ビルド中の VNDK のバージョンの対応する ABI リファレンスと比較されます。参照 ABI ダンプは次の場所にあります。

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based

たとえば、API レベル 27 の x86 に対する libfoo のビルドでは、libfoo の推定 ABI は次の参照先と比較されます。

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

ABI 破損エラー

ABI が破損した場合、ビルドログに警告タイプや abi-diff レポートへのパスと一緒に警告が表示されます。たとえば、libbinder の ABI に互換性のない変更があると、ビルドシステムは次のようなメッセージとともにエラーをスローします。

*****************************************************
error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES
Please check compatibility report at:
out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff
******************************************************
---- Please update abi references by running
platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ----

VNDK ライブラリの ABI チェックの構築

VNDK ライブラリがビルドされている場合:

  1. header-abi-dumper は、VNDK ライブラリ(ライブラリのソースファイルと静的かつ推移的な依存関係から継承したソースファイル)をビルドするためにコンパイルされたソースファイルを処理して、各ソースに対応する .sdump を作成します。
    sdump の作成
    図 1. .sdump ファイルの作成
  2. header-abi-linker は続いて(提供されたバージョン スクリプトまたは共有ライブラリに対応する .so ファイルを使用して).sdump ファイルを処理し、共有ライブラリに対応するすべての ABI 情報を記録する .lsdump を作成します。
    lsdump の作成
    図 2. .lsdump ファイルの作成
  3. header-abi-diff.lsdump ファイルと参照 .lsdump ファイルを比較して、2 つのライブラリの ABI の違いを概説した差分レポートを作成します。
    abi diff の作成
    図 3. 差分レポートの作成

header-abi-dumper

header-abi-dumper ツールは、C / C++ のソースファイルを解析し、そのソースファイルから推測された ABI を中間ファイルにダンプします。ビルドシステムはコンパイルされたすべてのソースファイルで header-abi-dumper を実行しながら、推移的な依存関係からのソースファイルを含むライブラリも構築します。

入力
  • C / C++ ソースファイル
  • エクスポートされたインクルード ディレクトリ
  • コンパイラ フラグ
出力 ソースファイルの ABI を記述するファイル(例: foo.sdump foo.cpp の ABI を表します)。

現在 .sdump ファイルは JSON 形式であるため、将来のリリースで安定することは保証されていません。そのため、.sdump のファイル形式はビルドシステムの実装の詳細とみなす必要があります。

たとえば、libfoo.so には次のソースファイル foo.cpp があります。

#include <stdio.h>
#include <foo_exported.h>

bool Foo(int id, bar_t *bar_ptr) {
    if (id > 0 && bar_ptr->mfoo.m1 > 0) {
        return true;
    }
    return false;
}

header-abi-dumper を使用して、以下を使用するソースファイルによって提示される ABI を表す中間 .sdump ファイルを生成できます。

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++

このコマンドは、-- の後にコンパイラ フラグを設定して foo.cpp を解析し、exported ディレクトリの公開ヘッダーによってエクスポートされている ABI 情報を出力するように header-abi-dumper に指示します。header-abi-dumper で生成される foo.sdump は次のとおりです。

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" : [],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

foo.sdump には、ソースファイル foo.cpp と公開ヘッダーによってエクスポートされた ABI 情報が含まれています。次に例を示します。

  • record_types。公開ヘッダーで定義されている構造体、共用体、またはクラスを指します。各レコードタイプは、フィールド、サイズ、アクセス指定子、そこで定義されているヘッダー ファイル、その他の属性に関する情報を保持します。
  • pointer_types。公開ヘッダーのエクスポートされたレコード / 関数によって直接的 / 間接的に参照されるポインタ型と、そのポインタが(type_info にある referenced_type フィールドを介して)指す型を指します。類似の情報は、修飾された型、組み込みの C / C++ 型、配列型、lvalue 参照型、rvalue 参照型の .sdump ファイルに記録されています。このような情報は、再帰的な比較に利用できます。
  • functions。公開ヘッダーによってエクスポートされた関数を表します。関数のマングリングされた名前、戻り値の型、パラメータの型、アクセス指定子、その他の属性に関する情報も含まれます。

header-abi-linker

header-abi-linker ツールは、入力として header-abi-dumper が作成した中間ファイルを取得し、それらのファイルをリンクします。

入力
  • header-abi-dumper が作成した中間ファイル
  • バージョン スクリプトまたはマップファイル(省略可)
  • 共有ライブラリの .so ファイル
  • エクスポートされたインクルード ディレクトリ
出力 共有ライブラリの ABI を記述するファイル(例: libfoo.so.lsdump libfoo の ABI を表します)。

このツールは、変換ユニット間の 1 つの定義(完全修飾名が同じで、変換ユニットが異なるユーザー定義型は意味論的に異なる可能性があります)を考慮して、すべての中間ファイルの型グラフを結合します。ツールはその後、バージョン スクリプトまたは共有ライブラリの .dynsym の表(.so ファイル)を解析し、エクスポート済みシンボルのリストを作成します。

たとえば、libfoofoo.cppbar.cpp で構成されています。次のように header-abi-linker を呼び出して libfoo の完全にリンクされた ABI のダンプを作成することが可能です。

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current

libfoo.so.lsdump のコマンド出力の例:

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 1,
   "is_integral" : true,
   "is_unsigned" : true,
   "linker_set_key" : "_ZTIb",
   "name" : "bool",
   "referenced_type" : "_ZTIb",
   "self_type" : "_ZTIb",
   "size" : 1
  },
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" :
 [
  {
   "name" : "_Z3FooiP3bar"
  },
  {
   "name" : "_Z6FooBadiP3foo"
  }
 ],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "Foo",
   "linker_set_key" : "_Z3FooiP3bar",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3bar"
    }
   ],
   "return_type" : "_ZTIb",
   "source_file" : "exported/foo_exported.h"
  },
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3bar",
   "name" : "bar *",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTIP3bar",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

header-abi-linker ツールは次の処理を行います。

  • 提示された .sdump ファイル(foo.sdumpbar.sdump)をリンクし、ディレクトリ exported に存在するヘッダーにない ABI 情報を除外します。
  • libfoo.so を解析し、.dynsym の表を通じてライブラリによってエクスポートされたシンボルに関する情報を収集します。
  • _Z3FooiP3bar_Z6FooBadiP3foo を追加します。

libfoo.so.lsdump は、libfoo.so の最後に生成された ABI のダンプです。

header-abi-diff

header-abi-diff ツールは、2 つのライブラリの ABI を表す 2 つの .lsdump ファイルを比較し、2 つの ABI の違いを示す差分レポートを作成します。

入力
  • 古い共有ライブラリの ABI を表す .lsdump ファイル。
  • 新しい共有ライブラリの ABI を表す .lsdump ファイル。
出力 2 つの共有ライブラリにおける ABI の違いを示す差分レポート。

ABI 差分ファイルは protobuf テキスト形式です。今後のリリースでは、形式が変更される可能性があります。

たとえば、libfoolibfoo_old.solibfoo_new.so の 2 つのバージョンがあるとします。libfoo_new.sobar_t で、mfoo のタイプを foo_t から foo_t * に変更します。bar_t はアクセス可能なタイプのため、これは、header-abi-diff による ABI の互換性を破る変更としてフラグされる必要があります。

header-abi-diff を実行する方法は以下のとおりです。

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo

libfoo.so.abidiff のコマンド出力の例:

lib_name: "libfoo"
arch: "arm64"
record_type_diffs {
  name: "bar"
  type_stack: "Foo-> bar *->bar "
  type_info_diff {
    old_type_info {
      size: 24
      alignment: 8
    }
    new_type_info {
      size: 8
      alignment: 8
    }
  }
  fields_diff {
    old_field {
      referenced_type: "foo"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
    new_field {
      referenced_type: "foo *"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
  }
}

libfoo.so.abidiff には、libfoo 内のすべての ABI の互換性を破る変更のレポートが含まれています。record_type_diffs のメッセージは、レコードに変更があったことを示し、次のような互換性のない変更を表示します。

  • 24 バイトから 8 バイトに変更するレコードのサイズ。
  • foo から foo * に変更する mfoo のフィールド タイプ(すべての typedef は削除されます)。

type_stack フィールドは、header-abi-diff がどのように変更したタイプ(bar)に到達したのかを示します。このフィールドでは、Foo が、bar * をパラメータとして取得し、エクスポート、変更された bar を指すエクスポート済み関数として解釈される可能性があります。

ABI や API の強化

VNDK 共有ライブラリの ABI や API を強化するには、ABI リファレンスを ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/ にチェックインする必要があります。これらのリファレンスを作成するには、次のコマンドを実行します。

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py

リファレンスを作成した後に、VNDK ライブラリで互換性のない ABI や API の変更を引き起こす変更がソースコードに加えられた場合、ビルドエラーが発生します。

特定のライブラリの ABI リファレンスを更新するには、次のコマンドを実行します。

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>

たとえば、libbinder ABI リファレンスを更新するには、次のコマンドを実行します。

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder