Programmatic JVM debug via JDI

The Goal
A couple of times in my life of java programmer I used a trick to put some code ending with “return false;” within Eclipse’s conditional breakpoints just for sake of having that code executed each time JVM goes over the breakpoint, but without the actual suspension of the thread. Or well, suspending would happen anyway, but Eclipse will resume thread automatically after executing the code due to “return false” in the end which would indicate “false” for breakpoint condition value to the Eclipse. As a result program will continue to execute, and no suspending that would require manual resuming (or any interaction) would happen.

The simplest example of the case when one can need that would be logging some variable value of which changes in a loop, and you want to know it’s last value before something happens in the system (say exception occurs). Nothing simpler than just putting a breakpoint to a next line after variable assignment, and setting “System.out.println(theVariable); return false” as a breakpoint condition.

The question that bugged me though was wether I can do it in more controller way. For example from Groovy Shell, which is currently my favourite tool for quickly running several lines of ad-hoc Java code, i.e. for scripting using Java (previously I used BeanShell for that). And it turned out that one can do that fairly easy using JPDA/JDI.

The JPDA JDI
From all the documentation on Java Platform Debugger Architecture and Java Debugger Interface I could not figure out what is actually required in order to achieve my goal. There is a Java API for debugging JVMs – the JDI, but are there implementations? There is a jdb utility, but it’s a “native” app and not some pure-java code. Then there is Eclipse, but it has it’s own JDWP implementation.

Somehow I missed it that in Sun/Oracle docs it’s actually stated that reference implementation of JDI is provided (I only discovered that later).
So I had to try it out to actually see that indeed JDI implementation from Sun/Oracle is there and can be used from java code right away.

The APIs allow you to connect to remote JVM (one that supports JDI/JWDP of course), set up breakpoints, execute code (by invoking methods) in threads of that JVM when it’s suspended via breakpoint etc. The API is pretty verbose as for scripting purposes, but that’s where I suppose Groovy wrappers for JDI interfaces should be written to make it all nice and easy in a groovy-style.

The Example
The best reading is of course the code itself, so without further due let me show you what exactly was done.

The setup was 2 groovysh shells:
– one was a “debuggee” used for lack of better JVM app, and was started up on JVM in debug mode with JDWP via TCP/IP socket, port 7896 (JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7896).
– the other one was a “debugger”

To have a real-life like example in debuggee running the Groovy Shell a variable for Groovy Shell was initialised:
myVar = “Some special value”;
And from debugger I wanted to obtain value of that variable.
The fact that it was not just a field of some class, but a variable in dynamic context in class compiled from groovy made the example more interesting.

Now to the example of JDI APIs usage – here’s a code executed in “debugger” Groovy Shell:
First we connect to remote JVM at 127.0.0.1:7896 (well, localhost isn’t exactly remote of course, but there’s no special difference between localhost and any other host for this code):

vmm = com.sun.jdi.Bootstrap.virtualMachineManager();
vmm.attachingConnectors().each{ if("dt_socket".equalsIgnoreCase(it.transport().name())) { atconn = it; } }
prm = atconn.defaultArguments();
prm.get("port").setValue(7896);
prm.get("hostname").setValue("127.0.0.1");
vm = atconn.attach(prm);

As a result of this we get an instance of com.sun.jdi.VirtualMachine, which is a starting point for actual debugging.

In order to be able to invoke methods on that JVM’s object we should pick a thread in it, and that thread has to be suspended via event within it’s JVM – like a breakpoint hit event.
Quoting the javadocs:

“Method invocation can occur only if the specified thread has been suspended by an event which occurred in that thread. Method invocation is not supported when the target VM has been suspended through VirtualMachine.suspend() or when the specified thread is suspended through ThreadReference.suspend().”

In this case in order to set a breakpoint I’ve first suspended the “main” thread in the debuggee JVM and then put a breakpoint on what it’s currently doing – without even looking up the source code of Groovy Shell that was running there:

// Find thread by name "main"
vm.allThreads().each{ if(it.name().equals("main")) mainThread = it }
// Suspend it
mainThread.suspend()
// Look what's in it's stack trace
i=0; mainThread.frames().each{ println String.valueOf(i++)+": "+it; }; println "";

The output of last command is the list of stack trace “frames” of the thread.
For Groovy Shell in debugee JVM it was this:

