Search code examples
javaexceptionjdi

JDI ThreadReference.frame() causes IncompatibleThreadStateException


Currently I am trying to extract some execution data via the JDI. Therefore I first start a java vm manually with the command

java -agentlib:jdwp=transport=dt_socket,server=y,address=8000 DebugDummy

My DebugDummy.java:

public class DebugDummy {

    public class MyInnerClass {
        private int a;

        public MyInnerClass(int a) {
            this.a = a;
            this.doSomething();
        }

        public void doSomething() {
            System.out.println(this.a);
        }
    }

    public DebugDummy() {
        MyInnerClass myInnerClass = new MyInnerClass(5);
        myInnerClass.doSomething();
    }

    public static void main(String[] args) {
        DebugDummy dd = new DebugDummy();
    }
} 

And then connect with the JDI, waiting for the main-method entry and step line per line through the code execution.

public class VMStart {

public static void main(String[] args) {

    //Check for argument count
    if(args.length != 3){
        System.err.println("Not enough parameter!");
        System.exit(0);
    }

    String cwd = "", mainClass = "", vmPort = "";

    cwd = args[0];
    mainClass = args[1];
    vmPort = args[2];

    System.out.println("CWD: " + cwd);
    System.out.println("MainClass: " + mainClass);
    System.out.println("VM Port: " + vmPort);

    //Init vm arguments and settings
    VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
    AttachingConnector ac = vmm.attachingConnectors().get(0);

    //Setting port
    Map<String, Connector.Argument> env = ac.defaultArguments();
    Connector.Argument port = env.get("port");
    port.setValue(vmPort);

    //Setting hostname
    Connector.Argument hostname = env.get("hostname");
    hostname.setValue("localhost");

    //Attach vm to remote vm
    VirtualMachine vm = null;
    try {
        vm = ac.attach(env);
    } catch (IOException | IllegalConnectorArgumentsException e) {
        //Doesn't work, stop here...
        System.err.println("Can't connect to vm!");
        e.printStackTrace();
        System.exit(0);
    }

    //Create EventQueue and EventRequestManager for further event handling
    EventQueue eventQueue = vm.eventQueue();
    EventRequestManager mgr = vm.eventRequestManager();

    //Set the vm to sleep for further operations
    vm.suspend();

    //Searching for our main thread reference
    ThreadReference mainThread = null;
    List<ThreadReference> threads = vm.allThreads();
    for (ThreadReference thread : threads) {
        if ("main".equals(thread.name())) {
            mainThread = thread;
        }
    }

    //Create and register MethodEntryRequest, so we can pause execution at first line of main later on
    MethodEntryRequest methodEntryRequest = mgr.createMethodEntryRequest();
    methodEntryRequest.addClassFilter(mainClass);
    methodEntryRequest.addThreadFilter(mainThread);
    methodEntryRequest.enable();

    //Resume the execution of the remote vm
    vm.resume();

    //Resume the execution of the main thread in remote vm
    mainThread.resume();

    //Waiting for our needed MethodEntryEvent so execution started at first line of main method
    Event event = null;
    while (true) {
        EventSet eventSet = null;
        try {
            eventSet = eventQueue.remove();
        } catch (InterruptedException e) {
            System.err.println("Something went wrong while waiting for MethodEntryEvent");
            e.printStackTrace();
            System.exit(0);
        }
        event = eventSet.eventIterator().next();
        if (event instanceof MethodEntryEvent) {
            break;
        }
    }

    //Indicates whether there is still code execution in our remote vm
    boolean codeIsExecuting = true;

    //Step loop until there is no code execution
    while(codeIsExecuting){
        //Filter for steeping not into java api methods
        final String[] noBreakpointRequests = {"java.*", "javax.*", "sun.*", "com.sun.*"};

        //Creating our StepRequest for each line of code
        StepRequest stepRequest = mgr.createStepRequest(mainThread, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
        for(String classInFilter : noBreakpointRequests){ //Apply filter 
            stepRequest.addClassExclusionFilter(classInFilter);
        }
        stepRequest.addCountFilter(1);
        try{
            stepRequest.enable();
        }catch(IllegalThreadStateException e){
            //program reached end of code, so there is no code execution anymore
            codeIsExecuting = false;
            System.out.println("Code execution ended...");
            break;
        }

        //Extract data from current vm execution state
        //TODO
        System.out.println("TODO - extract data from current vm execution state");

        //Test
        StackFrame stackFrame = null;
        try {
            stackFrame = mainThread.frame(0);
            System.out.println(stackFrame.location());
        } catch (IncompatibleThreadStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //Resume vm for code execution
        vm.resume();

        try {
            Thread.sleep(10L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //StepRequest is done, remove it from event queue to prevent lock
        mgr.deleteEventRequest(stepRequest);
    }

    //Debugging has ended, we free our remote vm
    try{
        vm.dispose();
    }catch(VMDisconnectedException e){

    }

    //Print results
    //TODO
    System.out.println("TODO - print results...");

}
}

Unfortunally, if I delete the Thread.sleep(), I'm getting a IncompatibleThreadStateException at line

stackFrame = mainThread.frame(0);

Exception:

com.sun.jdi.IncompatibleThreadStateException at com.sun.tools.jdi.ThreadReferenceImpl.privateFrames(ThreadReferenceImpl.java:436)
at com.sun.tools.jdi.ThreadReferenceImpl.frame(ThreadReferenceImpl.java:355)
at VMStart.main(VMStart.java:143)

What's wrong? Do I have to wait for a certain event before stepping one line further?


Solution

  • Finally, I found a solution to myself...

    This code has to be inserted AFTER the vm.resume() instruction:

    boolean go = false;
    while (!go) {
        EventSet eventSet = null;
        try {
            eventSet = eventQueue.remove();
        } catch (InterruptedException e) {
            System.err.println("Something went wrong while waiting for StepEvent");
            e.printStackTrace();
            System.exit(0);
        }
        for (Event e : eventSet) {
            if (e instanceof StepEvent) {
                System.out.println("Step Event!");
                go = true;
            }
        }
    }