Android ABI   您所在的位置:网站首页 abi兼容解决方法 Android ABI  

Android ABI  

#Android ABI  | 来源: 网络整理| 查看: 265

不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。ABI 包含以下信息:

可使用的 CPU 指令集(和扩展指令集)。 运行时内存存储和加载的字节顺序。Android 始终是 little-endian。 在应用和系统之间传递数据的规范(包括对齐限制),以及系统调用函数时如何使用堆栈和寄存器。 可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。Android 始终使用 ELF。如需了解详情,请参阅 ELF System V 应用二进制接口。 如何重整 C++ 名称。如需了解详情,请参阅 Generic/Itanium C++ ABI。

本页列举了 NDK 支持的 ABI,并且介绍了每个 ABI 的运行原理。

ABI 还可以指平台支持的原生 API。如需影响 32 位系统的此类 ABI 问题列表,请参阅 32 位 ABI 错误。

支持的 ABI

表 1. ABI 和支持的指令集。

ABI 支持的指令集 备注 armeabi-v7a armeabi Thumb-2 VFPv3-D16 与 ARMv5/v6 设备不兼容。 arm64-v8a AArch64 仅限 Armv8.0。 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 仅限 x86-64-v1。

注意:NDK 以前支持 ARMv5 (armeabi) 以及 32 位和 64 位 MIPS,但 NDK r17 已不再支持。

armeabi-v7a

此 ABI 适用于 32 位 ARM CPU。它包含 Thumb-2 和 Neon (VFP) 硬件浮点指令,具体是指具有 16 个专用 64 位浮点寄存器的 VFPv3-D16。

如需详细了解 ABI 中并非特定于 Android 的部分,请参阅 ARM 架构的应用二进制接口 (ABI)

默认情况下,NDK 构建系统会生成 Thumb-2 代码,除非您在 Android.mk 中针对 ndk-build 使用 LOCAL_ARM_MODE,或在配置 CMake 时使用 ANDROID_ARM_MODE。

包括高级 SIMD (Neon) 和 VFPv3-D32 在内的其他扩展程序都是可选的。如需了解详情,请参阅 Neon 支持。

此 ABI 使用 -mfloat-abi=softfp 来强制执行以下规则:编译器在调用函数时必须传递整数寄存器中的所有 float 值以及整数寄存器对中的所有 double 值。这只会影响调用规范。编译器仍会使用硬件浮点指令。

此 ABI 使用 64 位 long double(与 double 相同的 IEEE binary64)。

arm64-v8a

此 ABI 适用于 64 位 ARM CPU。

如需了解 ABI 中并非特定于 Android 的部分的完整详情,请参阅 Arm 的了解架构。Arm 还针对 64 位 Android 开发提供了一些移植方面的建议。

您可以在 C 和 C++ 代码中使用 Neon 固有特性来充分利用高级 SIMD 扩展指令集。针对 Armv8-A 的 Neon 程序员指南详细介绍了 Neon 固有特性和 Neon 程序设计的概况。

在 Android 中,特定于平台的 x18 寄存器专用于 ShadowCallStack,不应由您的代码使用。当前的 Clang 版本默认使用 Android 中的 -ffixed-x18 选项,因此除非您使用的是手写汇编程序(或非常旧的编译器),否则无需担心这一点。

此 ABI 使用 128 位 long double (IEEE binary128)。

x86

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

Android 的 ABI 包括基础指令集以及 MMX、SSE、SSE2 和 SSE3 和 SSSE3 扩展指令集。

ABI 不含任何其他可选 IA-32 扩展指令集,例如 MOVBE 或 SSE4 的任何变体。您仍可使用这些扩展指令集,只要您使用运行时功能探测来启用它们,并且为不支持它们的设备提供回退机制。

NDK 工具链假设在函数调用之前进行 16 字节堆栈对齐。默认工具和选项会强制实施此规则。如果编写的是汇编代码,必须确保堆栈对齐,而且其他编译器也遵守此规则。

请参阅以下文档了解更多详情:

不同 C++ 编译器和操作系统的调用规范 Intel IA-32 Intel 架构软件开发者手册第 2 卷:指令集参考 Intel IA-32 Intel 架构软件开发者手册第 3 卷:系统编程指南 System V 应用二进制接口:Intel386 处理器架构补充

此 ABI 使用 64 位 long double(与 double 相同的 IEEE binary64,而不是更为常见的仅限 Intel 的 80 位 long double)。

x86_64

此 ABI 适用于支持通常称为“x86-64”的指令集的 CPU。

Android 的 ABI 包括基础指令集以及 MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2 和 POPCNT 指令。

ABI 不含任何其他可选 x86-64 扩展指令集,例如 MOVBE、SHA 或 AVX 的任何变体。 您仍可使用这些扩展指令集,只要您使用运行时功能探测来启用它们,并且为不支持它们的设备提供回退机制。

请参阅以下文档了解更多详情:

不同 C++ 编译器和操作系统的调用规范 Intel64 和 IA-32 架构软件开发者手册第 2 卷:指令集参考 Intel64 和 IA-32 Intel 架构软件开发者手册第 3 卷:系统编程

此 ABI 使用 128 位 long double (IEEE binary128)。

