In Android 8.0 and higher, the ART Tooling Interface (ART TI) exposes certain runtime internals, and enables profilers and debuggers to influence the runtime behavior of apps. This can be used to implement state-of-the-art performance tools that are provided for implementing native agents on other platforms.
Runtime internals are exposed to agents that have been loaded into the runtime process.
These communicate with the ART through direct calls and callbacks. The runtime
supports multiple agents so that different orthogonal-profiling concerns can
be separated. Agents may be either supplied at runtime start (when
dalvikvm
or app_process
are invoked), or attached to
an already-running process.
Because the ability to instrument and modify app and runtime behavior is very powerful, two safety measures have been integrated into the ART TI:
- First, the code exposing the agent interface, JVMTI, is implemented as a runtime plugin, not a core component of the runtime. Plugin loading may be restricted, so that agents can be blocked from finding any of the interface points.
- Second, both the
ActivityManager
class and the runtime process only allow agents to attach to debuggable apps. Debuggable apps have been signed-off by their developers to be analyzed and instrumented, and aren't distributed to end users. The Google Play store doesn't allow the distribution of debuggable apps. This ensures normal apps (including core components) can't be instrumented or manipulated.
Design
The general flow and interconnection in an instrumented app is shown in Figure 1.
The ART plugin libopenjdkjvmti
exposes the ART TI, which is
designed to accommodate the needs and constraints of the platform:
- Class redefinition is based on
Dex
files, containing only a single class definition, instead of class files. - Java-language APIs for instrumentation and redefinition aren't exposed.
The ART TI also supports Android Studio profilers.
Load or attach an agent
To attach an agent at runtime startup, use this command to load both the JVMTI plugin and the given agent:
dalvikvm -Xplugin:libopenjdkjvmti.so -agentpath:/path/to/agent/libagent.so …
There aren't safety measures in place when an agent is loaded at runtime startup, so keep in mind that a manually started runtime allows full modification without safety measures. (This allows ART testing.)
Note: This is not applicable to normal apps (including the system server) on a device. Apps are forked from an already-running zygote, and a zygote process isn't allowed to load agents.
To attach an agent to an app that's already running, use this command:
adb shell cmd activity attach-agent [process] /path/to/agent/libagent.so[=agent-options]
If the JVMTI plugin has not been loaded yet, attaching an agent loads both the plugin and the agent library.
An agent may only be attached to a running app that's marked as
debuggable (part of the app's manifest, with attribute
android:debuggable
set to true
on the app
node). Both the ActivityManager
class and the ART perform
checks before allowing an agent to be attached. The ActivityManager
class checks the current app information (derived from the PackageManager
class data) for the debuggable status, and the runtime checks its current status,
which was set when the app was started.
Agent locations
The runtime needs to load agents into the current process, so that the agent
can directly bind to and communicate with it. The ART itself is agnostic
regarding the specific location from which the agent comes. The string is used
for a dlopen
call. File system permissions and SELinux policies
restrict the actual loading.
To deliver agents that can be run by a debuggable app, do the following:
- Embed the agent in the library directory of the app's APK.
- Use
run-as
to copy the agent into the app's data directory.
APIs
The following method was added to 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 {
Other Android APIs
The attach-agent command is publicly visible. This command attaches a JVMTI agent to a running process:
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;\''
The am start -P
and am
start-profiler/stop-profiler
commands are similar to the attach-agent command.
JVMTI
This feature exposes the JVMTI API to agents (native code). The important capabilities include:
- Redefining a class.
- Tracking object allocation and garbage collection.
- Iterating over all objects in a heap, following the reference tree of objects.
- Inspecting Java call stacks.
- Suspending (and resuming) all threads.
Different capabilities may be available on different versions of Android.
Compatibility
This feature needs core runtime support that's only available on Android 8.0 and higher. Device manufacturers don't need to make any changes to implement this feature. It's part of AOSP.
Validation
CTS tests the following on Android 8 and higher:
- Tests that agents attach to debuggable apps, and fail to attach to non-debuggable apps.
- Tests all implemented JVMTI APIs
- Tests that the binary interface for agents is stable
Additional tests have been added to Android 9 and higher, and are included in the CTS tests for those releases.