Een inleiding tot de Java Debug Interface (JDI)

1. Overzicht

We kunnen ons afvragen hoe algemeen erkende IDE's zoals IntelliJ IDEA en Eclipse foutopsporingsfuncties implementeren. Deze tools zijn sterk afhankelijk van de Java Platform Debugger Architecture (JPDA).

In dit inleidende artikel bespreken we de Java Debug Interface API (JDI) die beschikbaar is onder JPDA.

Tegelijkertijd, we zullen een aangepast debuggerprogramma schrijven stap voor stap, vertrouwd raken met handige JDI-interfaces.

2. Inleiding tot JPDA

Java Platform Debugger Architecture (JPDA) is een set goed ontworpen interfaces en protocollen die worden gebruikt om Java te debuggen.

Het biedt drie speciaal ontworpen interfaces om aangepaste debuggers te implementeren voor een ontwikkelomgeving in desktopsystemen.

Om te beginnen helpt de Java Virtual Machine Tool Interface (JVMTI) ons te communiceren en de uitvoering van applicaties die in de JVM worden uitgevoerd, te beheren.

Dan is er het Java Debug Wire Protocol (JDWP) dat het protocol definieert dat wordt gebruikt tussen de te testen applicatie (debuggee) en de debugger.

Eindelijk wordt de Java Debug Interface (JDI) gebruikt om de debugger-applicatie te implementeren.

3. Wat is JDI?

Java Debug Interface API is een set interfaces die door Java worden geleverd om de frontend van de debugger te implementeren. JDI is de hoogste laag van de JPDA.

Een debugger gebouwd met JDI kan applicaties debuggen die draaien in elke JVM die JPDA ondersteunt. Tegelijkertijd kunnen we het in elke foutopsporingslaag aansluiten.

Het biedt de mogelijkheid om toegang te krijgen tot de VM en zijn status, samen met toegang tot variabelen van de debuggee. Tegelijkertijd maakt het het mogelijk om de breekpunten, stepping, watchpoints in te stellen en threads af te handelen.

4. Installatie

We hebben twee afzonderlijke programma's nodig - een debuggee en een debugger - om de implementaties van JDI te begrijpen.

Eerst zullen we een voorbeeldprogramma schrijven als debuggee.

Laten we een JDIEvoorbeeldDebuggee klas met een paar Draad variabelen en println uitspraken:

