Различные устройства Android используют разные процессоры, которые, в свою очередь, поддерживают разные наборы инструкций. Каждая комбинация процессора и набора инструкций имеет свой собственный интерфейс двоичного кода приложения (ABI). ABI включает следующую информацию:
- Набор инструкций ЦП (и расширения), которые можно использовать.
- Порядок байтов в памяти, используемый для хранения и загрузки данных во время выполнения, имеет значение. В Android всегда используется порядок байтов little-endian.
- Соглашения о передаче данных между приложениями и системой, включая ограничения выравнивания, а также то, как система использует стек и регистры при вызове функций.
- Формат исполняемых двоичных файлов, таких как программы и разделяемые библиотеки, а также типы поддерживаемого ими контента. Android всегда использует формат ELF. Для получения дополнительной информации см. ELF System V Application Binary Interface .
- Как искажаются имена в C++. Для получения дополнительной информации см. Generic/Itanium C++ ABI .
На этой странице перечислены ABI, поддерживаемые NDK, и представлена информация о том, как работает каждый из этих ABI.
ABI также может относиться к собственному API, поддерживаемому платформой. Список подобных проблем ABI, затрагивающих 32-битные системы, см. в разделе «Ошибки ABI в 32-битных системах».
Поддерживаемые ABI
Таблица 1. ABI и поддерживаемые наборы инструкций.
| АБИ | Поддерживаемые наборы инструкций | Примечания |
|---|---|---|
armeabi-v7a | Несовместимо с устройствами ARMv5/v6. | |
arm64-v8a | Только для Armv8.0. | |
x86 | Поддержка MOVBE и SSE4 отсутствует. | |
x86_64 | Полная версия x86-64-v2 . |
Примечание: Исторически NDK поддерживал ARMv5 (armeabi), а также 32-битные и 64-битные MIPS, но поддержка этих ABI была удалена в NDK r17.
armeabi-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 ( IEEE binary64 — то же самое, что и double ).
arm64-v8a
Данный ABI предназначен для 64-битных процессоров ARM.
Подробную информацию о частях ABI, не специфичных для Android, можно найти в руководстве Arm «Изучение архитектуры» . Arm также предлагает некоторые рекомендации по портированию в руководстве «Разработка 64-битных приложений для Android» .
Вы можете использовать встроенные функции Neon в коде на C и C++, чтобы воспользоваться преимуществами расширения Advanced SIMD. В руководстве программиста Neon для Armv8-A содержится дополнительная информация о встроенных функциях Neon и программировании на Neon в целом.
На Android регистр x18, специфичный для платформы, зарезервирован для ShadowCallStack и не должен изменяться вашим кодом. Текущие версии Clang по умолчанию используют опцию -ffixed-x18 на Android, поэтому, если у вас нет написанного вручную ассемблера (или очень старого компилятора), вам не нужно об этом беспокоиться.
В этом ABI используется 128-битное значение типа long double ( IEEE binary128 ).
x86
Данный ABI предназначен для процессоров, поддерживающих набор инструкций, обычно известный как "x86", "i386" или "IA-32".
ABI Android включает базовый набор инструкций, а также расширения MMX , SSE , SSE2 , SSE3 и SSSE3 .
ABI не включает в себя какие-либо другие необязательные расширения набора инструкций IA-32, такие как MOVBE или любой вариант SSE4. Вы по-прежнему можете использовать эти расширения, если используете проверку функций во время выполнения для их включения и предоставляете резервные варианты для устройств, которые их не поддерживают.
Инструментарий NDK предполагает выравнивание стека по 16 байтам перед вызовом функции. Инструменты и параметры по умолчанию обеспечивают соблюдение этого правила. Если вы пишете ассемблерный код, вы должны убедиться в соблюдении выравнивания стека и гарантировать, что другие компиляторы также будут следовать этому правилу.
Для получения более подробной информации обратитесь к следующим документам:
- Соглашения о вызове функций для различных компиляторов C++ и операционных систем
- Руководство разработчика программного обеспечения для архитектуры Intel IA-32, том 2: Справочник по набору инструкций.
- Руководство разработчика программного обеспечения для архитектуры Intel IA-32, том 3: Руководство по системному программированию
- Двоичный интерфейс приложений System V: Дополнение к архитектуре процессора Intel386
В этом ABI используется 64-битное значение типа long double ( IEEE binary64, то же самое, что и 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, том 3: Системное программирование
В этом ABI используется 128-битное значение типа long double ( IEEE binary128 ).
Сгенерировать код для конкретного ABI.
Грэдл
Gradle (независимо от того, используется ли он через Android Studio или из командной строки) по умолчанию выполняет сборку для всех не устаревших ABI. Чтобы ограничить набор поддерживаемых вашим приложением ABI, используйте abiFilters . Например, чтобы выполнить сборку только для 64-битных ABI, установите следующую конфигурацию в вашем build.gradle :
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-build
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-файла. Настоятельно рекомендуется использовать App Bundles или APK Splits для уменьшения размера ваших 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}")
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: Включение PAC и BTI для C/C++
Включение PAC/BTI обеспечит защиту от некоторых векторов атак. PAC защищает адреса возврата, криптографически подписывая их в прологе функции и проверяя, что адрес возврата по-прежнему правильно подписан в эпилоге. BTI предотвращает переходы к произвольным местам в коде, требуя, чтобы каждая цель перехода представляла собой специальную инструкцию, которая ничего не делает, кроме как сообщает процессору, что можно перейти в это место.
Android использует инструкции PAC/BTI, которые не работают на старых процессорах, не поддерживающих новые инструкции. Защита PAC/BTI доступна только для устройств ARMv9, но тот же код можно запускать и на устройствах ARMv8: нет необходимости в нескольких вариантах библиотеки. Даже на устройствах ARMv9 PAC/BTI применяется только к 64-битному коду.
Включение PAC/BTI приведет к неболькому увеличению размера кода, обычно на 1%.
Подробное объяснение векторов атаки, на которые нацелен PAC/BTI, и принципов работы защиты см. в документе Arm «Изучение архитектуры — обеспечение защиты сложного программного обеспечения» ( PDF ).
Изменения сборки
ndk-build
Установите параметр 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 не будет включена. Вы можете использовать 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-системы за исправленной версией.