Unión de la secuencia de comandos de shell

Cuando depuras apps y generas perfiles de ellas con código nativo, es útil usar herramientas de depuración que se tengan que habilitar al inicio del proceso. Para ello, debes ejecutar tu app en un proceso nuevo en lugar de clonarla desde Zygote. Los siguientes son algunos ejemplos:

Uso de la unión de la secuencia de comandos de shell

Usar wrap.sh es sencillo:

  1. Compila un APK depurable personalizado que empaquete lo siguiente:
  2. Instala el APK depurable en un dispositivo.
  3. Inicia la app.

Cómo crear la unión de la secuencia de comandos de shell

Cuando inicias un APK depurable que contiene wrap.sh, el sistema ejecuta la secuencia de comandos y pasa el comando para iniciar la app como argumentos. La secuencia de comandos es responsable de iniciar la app, pero puede realizar cambios en cualquier entorno o argumento. La secuencia de comandos debe seguir la sintaxis MirBSD Korn shell (mksh).

El siguiente fragmento muestra la manera de escribir un archivo wrap.sh simple que solo inicia la app:

#!/system/bin/sh
exec "$@"

Depuración de malloc

Para usar la depuración de malloc a través de wrap.sh, debes incluir la siguiente línea:

#!/system/bin/sh
LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper "$@"

ASan

Se incluye un ejemplo de cómo hacerlo para ASan en la documentación correspondiente.

Cómo empaquetar wrap.sh

Para aprovechar wrap.sh, tu APK debe poder depurarse. Asegúrate de que el valor de android:debuggable="true" esté configurado como <application> en el manifiesto de Android o, si usas Android Studio, que hayas configurado una compilación de depuración en el archivo build.gradle.

También es necesario establecer android:extractNativeLibs="true" en <application>. Este es el valor predeterminado, pero si lo estableces explícitamente en false, no funcionará la secuencia de comandos de wrap.sh.

Debes empaquetar la secuencia de comandos wrap.sh con las bibliotecas nativas de la app. Si tu app no contiene bibliotecas nativas, agrega el directorio lib manualmente al directorio del proyecto. Para cada arquitectura que admita tu aplicación, debes proporcionar una copia de la unión de la secuencia de comandos de shell en ese directorio de la biblioteca nativa.

El ejemplo que se incluye a continuación muestra el diseño del archivo para que admita las arquitecturas ARMv8 y x86-64:

# App Directory
|- AndroidManifest.xml
|- …
|- lib
   |- arm64-v8a
      |- ...
      |- wrap.sh
   |- x86_64
      |- ...
      |- wrap.sh

Android Studio solo empaqueta archivos .so de los directorios lib/; por lo tanto, si usas Android Studio, deberás colocar tus archivos wrap.sh en los directorios src/main/resources/lib/* para que se empaqueten correctamente.

Ten en cuenta que resources/lib/x86 aparecerá como lib.x86 en la IU, pero en realidad debería ser un subdirectorio:

Ejemplo de empaquetado de wrap.sh en Android Studio

Cómo realizar depuraciones cuando se usa wrap.sh

Si quieres incorporar un depurador cuando usas wrap.sh, tu secuencia de comandos de shell deberá habilitar la depuración de forma manual. La forma de hacerlo varió entre una versión y otra, por lo que este ejemplo muestra cómo agregar las opciones adecuadas para todas las actualizaciones compatibles con 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 $@"
fi

exec $cmd