シェル スクリプトをラップする

アプリをネイティブ コードでデバッグおよびプロファイリングする場合、デバッグツールを使用すると便利です。デバッグツールはプロセスのスタートアップ時に有効にする必要があります。そのためには、zygote からクローンを作成するのではなく、新しいプロセスでアプリを実行する必要があります。次に例を示します。

Wrap シェル スクリプトの使用

wrap.sh は以下の手順で簡単に使用できます。

  1. 以下をパッケージ化したデバッグ可能なカスタム APK をコンパイルします。
  2. デバッグ可能な APK をデバイスにインストールします。
  3. アプリを起動します。

Wrap シェル スクリプトの作成

wrap.sh を含むデバッグ可能な APK を起動すると、システムによってそのスクリプトが実行され、アプリを開始するコマンドが引数として渡されます。スクリプトはアプリを開始する責任を負いますが、環境や引数を変更することもできます。スクリプトは MirBSD Korn シェル(mksh)の構文に沿って作成してください。

以下のスニペットは、アプリを開始するだけのシンプルな wrap.sh ファイルを作成するコードの例です。

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

malloc デバッグ

wrap.sh を介して malloc デバッグを使用するには、以下の行を入れます。

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

Asan

これを ASan で行う場合の例は、ASan のドキュメントでご覧ください。

wrap.sh のパッケージ化

wrap.sh を利用するには、APK をデバッグ可能にする必要があります。Android マニフェストの <application> 要素で android:debuggable="true" を設定するか、Android Studio を使用している場合は build.gradle ファイルでデバッグビルドを設定済みであることを確認してください。

アプリの build.gradle ファイルで useLegacyPackagingtrue に設定する必要もあります。ほとんどの場合、このオプションはデフォルトで false に設定されているため、予期しない事態を避けるため、これを明示的に true に設定することをおすすめします。

wrap.sh スクリプトはアプリのネイティブ ライブラリと一緒にパッケージ化する必要があります。アプリにネイティブ ライブラリが含まれていない場合は、プロジェクト ディレクトリに手動で lib ライブラリを追加します。アプリがサポートしているアーキテクチャごとに、対応するネイティブ ライブラリ アーキテクチャのディレクトリの下に Wrap Shell スクリプトのコピーを用意する必要があります。

以下の例は、ARMv8 および x86-64 の両方のアーキテクチャをサポートするファイル レイアウトです。

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

Android Studio は .so ファイルを lib/ ディレクトリからのみパッケージ化するため、Android Studio をお使いの場合は、適切にパッケージ化されるよう wrap.sh ファイルを src/main/resources/lib/* ディレクトリに入れる必要があります。

なお、以下のように、resources/lib/x86 は UI では lib.x86 と表示されますが、実際にはサブディレクトリです。

Android Studio で wrap.sh をパッケージ化する例

wrap.sh を使用する場合のデバッグ

wrap.sh を使用する際にデバッガをアタッチするには、シェル スクリプトで手動でデバッグを有効にする必要があります。その方法はリリースによって異なります。以下のコードは、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