Shell-Script umbrechen

Beim Debuggen und Erstellen von Profilen für Anwendungen mit nativem Code ist es oft nützlich, Debugging-Tools zu verwenden, die beim Start des Prozesses aktiviert werden müssen. Dies erfordert, dass Sie die Anwendung in einem neuen Prozess ausführen, anstatt sie aus der Zygote zu klonen. Beispiele:

Shell-Skript für Wrapping verwenden

Die Verwendung von wrap.sh ist einfach:

  1. Kompilieren Sie ein benutzerdefiniertes debugfähiges APK, das Folgendes verpackt:
    • Ein Shell-Skript mit dem Namen wrap.sh. Weitere Informationen finden Sie unter Wrapping-Shell-Script erstellen und Package wrap.sh.
    • Alle zusätzlichen Tools, die Ihr Shell-Skript benötigt (z. B. Ihr eigenes strace-Binärprogramm).
  2. Installiere das Debug-fähige APK auf einem Gerät.
  3. Starten Sie die App.

Shell-Skript für Wrapping erstellen

Wenn Sie ein debugfähiges APK starten, das wrap.sh enthält, führt das System das Skript aus und übergibt den Befehl zum Starten der App als Argumente. Das Skript ist für das Starten der Anwendung verantwortlich, kann jedoch alle Umgebungs- oder Argumentänderungen vornehmen. Das Skript sollte der MirBSD Korn Shell-Syntax (mksh) folgen.

Das folgende Snippet zeigt, wie Sie eine einfache wrap.sh-Datei schreiben, mit der nur die Anwendung gestartet wird:

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

Malloc-Fehlerbehebung

Sie würden die folgende Zeile einfügen, um die Malloc-Fehlerbehebung über wrap.sh zu verwenden:

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

Asan

In der ASan-Dokumentation finden Sie ein Beispiel für die entsprechende Vorgehensweise für ASan.

Paket wrap.sh

Damit du die Vorteile von wrap.sh nutzen kannst, muss dein APK debugfähig sein. Achte darauf, dass die Einstellung android:debuggable="true" im Element <application> in deinem Android-Manifest konfiguriert ist. Wenn du Android Studio verwendest, hast du in der Datei build.gradle einen Debug-Build konfiguriert.

Außerdem musst du in der Datei build.gradle deiner App useLegacyPackaging auf true setzen. In den meisten Fällen ist diese Option standardmäßig auf false gesetzt, sodass Sie sie explizit auf true setzen sollten, um Überraschungen zu vermeiden.

Sie müssen das Skript wrap.sh mit den nativen Bibliotheken der App verpacken. Wenn Ihre App keine nativen Bibliotheken enthält, fügen Sie das lib-Verzeichnis manuell Ihrem Projektverzeichnis hinzu. Für jede von Ihrer Anwendung unterstützte Architektur müssen Sie in diesem nativen Bibliotheksverzeichnis eine Kopie des Wrap-Shell-Skripts bereitstellen.

Das folgende Beispiel zeigt das Dateilayout zur Unterstützung der Architektur ARMv8 und x86-64:

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

In Android Studio werden nur .so-Dateien aus den lib/-Verzeichnissen gepackt. Wenn du Android Studio-Nutzer bist, musst du deine wrap.sh-Dateien stattdessen in den src/main/resources/lib/*-Verzeichnissen ablegen, damit sie richtig gepackt werden.

resources/lib/x86 wird in der UI als lib.x86 angezeigt, sollte tatsächlich aber ein Unterverzeichnis sein:

Beispiel für die Verpackung von wrap.sh in Android Studio

Fehler mit wrap.sh beheben

Wenn Sie bei der Verwendung von wrap.sh einen Debugger hinzufügen möchten, muss das Shell-Skript die Fehlerbehebung manuell aktivieren. Die Vorgehensweise hierfür variiert zwischen Releases. In diesem Beispiel wird gezeigt, wie Sie die entsprechenden Optionen für alle Releases hinzufügen, die wrap.sh unterstützen:

#!/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