Android NDK поддерживает Address Sanitizer (также известный как ASan) начиная с уровня API 27 (Android O MR 1).
ASan — это быстрый инструмент на основе компилятора для обнаружения ошибок, связанных с памятью в нативном коде. ASan обнаруживает:
- Переполнение/недополнение буфера стека и кучи
- Использование памяти после освобождения памяти
- Использование стека вне области видимости
- Двойной бесплатный/дикий бесплатный
Накладные расходы ASan на ЦП составляют примерно 2 раза, накладные расходы на размер кода — от 50% до 2 раз, а накладные расходы на память значительны (зависят от используемых шаблонов выделения памяти, но примерно в 2 раза).
Пример приложения
В примере приложения показано, как настроить вариант сборки для Asan.
Строить
Для компиляции нативного (JNI) кода вашего приложения с помощью Address Sanitizer выполните следующие действия:
ndk-build
В вашем файле Application.mk:
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
Для каждого модуля в вашем файле Android.mk:
LOCAL_ARM_MODE := arm
CMake
В файле build.gradle вашего модуля:
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
Для каждой цели в вашем файле CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
Бегать
Начиная с Android O MR1 (уровень API 27), приложение может предоставить скрипт оболочки , который может обернуть или заменить процесс приложения. Это позволяет отлаживаемому приложению настраивать запуск, что дает возможность использовать ASan на производственных устройствах.
- Добавьте
android:debuggableв манифест приложения. - Установите параметр
useLegacyPackagingвtrueв файлеbuild.gradleвашего приложения. Дополнительную информацию см. в руководстве по скриптам оболочки . - Добавьте библиотеку среды выполнения ASan в
jniLibsвашего модуля приложения. Добавьте файлы
wrap.shсо следующим содержимым в каждую директорию в вашем каталогеsrc/main/resources/lib.#!/system/bin/sh HERE="$(cd "$(dirname "$0")" && pwd)" export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi "$@"
Предположим, что модуль приложения вашего проекта называется app . В итоге структура каталогов должна включать следующее:
<project root>
└── app
└── src
└── main
├── jniLibs
│ ├── arm64-v8a
│ │ └── libclang_rt.asan-aarch64-android.so
│ ├── armeabi-v7a
│ │ └── libclang_rt.asan-arm-android.so
│ ├── x86
│ │ └── libclang_rt.asan-i686-android.so
│ └── x86_64
│ └── libclang_rt.asan-x86_64-android.so
└── resources
└── lib
├── arm64-v8a
│ └── wrap.sh
├── armeabi-v7a
│ └── wrap.sh
├── x86
│ └── wrap.sh
└── x86_64
└── wrap.sh
Трассировка стека
Address Sanitizer должен разматывать стек при каждом вызове malloc / realloc / free . Здесь есть два варианта:
Быстродействующий разматыватель на основе указателя рамки. Именно он используется в соответствии с инструкциями в разделе сборки .
«Медленный» механизм размотки CFI. В этом режиме ASan использует
_Unwind_Backtrace. Для его работы требуется только-funwind-tables, который обычно включен по умолчанию.
Для функций malloc/realloc/free по умолчанию используется быстрая размотка стека. Для критических ошибок трассировки стека по умолчанию используется медленная размотка стека. Медленную размотку можно включить для всех трассировок стека, добавив fast_unwind_on_malloc=0 к переменной ASAN_OPTIONS в файле wrap.sh.