アプリをネイティブ コードでデバッグおよびプロファイリングする場合、デバッグツールを使用すると便利です。デバッグツールはプロセスのスタートアップ時に有効にする必要があります。そのためには、zygote からクローンを作成するのではなく、新しいプロセスでアプリを実行する必要があります。次に例を示します。
- strace でシステム呼び出しをトレースする。
- malloc debug や Address Sanitizer(ASan) を使用してメモリのバグを見つける。
- Simpleperf を使用してプロファイリングを行う。
Wrap シェル スクリプトの使用
wrap.sh
は以下の手順で簡単に使用できます。
- 以下をパッケージ化したデバッグ可能なカスタム APK をコンパイルします。
wrap.sh
という名前のシェル スクリプト。詳細については、Wrap シェル スクリプトの作成と wrap.sh のパッケージ化をご覧ください。- シェル スクリプトに必要なその他のツール(独自の
strace
バイナリなど)。
- デバッグ可能な APK をデバイスにインストールします。
- アプリを起動します。
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
ファイルで useLegacyPackaging
を true
に設定する必要もあります。ほとんどの場合、このオプションはデフォルトで 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
と表示されますが、実際にはサブディレクトリです。
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