Java Platform Debugger Architecture |
JVM TI Modularity
The Java Virtual Machine Tool Interface (JVM TI)
describes the functionality provided by a virtual machine (VM)
in order to allow debugging of Java programming language
applications running under this VM. In JPDA, JVM TI is implemented
by a VM and the client is the JPDA back-end. In the reference
implementation of JPDA, JVM TI is implemented by the Java HotSpot VM
and the client is the reference implementation of the back-end,
supplied as a native shared library (jdwp.so, jdwp.dll, ...),
which is shipped with the JDK.
Many VMs other than the Java HotSpot VM implement JVM TI. The reference implementation of the back-end has been ported to other platforms. And there are JVM TI clients other than the back-end, most notably agents for applications that allow debugging of both native and Java programming language code, and thus need native level control and information. We are aware of no clean-room implementations of the back-end, although this is possible - and a lot of work.
JVM TI is problematic to implement in some VMs. JDWP is implemented directly in such VMs. On the client side, an application written in a language other than the Java programming language may not be an optimal candidate for using JDI. Some have chosen to be clients of JDWP.
The JDI could be implemented by a system with a static view of an application. It could be implemented by a system with a mechanism utterly different than JDWP/front-end for collecting information or controlling a VM.
Walk-through
The various ways that the interfaces can be used is discussed
above. This section will examine how the standard full JPDA
works. The examples go into detail about specific calls and
codes. It is not important to understand these -- they are
present only to make the examples more concrete.
Across each interface there are two classes of activity: requests and events. Requests originate on the debugger side and include queries for information, setting of state changes in the remote VM/application, and setting of debugging state. Events originate on the debuggee side and denote changes of state in the remote VM/application.
Let's walk through an example. A user clicks on a local variable
in a stack view in an IDE, requesting its value. The IDE uses
the JDI to get the value, in particular it calls the
getValue
method, for example:
theStackFrame.getValue(theLocalVariable)Where
theStackFrame
is a
com.sun.jdi.StackFrame
and
theLocalVariable
is a
com.sun.jdi.LocalVariable
.
The front-end then sends this query over a communications channel (let's say a socket) to the back-end running in the debuggee process. It sends it by formatting it into a byte stream in accordance with the JDWP. In particular, it sends a GetValues command (byte value: 1) in the StackFrame command set (byte value: 16), followed by the thread ID, frame ID, etc.
The back-end deciphers the byte-stream and sends the query off to the VM through the JVM TI. In particular, let's say the requested value is an integer, the following JVM TI function call is made:
error = jvmti->GetLocalInt(frame, slot, &intValue);The back-end sends back across the socket, a response packet, which will include the value of
intValue
, and which will be
formatted according to JDWP. The front-end deciphers the
response packet and returns the value as the value of the
getValue
method call. The IDE then displays
the value.
Requests to change debugging state are processed in a similar manner. For example, a request to set a breakpoint goes through the same steps -- although, of course, the JDI methods called, the JDWP commands sent, and the JVM TI functions called are different. Additionally, the front-end and back-end do more than shove data back and forth, they track and schedule activity and convert, filter, and cache information, so a breakpoint request will be processed quite differently than a get value query - but the communication sequence will be the same.
What happens when the application being debugged finally hits this breakpoint? This is where events come into play. The virtual machine sends an event across the JVM TI interface. In particular, it calls the event handling function passing the breakpoint:
The back-end has set the event handling function to be:
static void Breakpoint(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method, jlocation location) { ...
This back-end function starts a chain of activity which filters
the event to see if it is interesting, queues it, and sends it
across the socket in the JDWP format defined for breakpoint
events. The front-end decodes and processes the event,
eventually generating a JDI event. In particular, the
JDI event is exposing it as a
com.sun.tools.jdi.event.BreakpointEvent
.
The IDE then gets the event by removing it from the event queue:
theEventQueue.remove()where
theEventQueue
is a com.sun.jdi.event.EventQueue
. The IDE will probably
update its displays by making many query calls across the JDI.
Porting
Each virtual machine implementation needs its own JVM TI
implementation -- a JVM TI implementation must dig deeply into
VM data structures and must set hooks into the VM implementation
in order to get events. Adding JVMDT to a VM without JVM TI
support is a significant undertaking. Depending on the complexity
of the VM and the amount of optional JVM TI implemented, it might
be a three to twelve month project. Porting a VM which has
JVM TI support to a new platform is primarily the work of porting
the non-JVM TI portions of the VM -- JVM TI adds a comparatively
small amount of work.
The reference implementation of the back-end can usually be moved to a new platform with little (a few lines) or no change to the source and then recompiled. To use a new VM on the same platform, the binary of the back-end should generally work -- although, it's not Java programming language code so you never know. Note that licensing issues are not covered by this document.
The front-end implementation is written in the Java programming language and will run on any platform or VM. However, the connector code has functionality that may need to be extended for some systems. For example, the reference implementation of the front-end includes a launcher which assumes virtual machines are launched using Java SE conventions. A user of the JDI can configure any launcher syntax he or she wants, but generally a debugger application would prefer to leave this to the JDI implementation. If a different type of communication channel is desired (serial, for example) this too would need to be added using the Service Provider Interface introduced in JDK 5.0.
Copyright © 2005 Sun Microsystems, Inc. All Rights Reserved.
Feedback |
Java Software |