Ao depurar e criar perfis de apps com código nativo, geralmente é útil usar ferramentas de depuração que precisem ser ativadas na inicialização do processo. Isso requer que você execute o app em um processo novo em vez de clonar a partir do zigoto. Por exemplo:
- Rastreamento de chamadas do sistema com strace (link em inglês).
- Localização de bugs de memória com depuração malloc ou Limpador de endereços (ASan) (links em inglês).
- Criação de perfil com Simpleperf.
Usar o script de shell de agrupamento
Usar wrap.sh
é fácil:
- Compile um APK depurável personalizado que empacote os seguintes itens:
- Um script de shell chamado
wrap.sh
. Consulte Criar o script de shell de agrupamento e Pacote wrap.sh para ver mais detalhes. - Todas as ferramentas extras que o script de shell precisar, como seu binário
strace
.
- Um script de shell chamado
- Instale o APK depurável em um dispositivo.
- Inicie o app.
Criar o script de shell de agrupamento
Quando você inicia um APK depurável que contém wrap.sh
, o sistema executa
o script e transmite o comando para iniciar o app como argumentos. O script é responsável por iniciar o app, mas pode fazer com que qualquer ambiente ou argumento mude. O script precisa seguir a sintaxe MirBSD Korn shell (mksh) (link em inglês).
O snippet a seguir mostra como escrever um arquivo wrap.sh
simples que só inicia o app:
#!/system/bin/sh exec "$@"
Depuração Malloc
Para usar
a depuração malloc (link em inglês)
via wrap.sh
, você precisa incluir a seguinte linha:
#!/system/bin/sh LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper "$@"
ASan
Há um exemplo de como fazer isso na documentação do ASan.
Pacote wrap.sh
Para aproveitar wrap.sh
, o APK precisa ser depurável. A configuração
android:debuggable="true"
precisa estar definida no elemento
<application>
no manifesto do Android ou, se você está usando o Android Studio,
um build de depuração precisa estar configurado no
arquivo build.gradle
.
Também é necessário definir useLegacyPackaging
como true
no arquivo build.gradle
do app. Na maioria dos casos, essa opção é definida como
false
por padrão. Portanto, defina-a explicitamente como true
para evitar surpresas.
É preciso empacotar o script de wrap.sh
com as bibliotecas nativas do app. Se
o app não tiver bibliotecas nativas, adicione o diretório lib manualmente ao
diretório do projeto. Para cada arquitetura compatível com o app, você precisa fornecer uma cópia do script de shell de agrupamento nesse diretório da biblioteca nativa.
O exemplo a seguir mostra o layout do arquivo para que seja compatível com arquiteturas ARMv8 e x86-64:
# App Directory |- AndroidManifest.xml |- … |- lib |- arm64-v8a |- ... |- wrap.sh |- x86_64 |- ... |- wrap.sh
O Android Studio só empacota arquivos .so
dos diretórios lib/
. Portanto, se você for usuário do Android Studio, precisará colocar seus arquivos wrap.sh
no diretório src/main/resources/lib/*
para que eles sejam empacotados corretamente.
Observe que resources/lib/x86
será exibido na IU como lib.x86
, mas, na verdade, será um subdiretório:
Depurar ao usar wrap.sh
Se você quiser anexar um depurador ao usar o wrap.sh
, seu script de shell precisará
ativar manualmente a depuração. Como isso varia entre as versões, este exemplo mostra como adicionar as opções apropriadas para todas as versões compatíveis com o wrap.sh
:
#!/system/bin/sh
cmd=$1
shift
os_version=$(getprop ro.build.version.sdk)
if [ "$os_version" -eq "27" ]; then
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
elif [ "$os_version" -eq "28" ]; then
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
else
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
fi
exec $cmd