ART TI

Android 8.0 以降では、ART Tooling Interface(ART TI)によって特定のランタイムの内部を公開し、プロファイラとデバッガでアプリの実行時の動作に影響を与えられます。これを利用して、他のプラットフォームにネイティブ エージェントを実装するための最先端のパフォーマンス ツールを実装できます。

ランタイムの内部は、ランタイム プロセスに読み込まれたエージェントに公開されます。 エージェントは直接の呼び出しとコールバックを介して ART とやり取りします。ランタイムは複数のエージェントをサポートするため、各種の直交プロファイリングの問題を分離できます。エージェントは、ランタイムの開始時(dalvikvm またはapp_process が呼び出されたとき)に提供されるか、またはすでに実行中のプロセスにアタッチされます。

アプリとランタイムの動作を計測および変更する機能は非常に強力であるため、ART TI には 2 つの安全対策が組み込まれています。

  • まず、エージェント インターフェースである JVMTI を公開するコードは、ランタイムのコア コンポーネントではなく、ランタイム プラグインとして実装されます。プラグインの読み込みを制限することで、エージェントによるインターフェース ポイントの検出をブロックできます。
  • 次に、ActivityManager クラスとランタイム プロセスの両方で、エージェントがデバッグ可能なアプリにのみアタッチすることを許可できます。デバッグ可能なアプリは、デベロッパーが解析と計測を終わらせるので、エンドユーザーには配布されません。Google Play ストアは、デバッグ可能なアプリの配布を許可していません。これにより、通常のアプリ(コア コンポーネントを含む)の計測や操作を確実に防止できます。

設計

計測されるアプリの一般的なフローと相互接続を図 1 に示します。

計測されるアプリのフローと相互接続
図 1. 計測されるアプリのフローと相互接続

ART プラグイン libopenjdkjvmti は、プラットフォームのニーズと制約に対応できるように設計された ART TI を公開します。

  • クラスの再定義は、クラスファイルではなく、単一のクラス定義のみを含む Dex ファイルに基づいています。
  • 計測と再定義用の Java 言語 API は公開されていません。

ART TI は、Android Studio のプロファイラもサポートします。

エージェントの読み込みまたはアタッチ

ランタイムの起動時にエージェントをアタッチするには、次のコマンドを使用して、JVMTI プラグインと特定のエージェントの両方を読み込みます。

dalvikvm -Xplugin:libopenjdkjvmti.so -agentpath:/path/to/agent/libagent.so …

ランタイムの起動時にエージェントが読み込まれる際に実施される安全対策はありません。したがって、手動で起動されるランタイムは、安全対策のない状態で全面的な変更を行えることに留意してください(これにより、ART テストが可能になります)。

注: このことは、デバイス上の通常のアプリ(システム サーバーを含む)には当てはまりません。アプリはすでに実行中の Zygote からフォークされ、Zygote プロセスにはエージェントの読み込みが許可されません。

すでに実行中のアプリにエージェントをアタッチするには、次のコマンドを使用します。

adb shell cmd activity attach-agent [process]
/path/to/agent/libagent.so[=agent-options]

JVMTI プラグインがまだ読み込まれていない場合、エージェントをアタッチすると、プラグインとエージェント ライブラリの両方が読み込まれます。

エージェントは、「デバッグ可能」とマークされた実行中のアプリにのみアタッチできます(「デバッグ可能」とマークするには、アプリのマニフェスト内でアプリノードの属性 android:debuggabletrue に設定します)。ActivityManager クラスと ART はいずれも、エージェントのアタッチを許可する前にチェックを実行します。ActivityManager クラスは、デバッグ可能な状態であるかどうかについて現在のアプリ情報(PackageManager クラスデータから導出される)をチェックし、ランタイムはアプリの起動時に設定された現在のステータスを確認します。

エージェントの場所

ランタイムは、エージェントを現在のプロセスに読み込むことにより、エージェントが現在のプロセスを直接バインドしてやり取りできるようにする必要があります。ART 自体は、エージェントがどこから来るかについて特定の場所を認識しません。dlopen 呼び出しでは文字列が使用されます。実際の読み込みは、ファイル システム権限と SELinux ポリシーによって制限されます。

デバッグ可能なアプリで実行できるエージェントを配信するには、次の手順を実施します。

  • アプリの APK のライブラリ ディレクトリにエージェントを埋め込みます。
  • run-as を使用して、アプリのデータ ディレクトリにエージェントをコピーします。

API

android.os.Debug に次のメソッドが追加されました。

/**
     * Attach a library as a jvmti agent to the current runtime, with the given classloader
     * determining the library search path.
     * Note: agents may only be attached to debuggable apps. Otherwise, this function will
     * throw a SecurityException.
     *
     * @param library the library containing the agent.
     * @param options the options passed to the agent.
     * @param classLoader the classloader determining the library search path.
     *
     * @throws IOException if the agent could not be attached.
     * @throws a SecurityException if the app is not debuggable.
     */
    public static void attachJvmtiAgent(@NonNull String library, @Nullable String options,
            @Nullable ClassLoader classLoader) throws IOException {

その他の Android API

attach-agent コマンドは一般公開されています。このコマンドは、実行中のプロセスに JVMTI エージェントをアタッチします。

adb shell 'am attach-agent com.example.android.displayingbitmaps
\'/data/data/com.example.android.displayingbitmaps/code_cache/libfieldnulls.so=Ljava/lang/Class;.name:Ljava/lang/String;\''

am start -P および am start-profiler/stop-profiler コマンドは、attach-agent コマンドと同様に機能します。

JVMTI

この機能は、JVMTI API をエージェント(ネイティブ コード)に公開します。主な機能は次のとおりです。

  • クラスを再定義します。
  • オブジェクト割り当てとガベージ コレクションをトラッキングします。
  • オブジェクトの参照ツリーに沿ってヒープ内のすべてのオブジェクトを反復処理します。
  • Java コールスタックを検査します。
  • すべてのスレッドを停止(および再開)します。

Android のバージョンによって使用できる機能が異なります。

互換性

この機能には、Android 8.0 以上でのみ利用可能なコアランタイム サポートが必要です。デバイス メーカーは、この機能を実装するために変更する必要はありません。この機能は AOSP の一部です。

検証

Android 8 以降では、次の CTS テストを実施できます。

  • エージェントがデバッグ可能なアプリにアタッチできることと、デバッグ不可能なアプリにアタッチできないことをテストします。
  • 実装されたすべての JVMTI API をテストします。
  • エージェントのバイナリ インターフェースが安定しているかどうかをテストします。

Android 9 以降ではさらにテストが追加されています。追加のテストは各リリースの CTS テストに含まれています。