Breakpoint processing in JDI
Join the DZone community and get the full member experience.
Join For FreeI've been working on a JDI (Java Debug Interface) project lately and
have been posting helpful tips as I go along. It has been a few years
since I've worked with this API, but although I know there have been a
few enhancements, the API is quite consistent with what I remember. In
this post I'm going to discuss how to use the API to inspect variable
values at a breakpoint.
As in a previous post,
I'll be restricting myself to breakpoint requests (as opposed to
method-entry, method-exit, exception requests, etc.), mainly because I
know they can be processed rather quickly in near-real-time. Some other
types of exceptions significantly slow down the target application, even
if you process them as quickly as practical and immediately resume all
threads. In a typical, interactive debugger, a human is in the loop,
inspecting variables and making decisions, and a speed difference
between JDI event types would not be noticeable. But in the application
I'm developing, I want to pause execution just long enough to collect
data and then continue execution immediately. So I'm going to use only
breakpoint requests for my project.
Finding Values at a Breakpoint
Given an instance vm of a VirtualMachine, you might configure your event-consuming code as follows:
EventQueue evtQueue = vm.eventQueue(); while(true) { EventSet evtSet = evtQueue.remove(); EventIterator evtIter = evtSet.eventIterator(); while (evtIter.hasNext()) { try { Event evt = evtIter.next(); EventRequest evtReq = evt.request(); if (evtReq instanceof BreakpointRequest) { BreakpointRequest bpReq = (BreakpointRequest)evtReq; BreakpointEvent bpEvt = (BreakpointEvent)evt; ThreadReference threadRef = bpEvt.thread(); StackFrame stackFrame = threadRef.frame(0); ...At this point, you can find variables in two places:
- The stack (hence the retrieval of the StackFrame); for example, local variables in a method;
- The current ObjectReference, for static and instance variables.
List visVars = stackFrame.visibleVariables(); for (LocalVariable visibleVar: visVars) { if (visibleVar.name().equals(varName)) { Value val = stackFrame.getValue(visibleVar); ...As I've mentioned before, the JDI API isn't big on classes with constructors; most of what you see is interface definitions, with impls supplied behind the scenes. So, you would not instantiate a Field in JDI and search for it; instead, you drill down into the API and compare some aspect of the object you want with the data you have at hand. For example, you list all the LocalVariables on the stack and then compare their .name()s with the name of your target variable.
If you don't find your variable on the stack, then you can check the object's fields (I prefer to check the stack first). To get object field values, you would first get the ObjectReference and then query it:
ObjectReference objRef = stackFrame.thisObject(); Value targetVal = objRef.getValue(field...Halt. An ObjectReference will get a value for you, but it requires a Field object, for which there is no constructor. This is just the issue I was discussing above. Just where do you get a Field object? You query the available Fields of the object's ReferenceType first, then pass those Field references to the object to get their values for this instance:
ObjectReference objRef = stackFrame.thisObject(); ReferenceType refType = objRef.referenceType(); List<Field> objFields = refType.allFields(); for (int i=0; i<objFields.size(); i++) { Field nextField = objFields.get(i); if (nextField.name().equals(varName)) { Value targetVal = objRef.getValue(nextField); } }
So there are your values at the breakpoint, obtained either from the stack or the instance fields of the ObjectReference.
Working with JDI Values
The Value that you get from JDI requires a little effort to be of practical use. For example, if the Value is an instance of a StringReference, you'll need to cast it to a StringReference, then call .value() to get a java.lang.String value. If it's an instance of an IntegerValue, then you'll have to cast the Value to an IntegerValue and call .value(), which in this case will return an int! I find this process to be a little tedious (note: .toString() doesn't return the value!). In my case, I'm going to be generating either log output or key-value pairs for each breakpoint event, so my approach is just to write a convenience method that hides the details and provides the value in String form.
An interesting case is when the Value is itself an ObjectReference. Just as you can expand variables down to their fields in a typical debugger, you can also choose to drill down into objects at a breakpoint and retrieve their fields, to an arbitrary level of depth. The same process can be used for other JDI types, such as StringReference.
These fields might not be quite as familiar as the methods you are more accustomed to using. For example, if you are logging the size of Strings that are being generated in your target program, you can't call .length() on the StringReference, but you can get its instance fields and retrieve count, which is the name of the field holding the String's length.
Practical Considerations
While it is possible to drill down to an arbitrary depth in the stack or in the instance's fields, keep in mind that you have halted your application while this is occurring. It's best to keep processing during this period to a minimum. While you could spawn a thread to finish processing the breakpoint (for example, creating and sending a JMS message), you will need to retrieve all the variables from the stack and instance while the target application is halted. So that drill-down should be quick.
That caveat aside, you can then specify variables in some custom format, such as dot-notation, and then drill down into the fields/variables recursively until you find the value you want. For example, you could have a variable specified as fileName.count, in which case you would locate the fileName StringReference, then get the instance fields of fileName and find count, which is the length of the file name.
For fields like String's count, which might not be an obvious choice to someone configuring a breakpoint specification, some type of browser would be useful. Browsing the stack isn't an option until you've already set breakpoints and reached a breakpoint, as the stack frame isn't in scope until the breakpoint is reached. But for static and instance fields, you only need a ReferenceType to list the available Fields, to some configurable depth, before you even start debugging the application. Since these Fields are available in JDI as soon as the class is loaded, you don't have to halt program execution just to get the static and instance field names for the class.
From http://wayne-adams.blogspot.com/2011/11/breakpoint-processing-in-jdi.html
Opinions expressed by DZone contributors are their own.
Comments