ABI 管理

Android ハンドセットによって、使用される CPU が異なるため、サポートされる命令セットも異なります。 CPU と命令セットの組み合わせごとにアプリケーション バイナリ インターフェース(ABI)が異なります。 ABI は、アプリケーションのマシンコードが実行時にシステムとやり取りする方法を詳細に定義したものです。 アプリで使用したい CPU アーキテクチャごとに、ABI を指定する必要があります。

一般的な ABI には次の情報が含まれます。

  • マシンコードで使用される CPU 命令セット。
  • ランタイム時のメモリストアとメモリロードのエンディアン。
  • プログラム、共有ライブラリ、これらがサポートするコンテンツのタイプなど、実行可能バイナリのフォーマット。
  • コードとシステムの間でデータを受け渡すためのさまざまな規則。 規則には、アライメント制約や、システムが関数の呼び出し時にスタックやレジスターを使用する方法が含まれます。
  • 一般的に非常に限られたライブラリ セットからの、ランタイム時のマシンコードに使用できる関数シンボルのリスト。

このページには NDK がサポートする ABI と各 ABI の仕組みに関する情報が記載されています。 32 ビットシステムでの ABI の問題のリストについては、32 ビット ABI のバグ

サポート対象 ABI

各 ABI は 1 つ以上の命令セットをサポートします。 表 1 は、各 ABI がサポートする命令セットの概要です。

表 1. ABI とサポート対象命令セット。

