Limpador de endereços

O Android NDK tem suporte ao Limpador de endereços (link em inglês), também conhecido como ASan, do nível 27 em diantes da API (Android O MR 1).

O ASan é uma ferramenta rápida baseada em compilador para detectar bugs de memória no código nativo. O ASan detecta:

  • Overflow/underflow do buffer de heap e pilha
  • Uso de heap depois da liberação de memória
  • Uso de pilha fora do escopo
  • Double free/wild free

A sobrecarga de CPU do ASan é de aproximadamente duas vezes, a de tamanho do código está entre 50% e 100%, e a de memória é grande e depende dos padrões de alocação, mas é de cerca de duas vezes.

App de exemplo

Um app de exemplo (link em inglês) mostra como configurar uma variante de build para o ASan.

Criar

Para criar o código nativo do app (JNI) com o Limpador de endereços, faça o seguinte:

ndk-build

No Application.mk:

APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address

Para cada módulo do Android.mk:

LOCAL_ARM_MODE := arm

CMake

No build.gradle do seu módulo:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                // Can also use system or none as ANDROID_STL.
                arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
            }
        }
    }
}

Para cada destino no CMakeLists.txt:

target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)

Executar

A partir do Android O MR1 (API de nível 27), um app pode fornecer um script de shell de agrupamento que pode ajustar ou substituir o processo do app. Assim um app depurável consegue personalizar a própria inicialização, o que permite o uso do ASan em dispositivos de produção.

  1. Adicione android:debuggable ao manifesto do aplicativo.
  2. Defina useLegacyPackaging como true no arquivo build.gradle do app. Consulte o guia de script de shell de agrupamento para mais informações.
  3. Adicione a biblioteca de tempo de execução do ASan ao jniLibs do módulo do app.
  4. Adicione arquivos wrap.sh com os seguintes conteúdos a cada diretório do seu 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
    "$@"
    

Se o nome do módulo do app do seu projeto for app, sua estrutura de diretórios final precisará incluir o seguinte:

<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

Stack traces

O Limpador de endereços precisa desvincular a pilha em cada chamada malloc/realloc/free. Há duas opções aqui:

  1. Um unwinder de frame “rápido” baseado em ponteiro. É o que é usado de acordo com as instruções na seção de build.

  2. Unwinder de CFI "lento". Nesse modo, o limpador usa _Unwind_Backtrace. Ele requer apenas -funwind-tables, que normalmente é ativado por padrão.

O unwinder rápido é o padrão para malloc/realloc/free. O unwinder lento é o padrão para stack traces fatais. O unwinder lento pode ser ativado para todos os stack traces adicionando fast_unwind_on_malloc=0 à variável ASAN_OPTIONS no seu wrap.sh.