使用 jdi 构建一个简单的调试器来设置断点并检索变量的值

build a simple debugger with jdi to set breakpoints and retrieve the value of a variable

我希望使用 java debug interface 构建调试器。
我的objective是设置断点,获取一个变量的值。
我找到了 接近我正在寻找的答案,我知道我必须使用以下界面:- VirtualMachineManagerLaunchingConnectorClassPrepareEvent , ClassPrepareRequest。 但是我想不通,如何在特定行设置断点并获取变量的值,或者应该以什么顺序使用接口。

例如在下面的代码中,我如何使用 jdi 继续 运行 它以便我得到变量的值 S

import java.io.*;

class Hello {

  public static void main(String args[]) {
    String S = "Hello World";
    int a = 12;
  }
}

我正在考虑在 a = 12 行或方法 main 结束时设置调试点,以便我得到 S 的值

发现此 article 有用。 这里还有一个很好的 可以帮助你。

或者,您可以检查以下内容project

这里有一个示例代码供您继续使用。

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package jdidebugger;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.ClassType;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.connect.VMStartException;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventIterator;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequestManager;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author bonnie
 */
public class JdiDebugger {

    /**
     * @param options
     * @param main
     * @param classPattern
     * @param methodName
     * @param lineNumber
     * @throws java.io.IOException
     * @throws com.sun.jdi.connect.IllegalConnectorArgumentsException
     * @throws com.sun.jdi.connect.VMStartException
     * @throws java.lang.InterruptedException
     * @throws com.sun.jdi.AbsentInformationException
     * @throws com.sun.jdi.IncompatibleThreadStateException
     */
    public static void onMethodExit(String options, String main, String classPattern, String methodName) throws IOException, IllegalConnectorArgumentsException, VMStartException, InterruptedException, AbsentInformationException, IncompatibleThreadStateException {

        // create and launch a virtual machine
        VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
        LaunchingConnector lc = vmm.defaultConnector();
        Map<String, Connector.Argument> env = lc.defaultArguments();
        env.get("options").setValue(options);
        env.get("main").setValue(main);
        VirtualMachine vm = lc.launch(env);

        // create a class prepare request
        EventRequestManager erm = vm.eventRequestManager();
        ClassPrepareRequest r = erm.createClassPrepareRequest();
        r.addClassFilter(classPattern);
        r.enable();

        EventQueue queue = vm.eventQueue();
        while (true) {
            EventSet eventSet = queue.remove();
            EventIterator it = eventSet.eventIterator();
            while (it.hasNext()) {
                Event event = it.nextEvent();
                if (event instanceof ClassPrepareEvent) {
                    ClassPrepareEvent evt = (ClassPrepareEvent) event;
                    ClassType classType = (ClassType) evt.referenceType();

                    classType.methodsByName(methodName).forEach(new Consumer<Method>() {
                        @Override
                        public void accept(Method m) {
                            List<Location> locations = null;
                            try {
                                locations = m.allLineLocations();
                            } catch (AbsentInformationException ex) {
                                Logger.getLogger(JdiDebuggerOld.class.getName()).log(Level.SEVERE, null, ex);
                            }
                            // get the last line location of the function and enable the 
                            // break point
                            Location location = locations.get(locations.size() - 1);
                            BreakpointRequest bpReq = erm.createBreakpointRequest(location);
                            bpReq.enable();
                        }
                    });

                }
                if (event instanceof BreakpointEvent) {
                    // disable the breakpoint event
                    event.request().disable();

                    ThreadReference thread = ((BreakpointEvent) event).thread();
                    StackFrame stackFrame = thread.frame(0);

                    // print all the visible variables with the respective values
                    Map<LocalVariable, Value> visibleVariables = (Map<LocalVariable, Value>) stackFrame.getValues(stackFrame.visibleVariables());
                    for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) {
                        System.out.println(entry.getKey() + ":" + entry.getValue());
                    }
                }
                vm.resume();
            }
        }
    }
}

这就是调用方法的方式

new jdiDebugger().onMehtodeExit("-cp <whatever your class path is>", "<name of the class that contains the main method>", "<the name of the class that you wish to debug>", "<the name of the method that you want to debug>");

为了回答关于接口的问题,这里是快速解释。

虚拟机 (VM) - JVM,它是 运行 调试目标程序。

Connector - 这将调试器程序连接到调试目标的 JVM。 LaunchingConnector 将启动 JVM 并连接到它。还有连接到现有 运行 JVM 的 AttachingConnector。

Events - 当 VM 在调试模式下运行调试目标程序时,它会触发几个事件,以便调试程序可以根据需要采取行动。调试器程序还可以请求VM触发某些默认不触发的特殊事件。

为了回答问题的断点部分,这里有一个片段。

Location location = classType.locationsOfLine(lineNumberToPutBreakpoint).get(0);
                    BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location);
                    bpReq.enable();

This article 有完整的简单 Hello World 示例和进一步的解释。可能会发现对开始基本理解很有用。