Pengenalan Antaramuka Debug Java (JDI)

1. Gambaran keseluruhan

Kita mungkin tertanya-tanya bagaimana IDE yang dikenali secara meluas seperti IntelliJ IDEA dan Eclipse melaksanakan ciri penyahpepijatan. Alat-alat ini sangat bergantung pada Java Platform Debugger Architecture (JPDA).

Dalam artikel pengantar ini, kita akan membincangkan Java Interug Interface API (JDI) yang tersedia di bawah JPDA.

Pada masa yang sama, kami akan menulis program debugger khusus langkah demi langkah, membiasakan diri dengan antara muka JDI yang berguna.

2. Pengenalan JPDA

Java Platform Debugger Architecture (JPDA) adalah satu set antara muka dan protokol yang dirancang dengan baik yang digunakan untuk men-debug Java.

Ini menyediakan tiga antaramuka yang dirancang khas, untuk menerapkan debuger khusus untuk lingkungan pengembangan dalam sistem desktop.

Untuk memulakan, Antaramuka Alat Mesin Maya Java (JVMTI) membantu kita berinteraksi dan mengawal pelaksanaan aplikasi yang berjalan di JVM.

Kemudian, ada Java Debug Wire Protocol (JDWP) yang menentukan protokol yang digunakan antara aplikasi yang diuji (debuggee) dan debugger.

Akhirnya, Java Debug Interface (JDI) digunakan untuk melaksanakan aplikasi debugger.

3. Apa itu JDI ?

Java Debug Interface API adalah sekumpulan antaramuka yang disediakan oleh Java, untuk menerapkan frontend debugger. JDI adalah lapisan tertinggi JPDA .

Debugger yang dibina dengan JDI dapat men-debug aplikasi yang berjalan di mana-mana JVM yang menyokong JPDA. Pada masa yang sama, kita dapat menghubungkannya ke lapisan debugging apa pun.

Ini menyediakan kemampuan untuk mengakses VM dan keadaannya bersama dengan akses ke pemboleh ubah debuggee. Pada masa yang sama, ia memungkinkan untuk mengatur titik putus, melangkah, titik pengawasan dan mengendalikan utas.

4. Persediaan

Kami memerlukan dua program berasingan - debuggee dan debugger - untuk memahami pelaksanaan JDI.

Pertama, kita akan menulis contoh program sebagai debuggee.

Mari buat kelas JDIExampleDebuggee dengan beberapa pembolehubah String dan pernyataan println :

