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.
- Adicione
android:debuggable
ao manifesto do aplicativo. - Defina
useLegacyPackaging
comotrue
no arquivobuild.gradle
do app. Consulte o guia de script de shell de agrupamento para mais informações. - Adicione a biblioteca de tempo de execução do ASan ao
jniLibs
do módulo do app. Adicione arquivos
wrap.sh
com os seguintes conteúdos a cada diretório do seusrc/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:
Um unwinder de frame “rápido” baseado em ponteiro. É o que é usado de acordo com as instruções na seção de build.
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.