VM Tooling interface

As a research VM, Maxine should make it is easy as possible to analyze the behavior of applications and Maxine itself. Since Maxine is meta-circular, many of the techniques used to analyze applications are also applicable to Maxine, although some aspects of meta-circularity can cause problems that can be hard to foresee, such as trying to allocate on the heap during a garbage collection.

Maxine supports (most of) the standard JVMTI API, which supports agents written in native code. In particular, the standard jdwp agent is supported which allows debugging of applications and, experimentally, the VM itself, with a Java IDE. Since Maxine is written in Java, the native JVMTI interface is not very appropriate, and Maxine provides a Java version of the JVMTI API, that is much easier to use than the native interface.

VMTI

Maxine does not make reference to any specific tooling interface or implementation in the core VM code. Instead it defines an interface VMTI that defines methods that a tooling implementation must implement to be included in Maxine. Multiple tooling implementations can be active in a single VM. Currently Maxine supports two tooling implementations, JVMTI and Virtual Machine Level Analysis. The latter, which overlaps somewhat with JVMTI, is specific to Maxine and primarily supports the advising of the execution of the virtual machine at the bytecode abstraction level.

JVMTI

The JVMTI implementation is contained in the package com.oracle.max.vm.ext.jvmti. Although separately specified in an extension package, currently it is included by default in the default boot image. The implementation is incomplete but sufficient for many purposes, including debugging. Notable omissions are the methods related to monitor contention and the reference walking heap iterators. The implementation will be completed in due course.

JJVMTI

JJVMTI is a Java version of the standard JVMTI native interface. As far as possible it is equivalent the native version, so translation between the two should be straightforward. Some design choices were changed to reflect the nature of Java. For example, whereas JVMTI returns errors as the function result, and uses pointers to caller defined variables to pass data, JJVMTI throws an exception in the event of an error and returns data as the method result. Also, whereas JVMTI necessarily uses either JNI handles or scalar values to represent classes and methods, JJVMTI uses Maxine’s actor classes, e.g., ClassActor Using JJVMTI

Writing a JJVMTI agent is considerably simpler than writing the equivalent JVMTI native agent, as there is no need to deal with all the complexity of the JVMTI and JNI native interfaces. However, since the agent must necessarily access Maxine VM classes, it must either be included in the boot image or dynamically loaded as a VM extension (preferred). Note that, unlike JVMTI native agents, JJVMTI agents cannot get control early in the VM startup, so certain changes to the VM environment cannot be made. This currently does not affect Maxine as it does not reconfigure itself in response to JVMTI capability requests.

The standard form of an agent that can be included in the boot image or loaded dynamically is as follows:

public class Agent extends NullJJVMTICallbacks {

    private static Agent agent;
    private static String AgentArgs;

    static {
        agent = (Agent) JJVMTIAgentAdapter.register(new Agent());
        if (MaxineVM.isHosted()) {
            VMOptions.addFieldOption("-XX:", "AgentArgs", "arguments for exemplar JJVMTI agent");
        }
    }

    /***
     * VM extension entry point.
     * @param args
     */
    public static void onLoad(String agentArgs) {
        AgentArgs = agentArgs;
        agent.onBoot();
    }

    /**
     * Boot image entry point.
     */
    @Override
    public void onBoot() {
        agent.setEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, null);
    }

    @Override
    public void vmInit() {
        if (AgentArgs != null) {
            // process arguments and enable needed JVMTI capabilities
        }
    }
}

Note that the mechanism for communicating arguments to the agent is necessarily different between a boot image agent and a dynamically loaded agent. In the former case a new VM command line option is defined, whereas in the latter case the arguments are passed in the VM extension option, -vmextension:jar[=args]. Note also that onBoot is only called in boot image mode and onLoad is only called when dynamically loaded, as per the specification for VM extensions. Since the VM is still in PRIMORDIAL mode in onBoot the recommended idiom is to enable the VMINIT event and do all further processing in the vmInit event callback, which is invoked (if enabled) in either case.

An example of a complete JJVMTI agent is the conversion of the native heap viewer agent that is supplied as a demo with the JDK. This agent also demonstrates one of the meta-circularity issues with JJVMTI. The heapIteration callback is not callback safe in JVMTI terminology, in particular, it cannot allocate. However, when the agent is dynamically loaded, Maxine will attempt to allocate implicitly as the heapIteration method will be compiled at the point that it is first invoked. This is finessed by the agent by forcing the compilation of heapIteration in the vmInit method. Note that this is not an issue if the agent is included in the boot image owing to ahead of time compilation. It would also be mitigated if Maxine kept a separate VM and application heap.