public class JDIExampleDebuggee { public static void main(String[] args) { String jpda = "Java Platform Debugger Architecture"; System.out.println("Hi Everyone, Welcome to " + jpda); // add a break point here String jdi = "Java Debug Interface"; // add a break point here and also stepping in here String text = "Today, we'll dive into " + jdi; System.out.println(text); } }

Kemudian, kami akan menulis program penyahpepijat.

Mari buat kelas JDIExampleDebugger dengan sifat untuk mengadakan program debugging ( debugClass ) dan nombor baris untuk breakpoint ( breakPointLines ):

public class JDIExampleDebugger { private Class debugClass; private int[] breakPointLines; // getters and setters }

4.1. LaunchingConnector

Pada mulanya, penyahpepijat memerlukan penyambung untuk mewujudkan hubungan dengan Mesin Maya sasaran (VM).

Kemudian, kita perlu menetapkan debuggee sebagai argumen utama penyambung . Akhirnya, penyambung harus melancarkan VM untuk penyahpepijatan.

Untuk melakukannya, JDI menyediakan kelas Bootstrap yang memberikan contoh LaunchingConnector . The LaunchingConnector menyediakan peta daripada hujah-hujah lalai, di mana kita boleh menetapkan utama hujah.

Oleh itu, mari tambah kaedah connectAndLaunchVM ke kelas JDIDebuggerExample :

public VirtualMachine connectAndLaunchVM() throws Exception { LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager() .defaultConnector(); Map arguments = launchingConnector.defaultArguments(); arguments.get("main").setValue(debugClass.getName()); return launchingConnector.launch(arguments); }

Sekarang, kami akan menambahkan kaedah utama ke kelas JDIDebuggerExample untuk menyahpepijat JDIExampleDebuggee:

public static void main(String[] args) throws Exception { JDIExampleDebugger debuggerInstance = new JDIExampleDebugger(); debuggerInstance.setDebugClass(JDIExampleDebuggee.class); int[] breakPoints = {6, 9}; debuggerInstance.setBreakPointLines(breakPoints); VirtualMachine vm = null; try { vm = debuggerInstance.connectAndLaunchVM(); vm.resume(); } catch(Exception e) { e.printStackTrace(); } }

Mari kumpulkan kedua-dua kelas kami, JDIExampleDebuggee (debuggee) dan JDIExampleDebugger (debugger):

javac -g -cp "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/lib/tools.jar" com/baeldung/jdi/*.java

Mari kita bincangkan arahan javac yang digunakan di sini, secara terperinci.

Pilihan -g menghasilkan semua maklumat penyahpepijatan tanpa itu, kita mungkin melihat AbsentInformationException .

Dan -cp akan menambahkan tools.jar di classpath untuk menyusun kelas.

All JDI libraries are available under tools.jar of the JDK. Therefore, make sure to add the tools.jar in the classpath at both compilation and execution.

That's it, now we are ready to execute our custom debugger JDIExampleDebugger:

java -cp "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/lib/tools.jar:." JDIExampleDebugger

Note the “:.” with tools.jar. This will append tools.jar to the classpath for current run time (use “;.” on windows).

4.2. Bootstrap and ClassPrepareRequest

Executing the debugger program here will give no results since we haven't prepared the class for debugging and set the breakpoints.

The VirtualMachine class has the eventRequestManager method to create various requests like ClassPrepareRequest, BreakpointRequest, and StepEventRequest.

So, let's add the enableClassPrepareRequest method to the JDIExampleDebugger class.

This will filter the JDIExampleDebuggee class and enables the ClassPrepareRequest:

public void enableClassPrepareRequest(VirtualMachine vm) { ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); classPrepareRequest.addClassFilter(debugClass.getName()); classPrepareRequest.enable(); }

4.3. ClassPrepareEvent and BreakpointRequest

Once, ClassPrepareRequest for the JDIExampleDebuggee class is enabled, the event queue of the VM will start having instances of the ClassPrepareEvent.

Using ClassPrepareEvent, we can get the location to set a breakpoint and creates a BreakPointRequest.

To do so, let's add the setBreakPoints method to the JDIExampleDebugger class:

public void setBreakPoints(VirtualMachine vm, ClassPrepareEvent event) throws AbsentInformationException { ClassType classType = (ClassType) event.referenceType(); for(int lineNumber: breakPointLines) { Location location = classType.locationsOfLine(lineNumber).get(0); BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location); bpReq.enable(); } }

4.4. BreakPointEvent and StackFrame

So far, we've prepared the class for debugging and set the breakpoints. Now, we need to catch the BreakPointEvent and display the variables.

JDI provides the StackFrame class, to get the list of all the visible variables of the debuggee.

Therefore, let's add the displayVariables method to the JDIExampleDebugger class:

public void displayVariables(LocatableEvent event) throws IncompatibleThreadStateException, AbsentInformationException { StackFrame stackFrame = event.thread().frame(0); if(stackFrame.location().toString().contains(debugClass.getName())) { Map visibleVariables = stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Variables at " + stackFrame.location().toString() + " > "); for (Map.Entry entry : visibleVariables.entrySet()) { System.out.println(entry.getKey().name() + " = " + entry.getValue()); } } }

5. Debug Target

At this step, all we need is to update the main method of the JDIExampleDebugger to start debugging.

Hence, we'll use the already discussed methods like enableClassPrepareRequest, setBreakPoints, and displayVariables:

try { vm = debuggerInstance.connectAndLaunchVM(); debuggerInstance.enableClassPrepareRequest(vm); EventSet eventSet = null; while ((eventSet = vm.eventQueue().remove()) != null) { for (Event event : eventSet) { if (event instanceof ClassPrepareEvent) { debuggerInstance.setBreakPoints(vm, (ClassPrepareEvent)event); } if (event instanceof BreakpointEvent) { debuggerInstance.displayVariables((BreakpointEvent) event); } vm.resume(); } } } catch (VMDisconnectedException e) { System.out.println("Virtual Machine is disconnected."); } catch (Exception e) { e.printStackTrace(); }

Now firstly, let's compile the JDIDebuggerExample class again with the already discussed javac command.

And last, we'll execute the debugger program along with all the changes to see the output:

Variables at com.baeldung.jdi.JDIExampleDebuggee:6 > args = instance of java.lang.String[0] (id=93) Variables at com.baeldung.jdi.JDIExampleDebuggee:9 > jpda = "Java Platform Debugger Architecture" args = instance of java.lang.String[0] (id=93) Virtual Machine is disconnected.

Hurray! We've successfully debugged the JDIExampleDebuggee class. At the same time, we've displayed the values of the variables at the breakpoint locations (line number 6 and 9).

Therefore, our custom debugger is ready.

5.1. StepRequest

Debugging also requires stepping through the code and checking the state of the variables at subsequent steps. Therefore, we'll create a step request at the breakpoint.

While creating the instance of the StepRequest, we must provide the size and depth of the step. We'll define STEP_LINE and STEP_OVER respectively.

Let's write a method to enable the step request.

For simplicity, we'll start stepping at the last breakpoint (line number 9):

public void enableStepRequest(VirtualMachine vm, BreakpointEvent event) { // enable step request for last break point if (event.location().toString(). contains(debugClass.getName() + ":" + breakPointLines[breakPointLines.length-1])) { StepRequest stepRequest = vm.eventRequestManager() .createStepRequest(event.thread(), StepRequest.STEP_LINE, StepRequest.STEP_OVER); stepRequest.enable(); } }

Now, we can update the main method of the JDIExampleDebugger, to enable the step request when it is a BreakPointEvent:

if (event instanceof BreakpointEvent) { debuggerInstance.enableStepRequest(vm, (BreakpointEvent)event); }

5.2. StepEvent

Similar to the BreakPointEvent, we can also display the variables at the StepEvent.

Let's update the main method accordingly:

if (event instanceof StepEvent) { debuggerInstance.displayVariables((StepEvent) event); }

At last, we'll execute the debugger to see the state of the variables while stepping through the code:

Variables at com.baeldung.jdi.JDIExampleDebuggee:6 > args = instance of java.lang.String[0] (id=93) Variables at com.baeldung.jdi.JDIExampleDebuggee:9 > args = instance of java.lang.String[0] (id=93) jpda = "Java Platform Debugger Architecture" Variables at com.baeldung.jdi.JDIExampleDebuggee:10 > args = instance of java.lang.String[0] (id=93) jpda = "Java Platform Debugger Architecture" jdi = "Java Debug Interface" Variables at com.baeldung.jdi.JDIExampleDebuggee:11 > args = instance of java.lang.String[0] (id=93) jpda = "Java Platform Debugger Architecture" jdi = "Java Debug Interface" text = "Today, we'll dive into Java Debug Interface" Variables at com.baeldung.jdi.JDIExampleDebuggee:12 > args = instance of java.lang.String[0] (id=93) jpda = "Java Platform Debugger Architecture" jdi = "Java Debug Interface" text = "Today, we'll dive into Java Debug Interface" Virtual Machine is disconnected.

If we compare the output, we'll realize that debugger stepped in from line number 9 and displays the variables at all subsequent steps.

6. Read Execution Output

We might notice that println statements of the JDIExampleDebuggee class haven't been part of the debugger output.

As per the JDI documentation, if we launch the VM through LaunchingConnector, its output and error streams must be read by the Process object.

Therefore, let's add it to the finally clause of our main method:

finally { InputStreamReader reader = new InputStreamReader(vm.process().getInputStream()); OutputStreamWriter writer = new OutputStreamWriter(System.out); char[] buf = new char[512]; reader.read(buf); writer.write(buf); writer.flush(); }

Now, executing the debugger program will also add the println statements from the JDIExampleDebuggee class to the debugging output:

Hi Everyone, Welcome to Java Platform Debugger Architecture Today, we'll dive into Java Debug Interface

7. Conclusion

In this article, we've explored the Java Debug Interface (JDI) API available under the Java Platform Debugger Architecture (JPDA).

Along the way, we've built a custom debugger utilizing the handy interfaces provided by JDI. At the same time, we've also added stepping capability to the debugger.

As this was just an introduction to JDI, it is recommended to look at the implementations of other interfaces available under JDI API.

As usual, all the code implementations are available over on GitHub.