0: java.io.FileInputStream.readBytes(byte[], int, int)+-1 in thread instance of java.lang.Thread(name='main', id=1)
1: java.io.FileInputStream:220 in thread instance of java.lang.Thread(name='main', id=1)
2: java.io.BufferedInputStream:218 in thread instance of java.lang.Thread(name='main', id=1)
3: java.io.BufferedInputStream:237 in thread instance of java.lang.Thread(name='main', id=1)
4: jline.Terminal:99 in thread instance of java.lang.Thread(name='main', id=1)
5: jline.UnixTerminal:128 in thread instance of java.lang.Thread(name='main', id=1)
6: jline.ConsoleReader:1453 in thread instance of java.lang.Thread(name='main', id=1)
7: jline.ConsoleReader:654 in thread instance of java.lang.Thread(name='main', id=1)
8: jline.ConsoleReader:494 in thread instance of java.lang.Thread(name='main', id=1)
9: jline.ConsoleReader:448 in thread instance of java.lang.Thread(name='main', id=1)
10: jline.ConsoleReader$readLine.call(java.lang.Object, java.lang.Object)+17 in thread instance of java.lang.Thread(name='main', id=1)
11: org.codehaus.groovy.tools.shell.InteractiveShellRunner:89 in thread instance of java.lang.Thread(name='main', id=1)
12: org.codehaus.groovy.tools.shell.ShellRunner:75 in thread instance of java.lang.Thread(name='main', id=1)
13: org.codehaus.groovy.tools.shell.InteractiveShellRunner.super$2$work()+1 in thread instance of java.lang.Thread(name='main', id=1)
14: sun.reflect.NativeMethodAccessorImpl.invoke0(java.lang.reflect.Method, java.lang.Object, java.lang.Object[])+-1 in thread instance of java.lang.Thread(name='main', id=1)
15: sun.reflect.NativeMethodAccessorImpl:39 in thread instance of java.lang.Thread(name='main', id=1)
16: sun.reflect.DelegatingMethodAccessorImpl:25 in thread instance of java.lang.Thread(name='main', id=1)
17: java.lang.reflect.Method:597 in thread instance of java.lang.Thread(name='main', id=1)
18: org.codehaus.groovy.reflection.CachedMethod:90 in thread instance of java.lang.Thread(name='main', id=1)
19: groovy.lang.MetaMethod:233 in thread instance of java.lang.Thread(name='main', id=1)
20: groovy.lang.MetaClassImpl:1054 in thread instance of java.lang.Thread(name='main', id=1)
21: org.codehaus.groovy.runtime.ScriptBytecodeAdapter:128 in thread instance of java.lang.Thread(name='main', id=1)
22: org.codehaus.groovy.runtime.ScriptBytecodeAdapter:148 in thread instance of java.lang.Thread(name='main', id=1)
23: org.codehaus.groovy.tools.shell.InteractiveShellRunner:100 in thread instance of java.lang.Thread(name='main', id=1)
.... yada yada yada up to 65 lines

It seemed to be ok to set up a breakpoint at this location:

10: jline.ConsoleReader$readLine.call(java.lang.Object, java.lang.Object)+17 in thread instance of java.lang.Thread(name='main', id=1)

The trick is that each StackFrame can provide us it’s Location, which we could use to set up a breakpoint. So we just take Location from that frame, and set up a breakpoint there:

evReqMan = vm.eventRequestManager();
frame = mainThread.frames().get(10);
bpReq = evReqMan.createBreakpointRequest(frame.location());
mainThread.resume();
bpReq.enable();

After that hitting <Enter> in the debuggee Groovy Shell it has got suspended, which shows that our breakpoint is in action now.
Let’s try to get the values from that debuggee Groovy Shell.
Note that we’re not going to re-use the “frame” we already obtained because the thread was resumed after we obtained it, so that frame is no longer valid.

// Checking frames for visible variables
i=0; mainThread.frames().each{ println String.valueOf(i++)+": "+it; try{ it.visibleVariables().each{var-> println " - "+var; }} catch(Exception e) {} }; println;
// 48: org.codehaus.groovy.tools.shell.Main:131 in thread instance of java.lang.Thread(name='main', id=1)
// This seems to be what we want - it's inside org.codehaus.groovy.tools.shell.Main, and has shell variable visible
frame = mainThread.frames().get(48);
vShell = frame.getValue(frame.visibleVariableByName("shell"));
vInterp = vShell.getValue(vShell.referenceType().fieldByName("interp"));
// Now we want to call getContext() on vInterp in order to obtain Groovy Shell interpreter variables context (which is of type groovy.lang.Binding)
vContext = vInterp.invokeMethod(mainThread, vInterp.referenceType().methodsByName("getContext").get(0), [], 0) 
varVal = vContext.invokeMethod(mainThread, vContext.referenceType().methodsByName("getVariable").get(0), [vm.mirrorOf("myVar")], 0)

The last line returns “Some special value” as a result, so we have achieved our goal – we did obtain value of the variable.

Just for fun we can also set variable in debuggee Groovy Shell context, so let’s do that.
Also after that we have to disable the breakpoint and resume the main thread to let debuggee continue executing.

varVal = vContext.invokeMethod(mainThread, vContext.referenceType().methodsByName("setVariable").get(0), [vm.mirrorOf("myVar"), vm.mirrorOf("Surprise!")], 0);
bpReq.disable();
mainThread.resume();

The check reveals that variable “myVar” in debuggee’s Groovy Shell has now the value “Surprise!” – as we wanted to.

So, this was a quick example of working with JDI APIs and debugging remote JVM straight from Groovy Shell.
Hope it was helpful.

UPD: It seems there are some Groovy wrappers for JDI classes already written: youdebug.kenai.com

Advertisements

One thought on “Programmatic JVM debug via JDI

  1. Hi,

    I’ve been looking for instructions on how to get the value of a local variable, and this tutorial is great!
    Do you know how to create a connection to the JVM that is currently running? I want to inspect a local variable in the JVM that I’m running this code from.

    Thanks,

    Dean

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s