O Limpador de endereços (ASan, na sigla em inglês) é 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
O ASan é executado em ARM de 32 e de 64 bits, além de x86 e x86-64. A sobrecarga de CPU do ASan é aproximadamente duas vezes, a sobrecarga de tamanho do código está entre 50% e duas vezes e a sobrecarga de memória é grande (dependendo dos padrões de alocação, mas cerca de duas vezes).
O Android 10 e a ramificação de lançamento mais recente do AOSP no AArch64 oferecem suporte ao Limpador de endereços assistido por hardware (HWASan), uma ferramenta semelhante com menor sobrecarga de RAM e uma variedade maior de bugs detectados. O HWASan detecta o uso da pilha após o retorno, além dos bugs detectados pelo ASan.
O HWASan tem sobrecarga de CPU e tamanho de código semelhantes, mas uma sobrecarga de RAM muito menor (15%). O HWASan não é determinístico. Há apenas 256 valores de tag possíveis, então há uma probabilidade de 0,4% de perder qualquer bug. O HWASan não tem as zonas vermelhas de tamanho limitado do ASan para detectar overflows e a quarentena de capacidade limitada para detectar o uso após a liberação. Portanto, não importa para o HWASan o tamanho do overflow ou há quanto tempo a memória foi desalocada. Isso torna o HWASan melhor que o ASan. Saiba mais sobre o design do HWASan ou sobre o uso de HWASan no Android.
O ASan detecta overflows globais/de pilha, além de overflows de heap, e é rápido com sobrecarga mínima de memória.
Este documento descreve como criar e executar partes/todo o Android com o ASan. Se você estiver criando um app SDK/NDK com o ASan, consulte Limpador de endereços em vez disso.
Limpar executáveis individuais com o ASan
Adicione LOCAL_SANITIZE:=address ou sanitize: { address: true } à regra de build do executável. Você pode pesquisar o código para exemplos atuais ou para encontrar os outros limpadores disponíveis.
Quando um bug é detectado, o ASan imprime um relatório detalhado na saída padrão e no logcat e, em seguida, falha no processo.
Limpar bibliotecas compartilhadas com o ASan
Devido à forma como o ASan funciona, uma biblioteca criada com o ASan só pode ser usada por um executável criado com o ASan.
Para limpar uma biblioteca compartilhada usada em vários executáveis, nem todos criados com o ASan, você precisa de duas cópias da biblioteca. A maneira recomendada de fazer isso é adicionar o seguinte ao Android.mk do módulo em questão:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
Isso coloca a biblioteca em /system/lib/asan em vez de
/system/lib. Em seguida, execute o executável com:
LD_LIBRARY_PATH=/system/lib/asan
Para daemons do sistema, adicione o seguinte à seção apropriada de /init.rc ou /init.$device$.rc.
setenv LD_LIBRARY_PATH /system/lib/asan
Verifique se o processo está usando bibliotecas de /system/lib/asan quando presentes lendo /proc/$PID/maps. Se não estiver, talvez seja necessário desativar o SELinux:
adb rootadb shell setenforce 0# restart the process with adb shell kill $PID # if it is a system service, or may be adb shell stop; adb shell start.
Stack traces melhores
O ASan usa um unwinder rápido baseado em ponteiro de frames para gravar um stack trace para cada evento de alocação e desalocação de memória no programa. A maior parte do Android é criada sem ponteiros de frames. Como resultado, você geralmente recebe apenas um ou dois frames significativos. Para corrigir isso, recrie a biblioteca com o ASan (recomendado!) ou com:
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
Ou defina ASAN_OPTIONS=fast_unwind_on_malloc=0 no ambiente de processo. O último pode ser muito intensivo em termos de CPU, dependendo da carga.
Simbolização
Inicialmente, os relatórios do ASan contêm referências a deslocamentos em binários e bibliotecas compartilhadas. Há duas maneiras de receber informações de arquivo de origem e linha:
- Verifique se o binário
llvm-symbolizerestá presente em/system/bin.llvm-symbolizeré criado com base em fontes emthird_party/llvm/tools/llvm-symbolizer. - Filtre o relatório pelo script
external/compiler-rt/lib/asan/scripts/symbolize.py.
A segunda abordagem pode fornecer mais dados (ou seja, locais file:line) devido à disponibilidade de bibliotecas simbolizadas no host.
ASan em apps
O ASan não pode acessar o código Java, mas pode detectar bugs nas bibliotecas JNI. Para isso, é necessário criar o executável com o ASan, que, nesse caso, é /system/bin/app_process(32|64). Isso ativa o ASan em todos os apps no dispositivo ao mesmo tempo, o que é uma carga pesada, mas um dispositivo com 2 GB de RAM deve ser capaz de lidar com isso.
Adicione LOCAL_SANITIZE:=address a
regra de build em frameworks/base/cmds/app_process.app_process
Edite a seção service zygote do arquivo system/core/rootdir/init.zygote(32|64).rc apropriado para adicionar as seguintes linhas ao bloco de linhas recuadas que contém class main, também recuado pela mesma quantidade:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
setenv ASAN_OPTIONS allow_user_segv_handler=true
Crie, adb sync, fastboot flash boot e reinicie.
Usar a propriedade de encapsulamento
A abordagem na seção anterior coloca o ASan em todos os apps do sistema (na verdade, em todos os descendentes do processo Zygote). É possível executar apenas um (ou vários) apps com o ASan, trocando alguma sobrecarga de memória por uma inicialização mais lenta do app.
Isso pode ser feito iniciando o app com a propriedade wrap..
O exemplo a seguir executa o app Gmail no ASan:
adb rootadb shell setenforce 0 # disable SELinuxadb shell setprop wrap.com.google.android.gm "asanwrapper"
Nesse contexto, asanwrapper reescreve /system/bin/app_process para /system/bin/asan/app_process, que é criado com o ASan. Ele também adiciona /system/lib/asan no início do caminho de pesquisa da biblioteca dinâmica. Dessa forma, as bibliotecas instrumentadas pelo ASan
de /system/lib/asan são preferidas às bibliotecas normais
em /system/lib ao serem executadas com asanwrapper.
Se um bug for encontrado, o app falhará e o relatório será impresso no registro.
SANITIZE_TARGET
O Android 7.0 e versões mais recentes incluem suporte para criar toda a plataforma Android com o ASan de uma só vez. Se você estiver criando uma versão mais recente que o Android 9, o HWASan será uma opção melhor.
Execute os comandos a seguir na mesma árvore de build.
make -j42SANITIZE_TARGET=address make -j42
Nesse modo, userdata.img contém bibliotecas extras e também precisa ser transferido para o dispositivo. Use a seguinte linha de comando:
fastboot flash userdata && fastboot flashall
Isso cria dois conjuntos de bibliotecas compartilhadas: normal em /system/lib (a primeira invocação de make) e instrumentada pelo ASan em /data/asan/lib (a segunda invocação de make). Os executáveis do segundo build substituem aqueles do primeiro. Os executáveis instrumentados pelo ASan recebem um caminho de pesquisa de biblioteca diferente que inclui /data/asan/lib antes de /system/lib usando /system/bin/linker_asan em PT_INTERP.
O sistema de build substitui diretórios de objetos intermediários quando o valor $SANITIZE_TARGET é alterado. Isso força uma recriação de todos os destinos, preservando os binários instalados em /system/lib.
Alguns destinos não podem ser criados com o ASan:
- Executáveis vinculados estaticamente
- Destinos
LOCAL_CLANG:=false LOCAL_SANITIZE:=falsenão são ASan'd paraSANITIZE_TARGET=address
Executáveis como esses são ignorados no build SANITIZE_TARGET, e a versão da primeira invocação de make é deixada em /system/bin.
Bibliotecas como essa são criadas sem o ASan. Elas podem conter algum código ASan das bibliotecas estáticas das quais dependem.