In earlier article, we understood how to programatically set a breakpoint in a debug target code & how to fetch local variables when breakpoint is hit.
Java Debug Interface API (JDI) – Hello World Example | Programmatic debugging for beginners
In this article, we will take it further & step through the code line by line.
Example in this article
- We will take same example from earlier article.
- We will start debugging from first line & then step through remaining lines.
- At each step, we will read local variables & see their value changing in each lines.
Debug Target
We will modify debug target from earlier article little bit. Instead of creating hello world variable in one line, we will incrementally keep appending to prepare String “Hello World!” in 4 lines. This way we can debug & read this variable & see variable changing at each step.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package com.itsallbinary.java.jdi; /** * Debug this program with JDI & read all local variables. * * @author ravik * */ public class HelloWorld { public static void main(String[] args) { String helloWorld = "Hello"; helloWorld += " "; helloWorld += "World"; helloWorld += "!"; String welcome = "Welcome to Its All Binary !"; String greeting = helloWorld + welcome; System.out.println("Hi Everyone, " + greeting);// Put a break point at this line. } } |
Lets debug & step
Now we will take same example from earlier article & add additional request for stepping when we hit breakpoint.
- StepRequest.STEP_LINE – This tells VM to step to next line location in debug target program.
- StepRequest.STEP_OVER – This tells VM to step over the line i.e. if its a call to other method, then instead of going inside of that method, just go over it & get output of that method.
1 2 3 4 |
// Request for stepping through lines after this breakpoint StepRequest stepRequest = event.virtualMachine().eventRequestManager().createStepRequest( ((BreakpointEvent) event).thread(), StepRequest.STEP_LINE, StepRequest.STEP_OVER); stepRequest.enable(); |
This request will make VM trigger “StepEvent” here onwards. Now we can add code to process StepEvent. We will keep this code similar to what we did in breakpoint event i.e. to print local variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* * If this is StepEvent, then read & print variables. */ if (event instanceof StepEvent) { // Get values of all variables that are visible and print StackFrame stackFrame = ((StepEvent) event).thread().frame(0); Map<LocalVariable, Value> visibleVariables = (Map<LocalVariable, Value>) stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Local Variables at this step ="); for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) { System.out.println(" " + entry.getKey().name() + " = " + entry.getValue()); } } |
Here is the complete code with addition over earlier article highlighted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
package com.itsallbinary.java.jdi; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.Map; import com.sun.jdi.Bootstrap; import com.sun.jdi.ClassType; import com.sun.jdi.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.StackFrame; import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.Value; import com.sun.jdi.VirtualMachine; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.LaunchingConnector; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.StepEvent; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.ClassPrepareRequest; import com.sun.jdi.request.StepRequest; /** * Hello world example for Java Debugging API i.e. JDI. Very basic & simple * example for stepping through lines. * * @author ravik * */ public class HelloWorldJDISteppingExample { public static void main(String[] args) throws Exception { Class classToDebug = HelloWorld.class; int lineNumberToPutBreakpoint = 12; // First line in main method. /* * Prepare connector, set class to debug & launch VM. */ LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager().defaultConnector(); Map<String, Connector.Argument> env = launchingConnector.defaultArguments(); env.get("main").setValue(classToDebug.getName()); VirtualMachine vm = launchingConnector.launch(env); /* * Request VM to trigger event when HelloWorld class is prepared. */ ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); classPrepareRequest.addClassFilter(classToDebug.getName()); classPrepareRequest.enable(); EventSet eventSet = null; try { while ((eventSet = vm.eventQueue().remove(100)) != null) { for (Event event : eventSet) { /* * If this is ClassPrepareEvent, then set breakpoint */ if (event instanceof ClassPrepareEvent) { ClassPrepareEvent evt = (ClassPrepareEvent) event; ClassType classType = (ClassType) evt.referenceType(); Location location = classType.locationsOfLine(lineNumberToPutBreakpoint).get(0); BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location); bpReq.enable(); } /* * If this is BreakpointEvent, then read & print variables. */ if (event instanceof BreakpointEvent) { // disable the breakpoint event event.request().disable(); // Get values of all variables that are visible and print StackFrame stackFrame = ((BreakpointEvent) event).thread().frame(0); Map<LocalVariable, Value> visibleVariables = (Map<LocalVariable, Value>) stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Local Variables ="); for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) { System.out.println(" " + entry.getKey().name() + " = " + entry.getValue()); } // Request for stepping through lines after this breakpoint StepRequest stepRequest = event.virtualMachine().eventRequestManager().createStepRequest( ((BreakpointEvent) event).thread(), StepRequest.STEP_LINE, StepRequest.STEP_OVER); stepRequest.enable(); } /* * If this is StepEvent, then read & print variables. */ if (event instanceof StepEvent) { // Get values of all variables that are visible and print StackFrame stackFrame = ((StepEvent) event).thread().frame(0); Map<LocalVariable, Value> visibleVariables = (Map<LocalVariable, Value>) stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Local Variables at this step ="); for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) { System.out.println(" " + entry.getKey().name() + " = " + entry.getValue()); } } vm.resume(); } } } catch (VMDisconnectedException e) { System.out.println("VM is now disconnected."); e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { InputStreamReader reader = new InputStreamReader(vm.process().getInputStream()); OutputStreamWriter writer = new OutputStreamWriter(System.out); char[] buf = new char[1024]; reader.read(buf); writer.write(buf); writer.flush(); } } } |
Execute & check output
Now use exact same steps from earlier article to execute this program. Follow instructions from earlier article to avoid any issues.
Now as you can see inf the output, local variable ‘helloWorld’ shows incrementally getting updated at each step & eventually form complete string “Hello World!”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
Java_Tutorials\src\main\java>java com.itsallbinary.java.jdi.HelloWorldJDISteppingExample Local Variables = args = instance of java.lang.String[0] (id=101) Local Variables at this step = helloWorld = "Hello" args = instance of java.lang.String[0] (id=101) Local Variables at this step = helloWorld = "Hello " args = instance of java.lang.String[0] (id=101) Local Variables at this step = helloWorld = "Hello World" args = instance of java.lang.String[0] (id=101) Local Variables at this step = helloWorld = "Hello World!" args = instance of java.lang.String[0] (id=101) Local Variables at this step = helloWorld = "Hello World!" args = instance of java.lang.String[0] (id=101) welcome = "Welcome to Its All Binary !" Local Variables at this step = helloWorld = "Hello World!" args = instance of java.lang.String[0] (id=101) greeting = "Hello World!Welcome to Its All Binary !" welcome = "Welcome to Its All Binary !" Local Variables at this step = helloWorld = "Hello World!" args = instance of java.lang.String[0] (id=101) greeting = "Hello World!Welcome to Its All Binary !" welcome = "Welcome to Its All Binary !" Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = Local Variables at this step = VM is now disconnected. Hi Everyone, Hello World!Welcome to Its All Binary ! |
Congrats, we have successfully stepped through entire debug target program & printed local variables.