Cannot get information about local variables on a StackFrame
I'm trying to write a relatively simple debugger in java, but I'm having some trouble with StackFrame.visibleVariables(). It always throws an AbsentInformationException, yet I cannot see why. Can anybody explain why this happens?
I've tried searching for hints as to what is wrong, but there doesn't seem to be any. Most of the hits I get in google is from the JPDA API documentation.
Here follows the code that's not working. It consists of two files: Trace.java with the debugger implementation (a modified version of the example tracer that comes with JPDA), and Foo.java which is the class that will be traced.
// ****** Trace.java ******
import java.io.*;
import java.util.*;
import com.sun.jdi.*;
import com.sun.jdi.request.*;
import com.sun.jdi.event.*;
import com.sun.jdi.connect.*;
/**
* This program traces the execution of another program.
*/
class Trace{
// Running remote VM
privatefinal VirtualMachine vm;
// Thread transferring remote error stream to our error stream
private Thread errThread =null;
// Thread transferring remote output stream to our output stream
private Thread outThread =null;
/**
* main
*/
publicstaticvoid main(String[] args){
new Trace(args);
}
/**
* Launch target VM.
* Generate the trace.
*/
Trace(String[] args){
PrintWriter writer =new PrintWriter(System.out);
// Launch target VM - Foo is the class to be executed
vm = launchTarget("Foo");
generateTrace(writer);
}
/**
* Generate the trace.
* Enable events, start thread to display events,
* start threads to forward remote error and output streams,
* resume the remote VM, wait for the final event, and shutdown.
*/
void generateTrace(PrintWriter writer){
vm.setDebugTraceMode(VirtualMachine.TRACE_NONE);
EventThread eventThread =new EventThread(vm, writer);
eventThread.start();
redirectOutput();
vm.resume();
// Shutdown begins when event thread terminates
try{
eventThread.join();
errThread.join();// Make sure output is forwarded
outThread.join();// before we exit
}catch (InterruptedException exc){
// we don't interrupt
}
writer.close();
}
/**
* Launch target VM.
* Forward target's output and error.
*/
VirtualMachine launchTarget(String mainArgs){
LaunchingConnector connector = findLaunchingConnector();
Map arguments = connectorArguments(connector, mainArgs);
try{
return connector.launch(arguments);
}catch (IOException exc){
thrownew Error("Unable to launch target VM: " + exc);
}catch (IllegalConnectorArgumentsException exc){
thrownew Error("Internal error: " + exc);
}catch (VMStartException exc){
thrownew Error("Target VM failed to initialize: " +
exc.getMessage());
}
}
void redirectOutput(){
Process process = vm.process();
// Copy target's output and error to our output and error.
errThread =new StreamRedirectThread("error reader", process.getErrorStream(), System.err);
outThread =new StreamRedirectThread("output reader",process.getInputStream(), System.out);
errThread.start();
outThread.start();
}
/**
* Find a com.sun.jdi.CommandLineLaunch connector
*/
LaunchingConnector findLaunchingConnector(){
List<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
for(Connector connector: connectors)
if (connector.name().equals("com.sun.jdi.CommandLineLaunch"))
return (LaunchingConnector)connector;
thrownew Error("No launching connector");
}
/**
* Return the launching connector's arguments.
*/
Map connectorArguments(LaunchingConnector connector, String mainArgs){
Map arguments = connector.defaultArguments();
Connector.Argument mainArg =
(Connector.Argument)arguments.get("main");
if (mainArg ==null){
thrownew Error("Bad launching connector");
}
mainArg.setValue(mainArgs);
// Need a VM that supports watchpoints
Connector.Argument optionArg = (Connector.Argument)arguments.get("options");
if (optionArg ==null){
thrownew Error("Bad launching connector");
}
optionArg.setValue("-classic");
return arguments;
}
}
class EventThreadextends Thread{
privatefinal VirtualMachine vm;// Running VM
privatefinal PrintWriter writer;// Where output goes
privateboolean connected =true;// Connected to VM
privateboolean vmDied =true;// VMDeath occurred
// Maps ThreadReference to ThreadTrace instances
private Map traceMap =new HashMap();
EventThread(VirtualMachine vm, PrintWriter writer){
super("event-handler");
this.vm = vm;
this.writer = writer;
}
/**
* Run the event handling thread.
* As long as we are connected, get event sets off
* the queue and dispatch the events within them.
*/
publicvoid run(){
EventQueue queue = vm.eventQueue();
while (connected){
try{
EventSet eventSet = queue.remove();
for(Event e: eventSet)
handleEvent(e);
eventSet.resume();
}catch (InterruptedException exc){
// Ignore
}catch (VMDisconnectedException discExc){
break;
}
}
}
/**
* This class keeps context on events in one thread.
* In this implementation, context is the indentation prefix.
*/
class ThreadTrace{
final ThreadReference thread;
ThreadTrace(ThreadReference thread){
this.thread = thread;
// Create step event which will step through all lines
EventRequestManager mgr = vm.eventRequestManager();
StepRequest req = mgr.createStepRequest(thread,
StepRequest.STEP_LINE,
StepRequest.STEP_INTO);
req.setSuspendPolicy(EventRequest.SUSPEND_ALL);
req.addClassFilter("Foo");
req.enable();
}
privatevoid println(String str){
writer.println(str);
}
void printStackFrame(StackFrame sf){
try{
for(LocalVariable localvar: sf.visibleVariables()){
println("Local variable: " + localvar.name());
}
}catch(AbsentInformationException e){
println("Got AbsentInformationException");
}
}
void stepEvent(StepEvent event){
try{
if(!event.thread().frame(0).location().sourceName().equals("Foo.java"))
return;
}catch(Exception e){
return;
}
try{
StackFrame sf = event.thread().frame(0);
println("\n**** " + sf.location().sourceName() +
":" + sf.location().lineNumber() +
" in method " + sf.location().method().name() +"() ****");
for(StackFrame s: event.thread().frames()){
printStackFrame(s);
}
}catch(Exception e){
//foo
}
}
}
/**
* Returns the ThreadTrace instance for the specified thread,
* creating one if needed.
*/
ThreadTrace threadTrace(ThreadReference thread){
return (ThreadTrace)traceMap.get(thread);
}
/**
* Dispatch incoming events
*/
privatevoid handleEvent(Event event){
if (eventinstanceof StepEvent){
stepEvent((StepEvent)event);
}elseif(eventinstanceof VMStartEvent){
startEvent((VMStartEvent)event);
}
}
// Create entry in traceMap
privatevoid startEvent(VMStartEvent event){
traceMap.put(event.thread(),new ThreadTrace(event.thread()));
}
// Forward event for thread specific processing
privatevoid stepEvent(StepEvent event){
threadTrace(event.thread()).stepEvent(event);
}
}
/**
* StreamRedirectThread is a thread which copies it's input to
* it's output and terminates when it completes.
*/
class StreamRedirectThreadextends Thread{
privatefinal Reader in;
privatefinal Writer out;
privatestaticfinalint BUFFER_SIZE = 2048;
/**
* Set up.
* @param name Name of the thread
* @param inStream to copy from
* @param outStream to copy to
*/
StreamRedirectThread(String name, InputStream in, OutputStream out){
super(name);
this.in =new InputStreamReader(in);
this.out =new OutputStreamWriter(out);
setPriority(Thread.MAX_PRIORITY-1);
}
publicvoid run(){
try{
char[] cbuf =newchar[BUFFER_SIZE];
int count;
while ((count = in.read(cbuf, 0, BUFFER_SIZE)) >= 0){
out.write(cbuf, 0, count);
}
out.flush();
}catch(IOException exc){
System.err.println("Child I/O Transfer - " + exc);
}
}
}
// ****** Trace.java ******
class Foo{
int a = 0, b = 1, c = 2;
publicstaticvoid main(String[] args){
int e = 4;
int g = 5;
Foo f =new Foo();
for(int i=0; i<2; i++)
f.foo();
}
publicstaticvoid foo(){
int d = 3;
System.out.println("Foo");
d = 4;
}
}
When Trace is run, the following is outputted:
Foo
Foo
**** Foo.java:6 in method main() ****
Got AbsentInformationException
**** Foo.java:7 in method main() ****
Got AbsentInformationException
**** Foo.java:8 in method main() ****
Got AbsentInformationException
**** Foo.java:1 in method <init>() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:2 in method <init>() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:8 in method main() ****
Got AbsentInformationException
**** Foo.java:9 in method main() ****
Got AbsentInformationException
**** Foo.java:10 in method main() ****
Got AbsentInformationException
**** Foo.java:15 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:16 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:17 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:18 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:9 in method main() ****
Got AbsentInformationException
**** Foo.java:10 in method main() ****
Got AbsentInformationException
**** Foo.java:15 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:16 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:17 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:18 in method foo() ****
Got AbsentInformationException
Got AbsentInformationException
**** Foo.java:9 in method main() ****
Got AbsentInformationException
**** Foo.java:11 in method main() ****
Got AbsentInformationException

