Разные устройства Android используют разные процессоры, которые, в свою очередь, поддерживают разные наборы инструкций. Каждая комбинация ЦП и набора команд имеет свой собственный двоичный интерфейс приложения (ABI). ABI включает следующую информацию:
- Набор инструкций ЦП (и расширения), который можно использовать.
- Порядок байтов памяти сохраняется и загружается во время выполнения. Android всегда имеет прямой порядок байтов.
- Соглашения о передаче данных между приложениями и системой, включая ограничения выравнивания, а также то, как система использует стек и регистрирует, когда она вызывает функции.
- Формат исполняемых двоичных файлов, таких как программы и общие библиотеки, а также типы поддерживаемого ими контента. Android всегда использует ELF. Для получения дополнительной информации см . Двоичный интерфейс приложения ELF System V.
- Как искажаются имена C++. Дополнительные сведения см. в разделе Generic/Itanium C++ ABI .
На этой странице перечислены ABI, поддерживаемые NDK, и представлена информация о том, как работает каждый ABI.
ABI также может ссылаться на собственный API, поддерживаемый платформой. Список подобных проблем ABI, влияющих на 32-разрядные системы, см. в разделе «Ошибки 32-разрядного ABI» .
Поддерживаемые ABI
АБИ | Поддерживаемые наборы инструкций | Примечания |
---|---|---|
armeabi-v7a | Несовместимо с устройствами ARMv5/v6. | |
arm64-v8a | Только Армв8.0. | |
x86 | Нет поддержки MOVBE или SSE4. | |
x86_64 | Полный x86-64-v1 , но только частичный x86-64-v2 (без CMPXCHG16B или LAHF-SAHF). |
Примечание. Исторически NDK поддерживал ARMv5 (armeabi), а также 32-битные и 64-битные MIPS, но поддержка этих ABI была удалена в NDK r17.
армеаби-v7a
Этот ABI предназначен для 32-битных процессоров ARM. В него входят Thumb-2 и Neon.
Информацию о частях ABI, не специфичных для Android, см. в разделе Двоичный интерфейс приложения (ABI) для архитектуры ARM.
Системы сборки NDK по умолчанию генерируют код Thumb-2, если вы не используете LOCAL_ARM_MODE
в своем Android.mk
для ndk-build или ANDROID_ARM_MODE
при настройке CMake .
Дополнительную информацию об истории Neon см. в разделе Поддержка Neon .
По историческим причинам этот ABI использует -mfloat-abi=softfp
в результате чего все значения float
передаются в целочисленных регистрах, а все значения double
передаются в парах целочисленных регистров при вызове функций. Несмотря на название, это влияет только на соглашение о вызовах с плавающей запятой: компилятор по-прежнему будет использовать аппаратные инструкции с плавающей запятой для арифметических операций.
Этот ABI использует 64-битный long double
( IEEEbinary64 то же самое, что double
).
рука64-v8a
Этот ABI предназначен для 64-битных процессоров ARM.
См. статью Arm « Изучите архитектуру» для получения полной информации о частях ABI, не специфичных для Android. Arm также предлагает несколько советов по портированию в 64-битной разработке Android .
Вы можете использовать встроенные функции Neon в коде C и C++, чтобы воспользоваться преимуществами расширения Advanced SIMD. Руководство программиста Neon для Armv8-A предоставляет дополнительную информацию о встроенных функциях Neon и программировании Neon в целом.
В Android регистр x18, специфичный для платформы, зарезервирован для ShadowCallStack , и ваш код не должен затрагивать его. Текущие версии Clang по умолчанию используют опцию -ffixed-x18
на Android, поэтому, если у вас нет рукописного ассемблера (или очень старого компилятора), вам не нужно об этом беспокоиться.
Этот ABI использует 128-битный long double
( IEEEbinary128 ).
х86
Этот ABI предназначен для процессоров, поддерживающих набор команд, обычно известный как «x86», «i386» или «IA-32».
ABI Android включает базовый набор инструкций, а также расширения 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
( IEEEbinary64 то же самое, что и double
, а не более распространенный 80-битный long double
предназначенный только для Intel).
x86_64
Этот ABI предназначен для процессоров, поддерживающих набор команд, обычно называемый «x86-64».
ABI Android включает в себя базовый набор инструкций, а также 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
( IEEEbinary128 ).
Генерировать код для конкретного ABI
Градл
Gradle (независимо от того, используется ли он через Android Studio или из командной строки) по умолчанию строится для всех неустаревших ABI. Чтобы ограничить набор ABI, поддерживаемых вашим приложением, используйте abiFilters
. Например, чтобы выполнить сборку только для 64-разрядных ABI, установите в build.gradle
следующую конфигурацию:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-сборка
По умолчанию ndk-build строит для всех неустаревших ABI. Вы можете настроить таргетинг на определенные ABI, установив APP_ABI
в файле Application.mk . В следующем фрагменте показано несколько примеров использования 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 .
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 . Толстый APK значительно больше, чем тот, который содержит только двоичные файлы для одного ABI; компромиссом является более широкая совместимость, но за счет более крупного APK. Настоятельно рекомендуется использовать пакеты приложений или разделения APK, чтобы уменьшить размер APK-файлов, сохраняя при этом максимальную совместимость с устройствами.
Во время установки менеджер пакетов распаковывает только наиболее подходящий машинный код для целевого устройства. Подробности см. в разделе Автоматическое извлечение машинного кода во время установки .
Управление ABI на платформе Android
В этом разделе представлена подробная информация о том, как платформа Android управляет собственным кодом в APK.
Нативный код в пакетах приложений
И Play Store, и диспетчер пакетов ожидают найти библиотеки, созданные NDK, в путях к файлам внутри APK, соответствующие следующему шаблону:
/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
Примечание. Устройства Android на базе ARMv7 под управлением версии 4.0.3 или более ранней версии устанавливают собственные библиотеки из каталога armeabi
вместо каталога armeabi-v7a
если оба каталога существуют. Это связано с тем, что /lib/armeabi/
идет после /lib/armeabi-v7a/
в APK. Эта проблема исправлена начиная с версии 4.0.4.
Поддержка ABI платформы Android
Система Android во время выполнения знает, какие ABI она поддерживает, поскольку свойства системы, специфичные для сборки, указывают:
- Основной ABI для устройства, соответствующий машинному коду, используемому в самом образе системы.
- Необязательно, вторичные ABI, соответствующие другим ABI, которые также поддерживает образ системы.
Этот механизм гарантирует, что система извлекает лучший машинный код из пакета во время установки.
Для достижения наилучшей производительности следует компилировать непосредственно для основного ABI. Например, типичное устройство на базе ARMv5TE определяет только основной ABI: armeabi
. Напротив, типичное устройство на базе ARMv7 будет определять основной ABI как armeabi-v7a
, а вторичный — как armeabi
, поскольку оно может запускать собственные двоичные файлы приложений, созданные для каждого из них.
64-битные устройства также поддерживают 32-битные варианты. На примере устройств Arm64-v8a устройство также может запускать код Armeabi и Armeabi-v7a. Однако обратите внимание, что ваше приложение будет работать намного лучше на 64-битных устройствах, если оно нацелено на Arm64-v8a, а не на устройство, на котором работает версия вашего приложения Armeabi-v7a.
Многие устройства на базе x86 также могут запускать двоичные файлы armeabi-v7a
и armeabi
NDK. Для таких устройств основным ABI будет x86
, а вторым — armeabi-v7a
.
Вы можете принудительно установить APK для определенного ABI . Это полезно для тестирования. Используйте следующую команду:
adb install --abi abi-identifier path_to_apk
Автоматическое извлечение собственного кода во время установки.
При установке приложения служба диспетчера пакетов сканирует APK и ищет общие библиотеки вида:
lib/<primary-abi>/lib<name>.so
Если ничего не найдено и вы определили вторичный ABI, служба сканирует общие библиотеки в форме:
lib/<secondary-abi>/lib<name>.so
Когда он находит нужные библиотеки, менеджер пакетов копирует их в /lib/lib<name>.so
в каталоге собственных библиотек приложения ( <nativeLibraryDir>/
). Следующие фрагменты извлекают nativeLibraryDir
:
Котлин
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}")
Ява
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: включение PAC и BTI для C/C++
Включение PAC/BTI обеспечит защиту от некоторых векторов атак. PAC защищает адреса возврата, подписывая их криптографически в прологе функции и проверяя, правильно ли подписан адрес возврата в эпилоге. BTI предотвращает переход к произвольным местам вашего кода, требуя, чтобы каждая цель ветвления представляла собой специальную инструкцию, которая ничего не делает, а лишь сообщает процессору, что туда можно приземлиться.
Android использует инструкции PAC/BTI, которые ничего не делают на старых процессорах, не поддерживающих новые инструкции. Только устройства ARMv9 будут иметь защиту PAC/BTI, но вы можете запускать тот же код и на устройствах ARMv8: нет необходимости использовать несколько вариантов вашей библиотеки. Даже на устройствах ARMv9 PAC/BTI применяется только к 64-битному коду.
Включение PAC/BTI приведет к небольшому увеличению размера кода, обычно на 1%.
Подробное объяснение векторов атак на цель PAC/BTI и принципов работы защиты см. в документе Arm «Изучите архитектуру — обеспечение защиты сложного программного обеспечения» ( PDF ).
Изменения сборки
ndk-сборка
Установите LOCAL_BRANCH_PROTECTION := standard
в каждом модуле вашего Android.mk.
CMake
Используйте target_compile_options($TARGET PRIVATE -mbranch-protection=standard)
для каждой цели в вашем CMakeLists.txt.
Другие системы сборки
Скомпилируйте свой код, используя -mbranch-protection=standard
. Этот флаг работает только при компиляции для ABI Arm64-v8a. Вам не нужно использовать этот флаг при связывании.
Поиск неисправностей
Нам не известно о каких-либо проблемах с поддержкой компилятором 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) содержат ошибку в рукописном ассемблере, вызывающую сбои PAC. Обновите до текущей версии OpenSSL.
Старые версии некоторых систем DRM приложений генерируют код, который нарушает требования PAC/BTI. Если вы используете DRM приложения и видите проблемы при включении PAC/BTI, обратитесь к поставщику DRM за исправленной версией.