ABI サポート対象命令セット
armeabi
  • ARMV5TE 以降
  • Thumb-1
  • r16 で廃止。r17 で削除。ハード浮動小数点なし。
    armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • その他、オプション
  • ARMv5、v6 端末と互換性なし。
    arm64-v8a
  • AArch64
  • x86
  • x86(IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • MOVBE または SSE4 に対するサポートなし。
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1、4.2
  • POPCNT
  • 注: これまで、NDK は 32 ビットおよび 64 ビット MIPS をサポートしていましたが、NDK r17 でこのサポートは削除されました。

    各 ABI の詳細については以下をご覧ください。

    armeabi

    注: この ABI は NDK r17 で削除されました。

    この ABI は少なくとも ARMv5TE 命令セットをサポートする ARM ベース CPU 向けです。 詳細については以下のドキュメントをご覧ください。

    AAPCS 標準は、類似する別々の ABI のファミリーとして EABI を定義します。 さらに、Android はリトル エンディアン ARM GNU/Linux ABI に準拠します。

    この ABI はハードウェア補助の浮動小数点演算をサポートしません。 すべての浮動小数点演算には、コンパイラの libgcc.a 静的ライブラリからのソフトウェア ヘルパー関数を使用します。

    armeabi ABI は ARM の Thumb (別名 Thumb-1)命令セットをサポートします。 Android.mk ファイルで LOCAL_ARM_MODE 変数を使用して別の動作を指定しない限り、NDK はデフォルトで Thumb コードを 生成します。

    armeabi-v7a

    armeabi を拡張したこの ABI には、複数の CPU 命令セット拡張機能が含まれます。 この Android 固有の ABI がサポートする命令の拡張機能は次のとおりです。

    • Thumb-2 命令セットの拡張機能。Thumb-1 同様の簡潔な 32 ビット ARM 命令と同等のパフォーマンスを提供します。
    • VFP ハードウェア FPU 命令。 具体的には、VFPv3-D16 は、16 個の専用 64 ビット浮動小数点レジスターと、ARM コアからの別の 16 個の 32 ビット レジスターを含みます。

    v7-a ARM 仕様に含まれる、 拡張 SIMD (別名 NEON)、VFPv3-D32、ThumbEE などのその他の拡張機能は、この ABI ではオプションです。 その他の拡張機能が含まれていることは保証されていないため、システムは実行時にこれらが利用可能かどうかを確認します。 利用不可の場合は別のコードパスを使用する必要があります。 この確認は、システムが MMXSSE2、x86 CPU での他の専用命令セットを確認または使用するために通常行う動作に似ています。

    これらのランタイムで行われる確認の実行方法については、cpufeatures ライブラリをご覧ください。 また、NEON 用マシンコードのビルドに関する NDK のサポートについては、NEON サポートをご覧ください。

    armeabi-v7a ABI は -mfloat-abi=softfp スイッチを使用して、コンパイラが関数呼び出し時に専用浮動小数点レジスターペアではなくコアレジスター ペアのすべての倍精度浮動小数点の値を渡す規則を適用します。 システムは FP レジスターを使用してすべての内部計算を実行できます。 この方法で計算速度が大幅に上がります。

    arm64-v8a

    この ABI は AArch64 をサポートする ARMv8 ベースの CPU 向けです。NEON 命令セットと VFPv4 命令セットも含まれます。

    詳しくは、ARMv8 テクノロジー プレビューをご覧ください。さらに詳しい内容については ARM にお問い合わせください。

    x86

    この ABI は、一般に「x86」や「IA-32」と呼ばれる命令セットをサポートする CPU 向けです。 この ABI の特徴は次のとおりです。

    • 次のようなコンパイラ フラグを使用して、通常 GCC から命令が生成される。
      -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
      

      これらのフラグは、 MMXSSESSE2SSE3SSSE3 命令セットの拡張機能に加えて、Pentium Pro 命令セットをターゲットとしています。 生成されたコードは上位の Intel 32 ビット CPU すべての間でバランスを取った形で最適化されます。

      コンパイラ フラグ、特にパフォーマンスの最適化に関連するフラグの詳細は、GCC x86 パフォーマンスのヒントをご覧ください。

    • SVR 向けの規則ではなく、標準 Linux x86 32 ビット呼び出し規則を使用する。 詳しくは、さまざまな C++ コンパイラおよびオペレーティング システム向けの呼び出し規則のセクション 6 「レジスターの使用」をご覧ください。

    ABI には、次のようなその他の任意の IA-32 命令セットの拡張機能は含まれません。

    • MOVBE
    • SSE4 のバリアント

    これらの拡張機能を使用するには、ランタイム機能プロービングを使用してこれらの機能を有効にし、これらをサポートしない端末にフォールバックを提供します。

    NDK ツールチェーンは関数を呼び出す前に 16 バイトのスタック アライメントを想定します。 デフォルトのツールとオプションで、この規則が適用されます。 アセンブリ コードを記述している場合、スタック アライメントを維持する必要があります。また、他のコンパイラもこの規則に従う必要があります。

    詳細については以下のドキュメントをご覧ください。

    x86_64

    この ABI は、一般に「x86-64」と呼ばれる命令セットをサポートする CPU 向けです。 GCC が通常次のコンパイラ フラグを使用して生成する命令をサポートします。

    -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel
    

    これらのフラグは、GCC のドキュメントにあるように、x86-64 命令セットをターゲットとします。 また、 MMXSSESSE2SSE3SSSE3SSE4.1SSE4.2POPCNT 命令セットの拡張機能もターゲットとします。 生成されたコードは上位の Intel 64 ビット CPU すべての間でバランスを取った形で最適化されます。

    コンパイラ フラグ、特にパフォーマンスの最適化に関連するフラグの詳細は、GCC x86 パフォーマンスをご覧ください。

    この ABI には、次のような他の任意の x86-64 命令セットの拡張機能は含まれません。

    • MOVBE
    • SHA
    • AVX
    • AVX2

    これらの拡張機能を使用するには、ランタイム機能プロービングを使用してこれらの機能を有効にし、これらをサポートしない端末にフォールバックを提供します。

    詳細については以下のドキュメントをご覧ください。

    特定の ABI のコードの生成

    デフォルトで、NDK はすべての非推奨 ABI をターゲットにします。 Application.mk ファイルで APP_ABI を設定することにより、単一の ABI を対象にすることができます。 以下のスニペットで、APP_ABI の使用例を示します。

    APP_ABI := arm64-v8a  # Target only arm64-v8a
    APP_ABI := all  # Target all ABIs, including those that are deprecated.
    APP_ABI := armeabi-v7a x86_64  # Target only armeabi-v7a and x86_64.
    

    APP_ABI に指定できる値の詳細については、Android.mk をご覧ください。

    ビルドシステムのデフォルト動作は、ファット APK とも呼ばれる単一の APK に各 ABI のバイナリを含めることです。 ファット APK は、単一の ABI のバイナリのみを含むものよりかなり大きくなります。APK のサイズが大きくなる代わりに、互換性が拡大するというメリットがあります。 分割 APK を利用して、APK のサイズを縮小しながら、端末の互換性を最大限維持することを強くお勧めします。

    Package Manager はインストール時にターゲット端末に最も適したマシンコードのみを解凍します。 詳細については、インストール時のネイティブ コードの自動抽出をご覧ください。

    Android プラットフォームでの ABI 管理

    このセクションでは、Android プラットフォームで APK でネイティブ コードを管理する方法について詳しく説明します。

    アプリ パッケージのネイティブ コード

    Play ストアと Package Manager の両方とも、次のパターンに一致する APK 内のファイルパスで、NDK により生成されたライブラリを検索します。

    /lib/<abi>/lib<name>.so
    

    この例では、<abi>サポート対象 ABI の一覧に含まれる ABI 名の 1 つで、<name>LOCAL_MODULE 変数に Android.mk ファイルで定義したライブラリの名前です。 APK ファイルは zip 形式のファイルにすぎないため、簡単に開くことができ、共有ネイティブ ライブラリの場所もすぐ確認できます。

    共有ネイティブ ライブラリが予想される場所で見つからないと、システムはライブラリを使用できません。 この場合は、アプリケーション自体がライブラリをコピーし、dlopen() を実行する必要があります。

    ファット APK では、各ライブラリは、対応する ABI と一致する名前のディレクトリに格納されます。 たとえば、ファット APK には以下のものが含まれています。

    /lib/armeabi/libfoo.so
    /lib/armeabi-v7a/libfoo.so
    /lib/arm64-v8a/libfoo.so
    /lib/x86/libfoo.so
    /lib/x86_64/libfoo.so
    

    注: 4.0.3 以前を実行している ARMv7 ベースの Android 端末は、armeabi ディレクトリと armeabi-v7a ディレクトリの両方が存在する場合、armeabi ディレクトリから ネイティブ ライブラリをインストールします。 これは、APK 内で /lib/armeabi//lib/armeabi-v7a/ の後にくるためです。 この問題は 4.0.4 以降で修正されています。

    Android プラットフォーム ABI サポート

    ビルド専用のシステム プロパティが示す以下の内容をもとに、Android システムは実行時にサポートする API を認識します。

    • システム イメージ自体で使用されるマシンコードに対応する、端末向けのプライマリ ABI。
    • (省略可能): システム イメージもサポートする別の ABI に対応する、セカンダリ ABI。

    このメカニズムによって、システムはインストール時にパッケージから最良のマシンコードを抽出します。

    最良のパフォーマンスを実現するには、プライマリ ABI 用に直接コンパイルすることをお勧めします。 たとえば、一般的な ARMv5TE ベース端末はプライマリ ABI armeabi のみを定義します。 一方、一般的な ARMv7 ベース端末は、各 ABI 用に生成されたアプリケーション ネイティブ バイナリを実行できるため、プライマリ ABI を armeabi-v7a、セカンダリ ABI を armeabi に定義します。

    64 ビット端末は 32 ビット バリアントもサポートします。 たとえば、arm64-v8a 端末は、armeabi コードと armeabi-v7a コードを実行することもできます。 ただし、64 ビット端末では、アプリケーションの armeabi-v7a バージョンをその端末で実行できたとしても、arm64-v8a をターゲットとしたアプリケーションの方が効率的に動作します。

    多くの x86 ベース端末も armeabi-v7a NDK バイナリと armeabi NDK バイナリを実行できます。 このような端末では、プライマリ ABI は x86 で、セカンダリ ABI は armeabi-v7a です。

    インストール時のネイティブ コードの自動抽出

    アプリケーションのインストール時に、Package Manager サービスは APK をスキャンし、次の形式の共有ライブラリを検索します。

    lib/<primary-abi>/lib<name>.so
    

    ライブラリが見つからず、かつセカンダリ ABI を定義している場合、このサービスは次の形式の共有ライブラリがないかスキャンします。

    lib/<secondary-abi>/lib<name>.so
    

    探しているライブラリが見つかると、Package Manager はライブラリをアプリケーションの data ディレクトリ(data/data/<package_name>/lib/)の下にある /lib/lib<name>.so にコピーします。

    共有オブジェクト ファイルがまったく存在しない場合も、アプリケーションのビルドとインストールは行われますが、実行時にクラッシュします。