Examining variables in JDI
Join the DZone community and get the full member experience.
Join For FreeAs I have mentioned in earlier posts, I am using the Java Debug
Interface (JDI) to create a Java process-monitoring tool. I'm retracing
my steps from an earlier such effort a few years ago, and as I wade
through the details, I'm posting details in the hope they'll be helpful
to someone else out there. I sometimes wonder who would really be
interested in this material, but I have been noticing that my old posts
on JDI, DTrace, etc.
continue to get hits every week, even the old ones, so I know you're out
there. I hope you find this article helpful, too.
Current Status:
At
this stage in the development of my application, I have set up
breakpoint requests and am stepping through events as they arrive.
You'd probably do this in a loop similar to the following:
EventQueue evtQueue = virtualMachine.eventQueue(); EventSet evtSet = null; while (!stopRequested) { try { evtSet = evtQueue.remove(); EventIterator evtIter = evtSet.eventIterator(); while (evtIter.hasNext()) { ... } } catch (Exception exc) { ... } finally { evtSet.resume(); } }
In my application, I handle debugger events in a thread, so I need a way for the thread to know if it should stop (that's the purpose of the boolean stopRequested). Note that an EventSet actually contains an EventIterator which can contain several events. Also note that it is quite important to ensure that resume() is always called on the EventSet, regardless of any exceptions that are thrown. Otherwise, you could end up with some permanently hung threads and have to restart your target application.
Inspecting Variables
At a breakpoint, you'll probably want to extract some of the current state of your target application. There are two places to find variables in the JDI API -- instance/class variables, and the stack. Static and instance variables can always be listed by inspecting the class's ReferenceType -- they're always there, as long as the class is loaded, even if the application isn't running (you may still have to be at a breakpoint to look at their values, of course, but the point is you can also see what a class's static and instance variables are simply by looking at its definition).
On the other hand, the stack variables aren't defined unless you are, well, on the stack where they're defined (e.g. local variables in a method). In JDI, you find these two types of variables in different parts of the API, in locations which will make sense to you as we continue our discussion. You see the same division in a debugger. For example in Eclipse, when you are at a breakpoint and look at the "Variables" window, you will see class/instance variables in a tree structure under "this", and all local (stack) variables listed peer-level to "this" under their local names.
The following sections assume that you have extracted an Event from an EventIterator, verified that it is a BreakpointEvent, extracted the EventRequest from the Event, and verified that it is a BreakpointRequest (these are all JDI classes).
Inspecting Static and Instance Variables:
Given a BreakpointEvent and a variable name (defined in your breakpoint request specification), how would you search for the variable in the static and instance variables list? Every BreakpointEvent has a Location (obtained via the location() method). In addition to having the usual information you would expect a Location object to have (the name of the source, the line number, method name, etc.), a Location has a reference to the class's ReferenceType. The ReferenceType has a method called allFields() which, as the API documentation says, "Returns a list containing each Field declared in this type, and its superclasses, implemented interfaces, and/or superinterfaces. All declared and inherited fields are included, regardless of whether they are hidden or multiply inherited. " Exactly what you want, right?
allFields() returns a List. The JDI Field type has a name field (method: name()) which can be used, while iterating over the List, to find the target variable. Once you have retrieved the correct Field, you get its Value by appealing to the ReferenceType again, as in:
Value val = theReferenceType.getValue(theField);
The only other detail at this point is actually extracting the value of the Value. This is a slightly messy topic, which we'll discuss after we look at the other location of variables -- the stack.
Inspecting Stack Variables:
Stack variables, and specifically their values at your breakpoint, are associated with the JDI StackFrame and Frame classes. They are referred to as LocalVariables in JDI, you get reach them through the BreakpointEvent, by accessing its ThreadReference, then the StackFrame at the top level (that is, element #0) of the stack. You can then query the visible variables on the StackFrame by name to find the target variable. In other words, you could do something like this:
StackFrame stackFrame = breakpointEvt.thread().frame(0); LocalVariable localVar = stackFrame.visibleVariableByName(varName); Value val = stackFrame.getValue(localVar);
Note that you must get the LocalVariable from the StackFrame, but having retrieved it, you must again access the StackFrame to get the Value of the LocalVariable. Note also that we are now at the same stage we were with class/instance variables -- we have a Value, but we need to find out what its "value" is. As I said, this is a little messy and is the subject of the next section.
Extracting Values:
In JDI, a Value isn't just something you can meaningfully display with toString() (unless you find hash codes especially meaningful). The JDI API documentation contains a very useful table under com.sun.jdi.Value which describes the family of subinterfaces of Value. Basically, all the interfaces which represent a usable value contain a value() method, but this method is only defined in the sub-interfaces (since not all of them define one). This means that if you want to inspect a Value that at heart is a boolean, or a String, you can call value() on it, but you must first determine if it is an instance of the type you need, then cast it to that type and call value() on the cast instance. I don't know if there's any more practical way to do this that doesn't involve writing extremely clever (in other words, unmaintainable) code, so here's how I do it:
if (value instanceof BooleanValue) { return ((BooleanValue)value).value(); } else if (value instanceof ByteValue) { return ((ByteValue)value).value(); } ...
and so on.
There are two main subinterfaces of Value -- PrimitiveValue and ObjectReference, along with a little more hierarchy which will be important to you. For example, if you are using code such as the example above to extract values, you will want to check for StringReference before ObjectReference. Why? StringReference has a value() method, as you would expect and hope, but StringReference is a subinterface of ObjectReference, which does not have a value() method. If you check for ObjectReference first, and you're looking at a String Value, you will miss out on the available cast to StringReference and miss out on a chance to inspect the value (yes, I accidentally did this; I did it this time, and I remembered when I did it that I did the same thing a few years ago).
Drill-down:
If you, too, are writing what I would call a "real-time" JDI-based tool, you'll probably want to provide some drill-down at a breakpoint. For example, you might want to look at a specific field of a variable on the stack, rather than just the Value of the variable. The way I handle this is to allow a "dot" notation, where "a.b.c" allows my users to specify that they want to look at field c of field b of variable/field a. Things get a little interesting here, too, as the following example illustrates.
Suppose you are monitoring the traffic of an application and you just want to know the length of Strings being processed (e.g. returned from a service) at a breakpoint. You don't really want to display the String itself; this could be enormous (and remember, your target application is halted while this breakpoint is being processed, so I/O on a large String could cripple the application). All you want to know is the length of the String. But you don't want to write code to actually invoke general methods on your stack/instance variables. And in this particular case, there is a field available to you from JDI on the String class -- count. So your users should be able to specify, for example, incomingMessage.count, and get back an integer with the length of the String.
The only problem is, not everyone knows that there is a field in java.lang.String called count -- they're more accustomed to calling the method length(). In an interactive debugger, this issue isn't a problem, as you don't specify what you want in advance -- you just inspect the state when the breakpoint occurs, and the debugger will show all the fields for you. In a debugger of the type I'm developing, users would need to know what is available. For a stack variable, this is a problem because you can't inspect it until the application is running. For class/instance variables, once you have the ReferenceType of the class, you can at least inspect those variables and drill down at will.
The way I'm handling this issue (for stack variables) is to provide a diagnostic version of a breakpoint request, where the user can request a dump of the variables on the stack, to a specified depth. Note such a feature should be used with some care; an application with a very large number of stack variables, all traversed to (say) 10 levels deep, can end up being suspended for seconds, minutes, or tens of minutes just dumping the state of the stack (yes, I've done that before too, but will not repeat that this time!). Suffice to say that if you want to inspect an application for nested variables, you should do so on a non-production system.
How do you drill down into fields? Unsurprisingly, there's a different procedure for class/instance variables than for stack variables. Unfortunately, some things in JDI are not immediately obvious (in fact, every time I write code to do this, I have to "re-discover" how to do it), which is why I illustrate below.
For class/instance variables, you got the fields by getting the ReferenceType and looking at its Fields. Having retrieved a reference to the Field, you can simply repeat the process -- there is no method on the Field itself to drill down, but you can get the ReferenceType of each Field and then get the Fields of each Field from each Field's ReferenceType. Here's an example:
List childFields = bpEvt.location().declaringType().allFields(); for (Field childField: childFields) { List grandChildFields = childField.declaringType().allFields(); for (Field grandChildField: grandChildFields) { System.out.println(grandChildField.name() + " is a grandchild field"); } }
The situation for stack variables is as follows. If you are trying to drill down into a variable, by definition it is an ObjectReference, right? In other words, there's no drilling down into a primitive. If the Value on the StackFrame is an instance of an ObjectReference, then you can get the ReferenceType of the Value and retrieve its Fields just as we did above when we retrieved the ReferenceType of the class/instance Field. Note that since StringReference is a subinterface of ObjectReference, we could do something like the following:
else if (value instanceof StringReference) { // look at the fields: List childFields = ((StringReference) value).referenceType().allFields(); for (Field childField: childFields) { System.out.println(">> child field: '" + childField.name() + "'"); } return ((StringReference)value).value(); }
If we run this code, we would see something like the following output for a String:
>> child field: 'value'>> child field: 'offset'
>> child field: 'count'
>> child field: 'hash'
>> child field: 'serialVersionUID'
>> child field: 'serialPersistentFields'
>> child field: 'CASE_INSENSITIVE_ORDER'
(Note our count Field, which happens to be an IntegerValue). Of course, you can continue drilling down, using the same procedure, on each of these Fields
That's all I have for now -- look for more in the coming weeks as the project comes along
From http://wayne-adams.blogspot.com/2011/12/examining-variables-in-jdi.html
Opinions expressed by DZone contributors are their own.
Comments