ABI 管理

不同的 Android 手机使用不同的 CPU,进而支持不同的指令集。 CPU 与指令集的每种组合都有专属的应用二进制界面,即 ABI。 ABI 可以非常精确地定义应用的机器代码在运行时如何与系统交互。 您必须为应用要使用的每个 CPU 架构指定 ABI。

典型的 ABI 包含以下信息:

  • 机器代码应使用的 CPU 指令集。
  • 运行时内存存储和加载的字节顺序。
  • 可执行二进制文件(例如程序和共享库)的格式,以及其支持的内容类型。
  • 用于在代码与系统之间传递数据的各种约定。 这些约定包括对齐限制,以及系统调用函数时如何使用堆栈和寄存器。
  • 运行时可用于机器代码的函数符号列表,通常来自非常具体的库集。

本页列举 NDK 支持的 ABI,并提供有关每个 ABI 如何运行的信息。 如需 32 位系统上的 ABI 问题列表,请参阅 32 位 ABI 错误

支持的 ABI

每个 ABI 支持一个或多个指令集。 表 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 适用于基于 ARM 且至少支持 ARMv5TE 指令集的 CPU。 请参阅以下文档了解详情:

    AAPCS 标准将 EABI 定义为一系列类似但不同的 ABI。 此外,Android 还采用小字节序 ARM GNU/Linux ABI

    此 ABI 不支持硬件辅助的浮点计算。 相反,所有浮点运算都使用编译器 libgcc.a 静态库中的软件帮助程序函数。

    armeabi ABI 支持 ARM 的 Thumb(亦称 Thumb-1)指令集。 NDK 默认生成 Thumb 代码,除非您在 Android.mk 文件中使用 LOCAL_ARM_MODE 变量指定不同的行为 。

    armeabi-v7a

    此 ABI 可扩展 armeabi 以包括多个 CPU 指令集扩展。 此 Android 特定 ABI 支持的指令扩展包括:

    • Thumb-2 指令集扩展,其性能堪比 32 位 ARM 指令,简洁性类似于 Thumb-1。
    • VFP 硬件 FPU 指令。 更具体地说,VFPv3-D16 除了 ARM 核心中的 16 个 32 位寄存器之外,还包含 16 个专用的 64 位浮点寄存器。

    v7-a ARM 规格描述的其他扩展,包括 高级 SIMD(亦称 NEON)、VFPv3-D32 和 ThumbEE,都是此 ABI 的可选扩展。 由于不能保证它们存在,因此系统在运行时应检查扩展是否可用。 如果不可用,您必须使用替代代码路径。 此检查类似于系统在检查或使用 MMXSSE2 及 x86 CPU 上其他专用指令集时所执行的检查。

    如需有关如何执行这些运行时检查的信息,请参阅 cpufeatures。 此外,如需有关 NDK 支持为 NEON 构建机器代码的信息,请参阅 NEON 支持

    armeabi-v7a ABI 使用 -mfloat-abi=softfp 开关来强制实施规则,要求编译器在函数调用期间必须通过核心寄存器对传递所有双精度值,而不是通过专用的浮点寄存器对进行传递。 系统可以使用 FP 寄存器执行所有内部计算。 这样可以极大加快计算速度。

    arm64-v8a

    此 ABI 适用于基于 ARMv8 且支持 AArch64 的 CPU。它还包含 NEON 和 VFPv4 指令集。

    如需了解详细信息,请参阅 ARMv8 技术预览,并联系 ARM 了解进一步的详细信息。

    x86

    此 ABI 适用于支持通常称为“x86”或“IA-32”的指令集的 CPU。 此 ABI 的特性包括:

    • 指令一般由 GCC 使用编译器标记生成,如下所示:
      -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
      

      这些标记指向 Pentium Pro 指令集,以及 MMXSSESSE2SSE3SSSE3 指令集扩展。 生成的代码在主流 Intel 32 位 CPU 之间进行均衡优化。

      如需了解有关编译器标记的详细信息,特别是与性能优化相关的信息,请参阅 GCC x86 性能提示

    • 使用标准 Linux x86 32 位调用约定,与 SVR 使用的约定相反。 如需了解详细信息,请参阅不同 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
    

    这些标记指向 x86-64 指令集(根据 GCC 文档), 以及 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 指定的值的详细信息,请参阅 Application.mk

    构建系统的默认行为是将每个 ABI 的二进制文件包括在单个 APK(也称为胖 APK)内。 与仅包含单个 ABI 的二进制文件的 APK 相比,胖 APK 要大得多;权衡方式是兼容性更广,但 APK 更大。 强烈建议您利用分包 APK 来减小 APK 的大小,同时仍保持最大限度的设备兼容性。

    在安装时,软件包管理器只解包最适合目标设备的机器代码。 如需了解详细信息,请参阅安装时自动解压缩原生代码

    Android 平台上的 ABI 管理

    本节详细说明 Android 平台如何管理 APK 中的原生代码。

    应用包中的原生代码

    Play 商店和软件包管理器专家预期在 APK 中符合以下模式的文件路径中找到 NDK 生成的库:

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

    这里的 <abi>支持的 ABI 下列出的 ABI 名称之一,<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 目录, 如果这两个目录都存在)安装原生库。 这是因为在 APK 中,/lib/armeabi//lib/armeabi-v7a/ 之后。 从 4.0.4 开始,此问题便得以修复。

    Android 平台 ABI 支持

    Android 系统在运行时知道其支持哪些 ABI,因为版本特定的系统属性会指出:

    • 与系统映像本身使用的机器代码对应的设备主要 ABI。
    • (可选)与系统映像也支持的其他 ABI 对应的辅助 ABI。

    此机制可确保系统在安装时从软件包提取最佳机器代码。

    为实现最佳性能,应直接针对主要 ABI 进行编译。 例如,基于 ARMv5TE 的典型设备只会定义主要 ABI:armeabi。 相比而言,基于 ARMv7 的典型设备将主要 ABI 定义为 armeabi-v7a,而将辅助 ABI 定义为 armeabi,因为它可以运行针对每个 ABI 生成的应用原生二进制文件。

    64 位设备也支持其 32 位变体。 以 arm64-v8a 设备为例,该设备也可以运行 armeabi 和 armeabi-v7a 代码。 但请注意,当应用指向 arm64-v8a 而非依赖于运行 armeabi-v7a 版应用的设备时,其在 64 位设备上的性能要好得多。

    许多基于 x86 的设备也可运行 armeabi-v7aarmeabi NDK 二进制文件。 对于这些设备,主要 ABI 将是 x86,辅助 ABI 是 armeabi-v7a

    安装时自动解压缩原生代码

    安装应用时,软件包管理器服务会扫描 APK,并查找任何以下形式的共享库:

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

    如果找不到,并且您已定义辅助 ABI,则该服务会扫描以下形式的共享库:

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

    找到所需的库时,软件包管理器会将其复制到应用的 data 目录 (data/data/<package_name>/lib/) 下的 /lib/lib<name>.so

    根本没有共享对象文件时,应用也会进行构建和安装,但在运行时会崩溃。