1. Обзор
Мы можем задаться вопросом, насколько широко признанные IDE, такие как IntelliJ IDEA и Eclipse, реализуют функции отладки . Эти инструменты в значительной степени зависят от архитектуры отладчика платформы Java (JPDA).
В этой вводной статье мы обсудим API интерфейса отладки Java (JDI), доступный в JPDA.
В то же время, мы напишем пользовательскую программу отладчика шаг за шагом, знакомясь с удобными интерфейсами JDI.
2. Введение в JPDA
Архитектура отладчика платформы Java (JPDA)-это набор хорошо разработанных интерфейсов и протоколов, используемых для отладки Java.
Он предоставляет три специально разработанных интерфейса для реализации пользовательских отладчиков для среды разработки в настольных системах.
Начнем с того, что интерфейс виртуальных машин Java (JVMTI) помогает нам взаимодействовать и контролировать выполнение приложений, запущенных в JVM .
Затем существует протокол Java Debug Wire Protocol (JDWP), который определяет протокол, используемый между тестируемым приложением (debuggee) и отладчиком.
Наконец, интерфейс отладки Java (JDI) используется для реализации приложения отладчика.
3. Что такое JDI?
API интерфейса отладки Java – это набор интерфейсов, предоставляемых Java для реализации интерфейса отладчика. JDI является самым высоким уровнем JPDA .
Отладчик, построенный с помощью JDI, может отлаживать приложения, работающие в любой JVM, которая поддерживает JPDA. В то же время мы можем подключить его к любому уровню отладки.
Он предоставляет возможность доступа к виртуальной машине и ее состоянию наряду с доступом к переменным отладчика. В то же время он позволяет устанавливать точки останова, шаг, точки наблюдения и обрабатывать потоки.
4. Настройка
Для понимания реализаций JDI нам потребуются две отдельные программы – отладчик и отладчик.
Во-первых, мы напишем пример программы в качестве отладчика.
Давайте создадим класс JDIExampleDebuggee с несколькими строковыми переменными и 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); } }
Затем мы напишем программу-отладчик.
Давайте создадим класс JDIExampleDebugger со свойствами для хранения программы отладки ( класс отладки ) и номерами строк для точек останова ( Линии останова ):
public class JDIExampleDebugger { private Class debugClass; private int[] breakPointLines; // getters and setters }
4.1. Запуск коннектора
Сначала отладчику требуется соединитель для установления соединения с целевой виртуальной машиной (ВМ).
Затем нам нужно будет установить отладчик в качестве аргумента main соединителя. Наконец, соединитель должен запустить виртуальную машину для отладки.
Для этого JDI предоставляет класс Bootstrap , который предоставляет экземпляр LaunchingConnector . LaunchingConnector предоставляет карту аргументов по умолчанию , в которой мы можем задать аргумент main .
Поэтому давайте добавим метод connect И Launch VM в пример класса JDIDebugger:
public VirtualMachine connectAndLaunchVM() throws Exception { LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager() .defaultConnector(); Maparguments = launchingConnector.defaultArguments(); arguments.get("main").setValue(debugClass.getName()); return launchingConnector.launch(arguments); }
Теперь мы добавим метод main в пример класса//JDIDebugger для отладки 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(); } }
Давайте скомпилируем оба наших класса, JDIExampleDebuggee (debuggee) и JDIExampleDebugger (debugger):
javac -g -cp "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/lib/tools.jar" com/baeldung/jdi/*.java
Давайте подробно обсудим команду javac , используемую здесь.
Параметр -g генерирует всю отладочную информацию без которой мы можем увидеть Исключение AbsentInformationException .
И -кп добавит добавит в пути к классам для компиляции классов.
Все библиотеки JDI доступны в разделе tools.jar JDK. Поэтому обязательно добавьте инструменты .jar в пути к классу как при компиляции, так и при выполнении.
Вот и все, теперь мы готовы выполнить наш пользовательский отладчик JDIExampleDebugger:
java -cp "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/lib/tools.jar:." JDIExampleDebugger
Обратите внимание на “:.” с tools.jar. Это добавит инструменты.jar в путь к классу для текущего времени выполнения (используйте “;.” в Windows).
4.2. Bootstrap и ClassPrepareRequest
Выполнение программы отладчика здесь не даст никаких результатов, так как мы не подготовили класс для отладки и не установили точки останова.
Класс VirtualMachine имеет метод eventRequestManager для создания различных запросов, таких как ClassPrepareRequest , BreakpointRequest и StepEventRequest.
Итак, давайте добавим метод enable ClassPrepareRequest в класс JDIExampleDebugger .
Это приведет к фильтрации класса JDIExampleDebuggee и включит ClassPrepareRequest:
public void enableClassPrepareRequest(VirtualMachine vm) { ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); classPrepareRequest.addClassFilter(debugClass.getName()); classPrepareRequest.enable(); }
4.3. ClassPrepareEvent и BreakpointRequest
Как только ClassPrepareRequest для класса JDIExampleDebuggee будет включен, в очереди событий виртуальной машины начнут появляться экземпляры ClassPrepareEvent .
Используя ClassPrepareEvent, мы можем получить местоположение для установки точки останова и создать запрос точки останова .
Для этого давайте добавим метод set BreakPoints в класс JDIExampleDebugger :
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. Событие точки останова и кадр стека
До сих пор мы подготовили класс для отладки и установили точки останова. Теперь нам нужно поймать событие точки останова и отобразить переменные.
JDI предоставляет класс StackFrame , чтобы получить список всех видимых переменных отладчика.
Поэтому давайте добавим метод display Variables в класс JDIExampleDebugger :
public void displayVariables(LocatableEvent event) throws IncompatibleThreadStateException, AbsentInformationException { StackFrame stackFrame = event.thread().frame(0); if(stackFrame.location().toString().contains(debugClass.getName())) { MapvisibleVariables = 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. Цель отладки
На этом этапе все, что нам нужно, это обновить main метод JDIExampleDebugger , чтобы начать отладку.
Следовательно, мы будем использовать уже обсужденные методы, такие как enable ClassPrepareRequest , set BreakPoints и 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(); }
Теперь, во-первых, давайте снова скомпилируем пример JDIDebugger с уже обсужденной командой javac .
И наконец, мы выполним программу отладчика вместе со всеми изменениями, чтобы увидеть результат:
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.
Ура! Мы успешно отладили класс JDIExampleDebuggee . В то же время мы отобразили значения переменных в местах точек останова (строки № 6 и 9).
Таким образом, наш пользовательский отладчик готов.
5.1. StepRequest
Отладка также требует пошагового выполнения кода и проверки состояния переменных на последующих шагах. Поэтому мы создадим шаговый запрос в точке останова.
При создании экземпляра запроса Step мы должны указать размер и глубину шага. Мы определим STEP_LINE и STEP_OVER соответственно.
Давайте напишем метод для включения запроса шага.
Для простоты мы начнем с последней точки останова (строка № 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(); } }
Теперь мы можем обновить main метод JDIExampleDebugger , чтобы включить запрос шага , когда это событие точки останова :
if (event instanceof BreakpointEvent) { debuggerInstance.enableStepRequest(vm, (BreakpointEvent)event); }
5.2. StepEvent
Аналогично событию Точка останова , мы также можем отображать переменные в событии Шаг .
Давайте обновим метод main соответствующим образом:
if (event instanceof StepEvent) { debuggerInstance.displayVariables((StepEvent) event); }
Наконец, мы запустим отладчик, чтобы увидеть состояние переменных во время выполнения кода:
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.
Если мы сравним выходные данные, то поймем, что отладчик перешел из строки № 9 и отображает переменные на всех последующих шагах.
6. Считывание Выходных данных Выполнения
Мы могли бы заметить, что операторы println класса JDIExampleDebuggee не были частью выходных данных отладчика.
Поэтому давайте добавим его в предложение finally нашего метода main :
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(); }
Теперь выполнение программы отладчика также добавит операторы println из класса JDIExampleDebuggee в выходные данные отладки:
Hi Everyone, Welcome to Java Platform Debugger Architecture Today, we'll dive into Java Debug Interface
7. Заключение
В этой статье мы рассмотрели API интерфейса отладки Java (JDI) , доступный в архитектуре отладчика платформы Java (JPDA).
Попутно мы создали пользовательский отладчик, использующий удобные интерфейсы, предоставляемые JDI. В то же время мы также добавили в отладчик возможность пошагового выполнения.
Поскольку это было всего лишь введение в JEDI, рекомендуется ознакомиться с реализациями других интерфейсов, доступных в JDI API .
Как обычно, все реализации кода доступны на GitHub .