Рубрики
Без рубрики

Введение в интерфейс отладки Java (JDI)

Краткий и практический обзор интерфейса отладки Java.

Автор оригинала: baeldung.

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();
    Map arguments = 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())) {
        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. Цель отладки

На этом этапе все, что нам нужно, это обновить 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 .