|
SYS-CON.TV Webcasts
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
Top Links You Must Click On
General Java Using The Java Platform Debugger Architecture
Using The Java Platform Debugger Architecture
By: Tony Loton
Mar. 1, 2001 12:00 AM
The Java Platform Debugger Architecture (JPDA) provides a standard set of protocols and APIs at three levels that facilitate the development of a new breed of debugging and profiling tools. The inclusion of JPDA in the Java 2 SDK enables individual developers as well as commercial vendors to find novel ways of analyzing Java applications as they run even remotely across a network. In this article I provide a quick-start guide to developing with the new APIs, with my own novel use of the JPDA as an example. Hopefully, this pragmatic approach will help you build your first debugger application quickly, making the prospect of wading through the comprehensive JPDA documentation less daunting.
The Example
What if you could reverse engineer a Java application as it runs? You could capture the important dynamic behavior of your applications and complete the UML picture with both sequence and state diagrams. I propose a new kind of tool that bridges the gap between reverse engineering and application profiling, a tool that presents the runtime behavior of your application, as shown in Figure 1. Note that the left-hand column of the sequence diagram view in Figure 1 shows the thread on which each object interaction has occurred. Note also that the dependencies view shows dynamic dependencies, for example, where an object instantiates and uses another object within a method, rather than the static dependencies that traditional reverse-engineering tools deduce from member variables. My first attempt at developing a tool such as this was based on using the original command-line Java Debugger, jdb. The idea was to use the input and output streams to drive debugging instructions through the command-line interface, and to parse the resulting text output to extract information on classes, threads, and method invocations. Looking back, I wouldn't have taken this approach; however, after several frustrating attempts, along came the JPDA as a breath of fresh air.
The JPDA Distribution
By unpacking the distribution JAR file, several directories are created:
A JVM that's supporting the JVMDI, such as the Java 2 SDK, provides the debugger back end. This back end interrogates and controls the VM, and communicates through shared memory or over the network via sockets with the debugger front end. JDWP provides the communication protocol through which the front and back ends exchange messages, irrespective of the transport mechanism, thus opening up the possibility of remote debugging across a network. The JDI is a 100% Java interface implemented by the front end that defines information and requests at user code level. Although developers can make direct use of the JVMDI and JDWP, the accompanying documentation recommends this JDI layer for all debugger development.
A Step-by-Step Example
com.sun.jdi
Running the Target Application
java -Djava.compiler=NONE -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,address=8000 MyApplication The following describes the options in the command:
There are many other combinations of options described in the JPDA documentation, too many to describe here in detail. For example, it's possible to run a target application that attaches to an already running debugger front end (-Xrunjdwp:server=n), or one that communicates with the debugger front end through shared memory (-Xrunjdwp:transport=dt_shmem) rather than sockets.
Attaching the Front-End Debugger to the Target Application
The command: jdb -attach targetHostName:8000 (shorthand for jdb -connect com.sun.jdi.SocketAttach:host=target HostName,port=8000)attaches the front-end debugger to the (remote) target application listening on machine targetHostname, port 8000. I recommend using jdb to test the basic JPDA operation prior to developing your own front-end debugger applications. However, for your own debugger applications, and for my example, we need to know how to write the equivalent Java code to establish a connection to a target application. To use the JDI APIs to establish a connection to a target application, it's necessary to understand the notion of a connector. A JPDA connector is similar to a JDBC driver that separates the implementation details from the API specification. The connectors provided with the JPDA distribution are:
My example uses the Socket Attaching Connector that attaches to a target application initiated using the typical invocation command shown earlier: java -Djava.compiler=NONE -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,address=8000 MyApplicationNow onto the Java code. First I look for the required connector by name as shown in Listing 1. Next I obtain the arguments or name-value pairs for this connector and set their values according to my needs (see Listing 2). Finally, I create an instance representing the target virtual machine and attach to it (see Listing 3).
Implementing the Front End Using the JDI APIs
The JDI API provides an EventRequestManager class, the singleton instance of which may be obtained from the VM. This may be configured to trap events such as method invocations as they happen. For each set of method invocations that we wish to intercept, a MethodEntryRequest must be added to the EventRequestManager. The Method-EntryRequest may be restricted by adding a class filter, which is important for my example because I want to display interactions between application objects (e.g., mypkg.*), not between Java library classes (java.*). Listing 4 demonstrates how to obtain the singleton EventRequestManager and how to add a MethodEntryRequest to it. For clarity Figure 3 presents a collaboration diagram showing the steps for attaching to the target VM in the first place, and then adding a MethodEntryRequest. Each time the EventRequestManager traps an event corresponding to my MethodEntryRequest, it places an event on the EventQueue for my attention. Thus the main body of any front-end debugger application will almost certainly consist of an endless loop in which events are popped from the EventQueue, as shown in Listing 5. The "// process this event" comment indicates where the code should go to take some action for the event. For my example the action is to call a method to add a new object interaction to the UML sequence diagram shown in Figure 1. To add a new object interaction I need to know three things: the method that's been invoked, the callee (receiver) class, and the caller (sender) class. The steps involved in getting these three pieces of information are:
Listing 6 shows how to implement these five steps, which are also presented as a collaboration diagram in Figure 4. For my application it gets a little more complicated. Since I'm interested only in interactions between application objects, not Java library objects, the caller class isn't necessarily the one on the second stack frame. I therefore work down the list of stack frames until I find one with a caller class that's not in one of the java.* packages. This identifies the caller application object that interacts with the callee application object, albeit possibly through several levels of Java library objects.
A Variation Using the Command Line Launching Connector
The name of the connector to search for in the list of available connectors (see Listing 1) is "com.sun.jdi.CommandLineLaunch". The arguments for this connector, which can be set by modifying the code shown in Listing 2, are "options" (options, in addition to the debug options, with which to invoke the JVM), "main" (the main class and command-line arguments for the target application), and "suspend" (true if the target VM is to be suspended immediately before the main class is loaded). A version of Listing 2, modified for the Command Line Launching Connector, is shown in Listing 7. You'll notice that in this example I obtain the InputStream and ErrorStream from the VM process and pass each to a displayRemoteOutput() method. The output from the invoked application must go somewhere, and for my application "somewhere" means the standard output of the debugger front end. A more elegant solution would be to provide both an "output" and "error" window as part of the debugger front-end GUI, and channel the target application's output streams to these windows as appropriate.
Conclusion
Using the JPDA I succeeded in developing a prototype of my proposed Runtime Reverse Engineering Tool, which allows any running - local or remote - application to be visualized as a UML sequence diagram and a (dynamic) dependency diagram as shown in Figure 1. Without this addition it wouldn't have been so easy, if at all possible, to realize my own debugger front end. The listings in this article have been simplified for publication, but they're based on code that actually runs as part of my prototype Runtime Reverse Engineering Tool. The complete code has taken a significant amount of time to develop, but I'll consider genuine e-mail requests for access to the full binary and source code for this project. Reader Feedback: Page 1 of 1
Your Feedback
Enterprise Open Source Magazine Latest Stories . . .
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||