为特定 ABI 生成代码 Gradle

默认情况下,Gradle(无论是通过 Android Studio 使用,还是从命令行使用)会针对所有非弃用 ABI 进行构建。要限制应用支持的 ABI 集,请使用 abiFilters。例如,要仅针对 64 位 ABI 进行构建,请在 build.gradle 中设置以下配置:

android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'x86_64' } } } ndk-build

默认情况下,ndk-build 会针对所有非弃用 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。

CMake

使用 CMake 时,您一次只能针对一个 ABI 进行构建,并且必须明确指定 ABI。为此,您需要使用 ANDROID_ABI 变量,而此变量必须在命令行中指定(无法在 CMakeLists.txt 中设置)。例如:

$ cmake -DANDROID_ABI=arm64-v8a ... $ cmake -DANDROID_ABI=armeabi-v7a ... $ cmake -DANDROID_ABI=x86 ... $ cmake -DANDROID_ABI=x86_64 ...

对于必须传递到 CMake 以使用 NDK 进行构建的其他标志,请参阅 CMake 指南。

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

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

Android 平台上的 ABI 管理

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

应用软件包中的原生代码

Play 商店和软件包管理器都希望能在 APK 中符合以下格式的文件路径上找到 NDK 生成的库:

/lib//lib.so

其中, 是支持的 ABI 下列出的 ABI 名称之一, 是您为 Android.mk 文件中的 LOCAL_MODULE 变量定义库时使用的库名称。由于 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,因为 build 特定的系统属性会指示:

设备的主要 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-v7a 和 armeabi NDK 二进制文件。对于这些设备,主 ABI 将是 x86,辅助 ABI 是 armeabi-v7a。

您可以为特定 ABI 强制安装 apk。这在测试时非常有用。请使用以下命令:

adb install --abi abi-identifier path_to_apk

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

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

lib//lib.so

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

lib//lib.so

找到所需的库时,软件包管理器会将它们复制到应用的原生库目录 (/) 下的 /lib/lib.so。以下代码段会收到 nativeLibraryDir:

Kotlin import android.content.pm.PackageInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager ... val ainfo = this.applicationContext.packageManager.getApplicationInfo( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ) Log.v(TAG, "native library dir ${ainfo.nativeLibraryDir}") Java import android.content.pm.PackageInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; ... ApplicationInfo ainfo = this.getApplicationContext().getPackageManager().getApplicationInfo ( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ); Log.v( TAG, "native library dir " + ainfo.nativeLibraryDir );

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

ARMv9:为 C/C++ 启用 PAC 和 BTI 重要提示:PAC 和 BTI 是用于内存调试和减少错误的众多工具中的两种。如需简要了解所有工具,请参阅调试和减少内存错误。

启用 PAC/BTI 可防范一些攻击途径。PAC 通过以下方式来保护返回地址:在函数 prolog 中以加密形式对这些地址进行签名,并检查返回地址是否仍在 epilog 中进行了正确签名。为了避免跳转到代码中的任意位置,BTI 要求每个分支目标都是一条特殊指令,除了告知处理器可以到达这个位置之外,该指令不会执行任何其他操作。

Android 使用 PAC/BTI 指令,这些指令在不支持新指令的旧版处理器上不会执行任何操作。只有 ARMv9 设备具有 PAC/BTI 保护功能,但您也可以在 ARMv8 设备上运行相同的代码:无需库的多个变体。即使在 ARMv9 设备上,PAC/BTI 也仅适用于 64 位代码。

启用 PAC/BTI 后,代码大小会略有增加,通常为 1%。

如需详细了解攻击途径 PAC/BTI 目标以及保护功能的运作方式,请参阅 Arm 的了解架构 - 为复杂软件提供保护 (PDF)。

build 变更 注意:在将代码视为攻击者的目标时,我们建议您也使用 CFI 进行构建。CFI 和 PAC/BTI 类似,但互补。 ndk-build

在 Android.mk 的每个模块中设置 LOCAL_BRANCH_PROTECTION := standard。

CMake

对 CMakeLists.txt 中的每个目标使用 target_compile_options($TARGET PRIVATE -mbranch-protection=standard)。

其他构建系统

使用 -mbranch-protection=standard 编译代码。仅当针对 arm64-v8a ABI 进行编译时,此标志才有效。建立关联时无需使用此标志。

问题排查

我们不了解编译器对 PAC/BTI 的支持是否存在任何问题,但:

请注意,不要在建立关联时混用 BTI 和非 BTI 代码,因为这会导致库未启用 BTI 保护。您可以使用 llvm-readelf 检查生成的库是否包含 BTI 备注。 $ llvm-readelf --notes LIBRARY.so [...] Displaying notes found in: .note.gnu.property Owner Data size Description GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0 (property note) Properties: aarch64 feature: BTI, PAC [...] $

旧版 OpenSSL(1.1.1i 之前的版本)在手写汇编程序中存在一个 bug,会导致 PAC 失败。升级到当前的 OpenSSL。

某些应用 DRM 系统的旧版本会生成违反 PAC/BTI 要求的代码。如果您使用的是应用 DRM 并且在启用 PAC/BTI 时遇到问题,请与您的 DRM 供应商联系以获取修复的版本。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有