ベンダー モジュールは、システム パーティション内にある 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_dirs
、export_header_lib_headers
、export_static_lib_headers
、export_shared_lib_headers
、export_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 を通じてエクスポートされます。その結果、さらに次の型がエクスポートされます。
ただし、 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 として分類されます。
データのタイプ | 説明 |
---|---|
構造体とクラス |
|
共用体 |
|
列挙型 |
|
グローバル シンボル |
|
* 公開インライン関数は非公開メンバー関数を参照できるため、公開メンバー関数と非公開メンバー関数は両方とも変更または削除しないでください。非公開メンバー関数へのシンボル参照は、呼び出し元のバイナリに保持できます。共有ライブラリから非公開メンバー関数を変更または削除すると、下位互換性のないバイナリが作成されます。
** インライン関数は関数本体でデータメンバーを参照できるため、公開データメンバーまたは非公開データメンバーへのオフセットは変更できません。データメンバーのオフセットを変更すると、下位互換性のないバイナリが作成される可能性があります。
*** これらによって型のメモリ レイアウトが変わることはありませんが、意味論的な違いにより、ライブラリが期待どおりに機能しなくなる可能性があります。
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 ライブラリがビルドされている場合:
header-abi-dumper
は、VNDK ライブラリ(ライブラリのソースファイルと静的かつ推移的な依存関係から継承したソースファイル)をビルドするためにコンパイルされたソースファイルを処理して、各ソースに対応する.sdump
を作成します。
図 1. .sdump
ファイルの作成header-abi-linker
は続いて(提供されたバージョン スクリプトまたは共有ライブラリに対応する.so
ファイルを使用して).sdump
ファイルを処理し、共有ライブラリに対応するすべての ABI 情報を記録する.lsdump
を作成します。
図 2. .lsdump
ファイルの作成header-abi-diff
は.lsdump
ファイルと参照.lsdump
ファイルを比較して、2 つのライブラリの ABI の違いを概説した差分レポートを作成します。
図 3. 差分レポートの作成
header-abi-dumper
header-abi-dumper
ツールは、C / C++ のソースファイルを解析し、そのソースファイルから推測された ABI を中間ファイルにダンプします。ビルドシステムはコンパイルされたすべてのソースファイルで header-abi-dumper
を実行しながら、推移的な依存関係からのソースファイルを含むライブラリも構築します。
入力 |
|
---|---|
出力 | ソースファイルの 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
が作成した中間ファイルを取得し、それらのファイルをリンクします。
入力 |
|
---|---|
出力 | 共有ライブラリの ABI を記述するファイル(例: libfoo.so.lsdump は libfoo の ABI を表します)。
|
このツールは、変換ユニット間の 1 つの定義(完全修飾名が同じで、変換ユニットが異なるユーザー定義型は意味論的に異なる可能性があります)を考慮して、すべての中間ファイルの型グラフを結合します。ツールはその後、バージョン スクリプトまたは共有ライブラリの .dynsym
の表(.so
ファイル)を解析し、エクスポート済みシンボルのリストを作成します。
たとえば、libfoo
は foo.cpp
と bar.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.sdump
とbar.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 の違いを示す差分レポートを作成します。
入力 |
|
---|---|
出力 | 2 つの共有ライブラリにおける ABI の違いを示す差分レポート。 |
ABI 差分ファイルは protobuf テキスト形式です。今後のリリースでは、形式が変更される可能性があります。
たとえば、libfoo
に libfoo_old.so
と libfoo_new.so
の 2 つのバージョンがあるとします。libfoo_new.so
の bar_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