Адресное дезинфицирующее средство

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 на производственных устройствах.

  1. Добавьте android:debuggable в манифест приложения.
  2. Установите параметр useLegacyPackaging в true в файле build.gradle вашего приложения. Дополнительную информацию см. в руководстве по скриптам оболочки .
  3. Добавьте библиотеку среды выполнения ASan в jniLibs вашего модуля приложения.
  4. Добавьте файлы 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 . Здесь есть два варианта:

  1. Быстродействующий разматыватель на основе указателя рамки. Именно он используется в соответствии с инструкциями в разделе сборки .

  2. «Медленный» механизм размотки CFI. В этом режиме ASan использует _Unwind_Backtrace . Для его работы требуется только -funwind-tables , который обычно включен по умолчанию.

Для функций malloc/realloc/free по умолчанию используется быстрая размотка стека. Для критических ошибок трассировки стека по умолчанию используется медленная размотка стека. Медленную размотку можно включить для всех трассировок стека, добавив fast_unwind_on_malloc=0 к переменной ASAN_OPTIONS в файле wrap.sh.