public class JDIExampleDebuggee {public static void main (String [] args) {String jpda = "Java Platform Debugger Architecture"; System.out.println ("Hallo allemaal, welkom bij" + jpda); // voeg hier een breekpunt toe String jdi = "Java Debug Interface"; // voeg hier een breekpunt toe en stap hier ook in String text = "Vandaag duiken we in" + jdi; System.out.println (tekst); }}

Vervolgens zullen we een foutopsporingsprogramma schrijven.

Laten we een JDIEvoorbeeldDebugger klasse met eigenschappen om het foutopsporingsprogramma vast te houden (debugClass) en regelnummers voor breekpunten (breakPointLines):

openbare klasse JDIExampleDebugger {privéklasse debugClass; private int [] breakPointLines; // getters en setters}

4.1. LaunchingConnector

In eerste instantie heeft een foutopsporingsprogramma een connector nodig om een ​​verbinding tot stand te brengen met de virtuele doelmachine (VM).

Vervolgens moeten we de foutopsporing instellen als die van de connector hoofd argument. Eindelijk moet de connector de virtuele machine starten voor foutopsporing.

Om dit te doen, biedt JDI een Bootstrap klasse die een instantie geeft van de LaunchingConnector. De LaunchingConnector biedt een kaart van de standaardargumenten, waarin we de hoofd argument.

Laten we daarom het connectAndLaunchVM methode naar de JDIDebuggerExample klasse:

openbare VirtualMachine connectAndLaunchVM () gooit Uitzondering {LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager () .defaultConnector (); Kaartargumenten = launchingConnector.defaultArguments (); arguments.get ("main"). setValue (debugClass.getName ()); return launchingConnector.launch (argumenten); }

Nu gaan we de hoofd methode naar de JDIDebuggerExample class om het JDIEvoorbeeldDebuggee:

public static void main (String [] args) gooit Uitzondering {JDIExampleDebugger debuggerInstance = nieuwe JDIExampleDebugger (); debuggerInstance.setDebugClass (JDIExampleDebuggee.class); int [] breakPoints = {6, 9}; debuggerInstance.setBreakPointLines (breakPoints); VirtualMachine vm = null; probeer {vm = debuggerInstance.connectAndLaunchVM (); vm. hervatten (); } catch (uitzondering e) {e.printStackTrace (); }}

Laten we onze beide lessen samenstellen, JDIEvoorbeeldDebuggee (debuggee) en JDIEvoorbeeldDebugger (debugger):

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

Laten we de Javac commando dat hier in detail wordt gebruikt.

De -g optie genereert alle foutopsporingsinformatie zonder welke, kunnen we zien AbsentInformationException.

En -cp zal de tools.jar in het klassenpad om de klassen te compileren.

Alle JDI-bibliotheken zijn beschikbaar onder tools.jar van de JDK. Zorg er daarom voor dat u het tools.jar in het klassenpad bij zowel compilatie als uitvoering.

Dat is alles, nu zijn we klaar om onze aangepaste debugger uit te voeren JDIEvoorbeeldontbugger:

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

Merk op ":." met tools.jar. Dit zal worden toegevoegd tools.jar naar het classpath voor de huidige runtime (gebruik ";." in windows).

4.2. Bootstrap en ClassPrepareRequest

Het uitvoeren van het foutopsporingsprogramma hier zal geen resultaten opleveren, aangezien we de klasse niet hebben voorbereid op foutopsporing en de breekpunten niet hebben ingesteld.

De Virtuele machine klasse heeft de eventRequestManager methode om verschillende verzoeken te maken, zoals ClassPrepareRequest, BreakpointRequest, en StepEventRequest.

Dus laten we de enableClassPrepareRequest methode naar de JDIEvoorbeeldDebugger klasse.

Dit filtert het JDIEvoorbeeldDebuggee class en maakt de ClassPrepareRequest:

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

4.3. ClassPrepareEvent en BreakpointRequest

Een keer, ClassPrepareRequest voor de JDIEvoorbeeldDebuggee class is ingeschakeld, begint de gebeurteniswachtrij van de VM exemplaren van het ClassPrepareEvent.

Gebruik makend van ClassPrepareEvent, we kunnen de locatie een breekpunt laten instellen en een BreakPointRequest.

Om dit te doen, voegen we het setBreakPoints methode naar de JDIEvoorbeeldDebugger klasse:

openbare ongeldige setBreakPoints (VirtualMachine vm, ClassPrepareEvent-gebeurtenis) genereert AbsentInformationException {ClassType classType = (ClassType) event.referenceType (); voor (int lineNumber: breakPointLines) {Location location = classType.locationsOfLine (lineNumber) .get (0); BreakpointRequest bpReq = vm.eventRequestManager (). CreateBreakpointRequest (locatie); bpReq.enable (); }}

4.4. BreakPointEvent en StackFrame

Tot nu toe hebben we de klasse voorbereid op foutopsporing en de breekpunten ingesteld. Nu moeten we de BreakPointEvent en geef de variabelen weer.

JDI biedt de StackFrame class, om de lijst met alle zichtbare variabelen van de debuggee te krijgen.

Laten we daarom het displayVariabelen methode naar de JDIEvoorbeeldDebugger klasse:

public void displayVariables (LocatableEvent-gebeurtenis) gooit IncompatibleThreadStateException, AbsentInformationException {StackFrame stackFrame = event.thread (). frame (0); if (stackFrame.location (). toString (). bevat (debugClass.getName ())) {Map visibleVariables = stackFrame .getValues ​​(stackFrame.visibleVariables ()); System.out.println ("Variabelen op" + stackFrame.location (). ToString () + ">"); voor (Map.Entry entry: visibleVariables.entrySet ()) {System.out.println (entry.getKey (). name () + "=" + entry.getValue ()); }}}

5. Foutopsporingsdoel

Bij deze stap is alles wat we nodig hebben om het hoofd methode van de JDIEvoorbeeldDebugger om te beginnen met debuggen.

Daarom gebruiken we de reeds besproken methoden zoals enableClassPrepareRequest, setBreakPoints, en variabelen weergeven:

probeer {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 ("Virtuele machine is verbroken."); } catch (uitzondering e) {e.printStackTrace (); }

Laten we nu eerst het JDIDebuggerExample klasse weer met de reeds besproken Javac opdracht.

En als laatste voeren we het debugger-programma uit samen met alle wijzigingen om de uitvoer te zien:

Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 6> args = instantie van java.lang.String [0] (id = 93) Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 9> jpda = "Java Platform Debugger Architecture" -args = instantie van java.lang.String [0] (id = 93) Virtuele machine is verbroken.

Hoera! We hebben met succes het JDIEvoorbeeldDebuggee klasse. Tegelijkertijd hebben we de waarden van de variabelen op de breekpuntlocaties (regel 6 en 9) weergegeven.

Daarom is onze aangepaste debugger klaar.

5.1. StepRequest

Foutopsporing vereist ook het doorlopen van de code en het controleren van de status van de variabelen bij de volgende stappen. Daarom maken we een stapverzoek op het breekpunt.

Bij het maken van de instantie van het StepRequest, we moeten de grootte en diepte van de trede opgeven. We zullen definiëren STEP_LINE en OVERSTAPPEN respectievelijk.

Laten we een methode schrijven om het stapverzoek in te schakelen.

Voor de eenvoud beginnen we te stappen bij het laatste breekpunt (regel nummer 9):

public void enableStepRequest (VirtualMachine vm, BreakpointEvent-gebeurtenis) {// schakel stapverzoek in voor laatste breekpunt als (event.location (). toString (). bevat (debugClass.getName () + ":" + breakPointLines [breakPointLines.length- 1])) {StepRequest stepRequest = vm.eventRequestManager () .createStepRequest (event.thread (), StepRequest.STEP_LINE, StepRequest.STEP_OVER); stepRequest.enable (); }}

Nu kunnen we het hoofd methode van de JDIEvoorbeeldDebugger, om het stapverzoek in te schakelen wanneer het een BreakPointEvent:

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

5.2. StepEvent

Net als bij de BreakPointEvent, kunnen we de variabelen ook weergeven bij de StepEvent.

Laten we het hoofd methode dienovereenkomstig:

if (gebeurtenisinstantie van StepEvent) {debuggerInstance.displayVariables ((StepEvent) -gebeurtenis); }

Ten slotte zullen we de debugger uitvoeren om de status van de variabelen te zien terwijl we door de code lopen:

Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 6> args = instantie van java.lang.String [0] (id = 93) Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 9> args = instantie van java.lang.String [0] (id = 93) jpda = "Java Platform Debugger Architecture" Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 10> args = instantie van java.lang.String [0] (id = 93) jpda = "Java-platform Debugger-architectuur "jdi =" Java-foutopsporingsinterface "Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 11> args = instantie van java.lang.String [0] (id = 93) jpda =" Java Platform Debugger-architectuur "jdi =" Java Debug Interface "text =" Vandaag duiken we in Java Debug Interface "Variabelen op com.baeldung.jdi.JDIExampleDebuggee: 12> args = instantie van java.lang.String [0] (id = 93) jpda =" Java Platform Debugger Architecture "jdi =" Java Debug Interface "text =" Vandaag duiken we in de Java Debug Interface "Virtuele machine is verbroken.

Als we de uitvoer vergelijken, zullen we ons realiseren dat debugger vanaf regel nummer 9 is ingeslagen en de variabelen bij alle volgende stappen weergeeft.

6. Lees de uitvoer van de uitvoering

Dat merken we misschien println verklaringen van de JDIEvoorbeeldDebuggee class heeft geen deel uitgemaakt van de debugger-uitvoer.

Volgens de JDI-documentatie, als we de VM starten via LaunchingConnector, de uitvoer- en foutstromen moeten worden gelezen door de Werkwijze voorwerp.

Laten we het daarom toevoegen aan het Tenslotte clausule van onze hoofd methode:

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

Nu zal het uitvoeren van het debugger-programma ook het println verklaringen van de JDIEvoorbeeldDebuggee class naar de debugging output:

Hallo allemaal, Welkom bij Java Platform Debugger Architecture. Vandaag gaan we dieper in op Java Debug Interface

7. Conclusie

In dit artikel hebben we de Java Debug Interface (JDI) API onderzocht die beschikbaar is onder de Java Platform Debugger Architecture (JPDA).

Onderweg hebben we een aangepaste debugger gebouwd met behulp van de handige interfaces van JDI. Tegelijkertijd hebben we ook stepping-mogelijkheden aan de debugger toegevoegd.

Aangezien dit slechts een inleiding was tot JDI, wordt het aanbevolen om te kijken naar de implementaties van andere interfaces die beschikbaar zijn onder JDI API.

Zoals gewoonlijk zijn alle code-implementaties beschikbaar op GitHub.