JVM Debugger

1.JVMDebugger的实现 package com.wanna.web.debug; import com.sun.jdi.*; import com.sun.jdi.connect.AttachingConnector; import com.sun.jdi.connect.Connect

1.JVMDebugger的实现

package com.wanna.web.debug;

import com.sun.jdi.*;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.*;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.StepRequest;
import com.sun.tools.jdi.SocketAttachingConnector;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * @author jianchao.jia
 * @version v1.0
 * @date 2023/1/22
 */
public class JVMDebugger {
    /**
     * 寻找到Map类当中的entrySet方法的函数
     */
    private static final Function<ObjectReference, Method> MAP_ENTRY_SET_METHOD_FUNC = value -> {
        final List<Method> methods = value.referenceType().methodsByName("entrySet");
        for (Method method : methods) {
            try {
                if (method.argumentTypes().isEmpty()) {
                    return method;
                }
            } catch (ClassNotLoadedException ex) {
                // ignore
            }
        }
        return null;
    };

    /**
     * 寻找到Collection类当中的toArray方法的函数
     */
    private static final Function<ObjectReference, Method> COLLECTION_TO_ARRAY_METHOD_FUNC = value -> {
        final List<Method> methods = value.referenceType().methodsByName("toArray");
        for (Method method : methods) {
            try {
                if (method.argumentTypes().isEmpty()) {
                    return method;
                }
            } catch (ClassNotLoadedException ex) {
                // ignore
            }
        }
        return null;
    };

    /**
     * 基础数据类型的包装类型的断言匹配
     */
    private static final Predicate<Type> PRIMITIVE_WRAPPER_PREDICATE = type ->
            type.name().equals("java.lang.Integer")
                    || type.name().equals("java.lang.Byte")
                    || type.name().equals("java.lang.Short")
                    || type.name().equals("java.lang.Long")
                    || type.name().equals("java.lang.Char")
                    || type.name().equals("java.lang.Boolean")
                    || type.name().equals("java.lang.Float")
                    || type.name().equals("java.lang.Double");

    /**
     * Debugger Cache
     */
    private static final Map<String, JVMDebugger> DEBUGGER_CACHE = new ConcurrentHashMap<>();

    /**
     * 获取JVMDebugger的工厂方法
     *
     * @param tag      tag
     * @param hostname hostname
     * @param port     port
     * @return JVMDebugger
     */
    public static JVMDebugger getDebugger(final String tag, final String hostname, final int port) {
        JVMDebugger debugger = getDebugger(tag);
        if (Objects.isNull(debugger)) {
            synchronized (DEBUGGER_CACHE) {
                debugger = new JVMDebugger(hostname, port);
                DEBUGGER_CACHE.put(tag, debugger);
            }
        }
        return getDebugger(tag);
    }

    /**
     * 根据tag去获取JVMDebugger的工厂方法
     *
     * @param tag tag
     * @return JVMDebugger
     */
    public static JVMDebugger getDebugger(final String tag) {
        return DEBUGGER_CACHE.get(tag);
    }

    /**
     * 根据tag去获取到JVMDebugger的工厂方法
     *
     * @param tag tag
     * @return DebugInfo
     */
    public static DebugInfo removeDebugger(final String tag) {
        final DebugInfo debugInfo = new DebugInfo();
        debugInfo.end = true;
        if (Objects.isNull(tag)) {
            return debugInfo;
        }
        final JVMDebugger debugger = getDebugger(tag);
        if (Objects.isNull(debugger)) {
            return debugInfo;
        }
        debugger.disconnect();
        DEBUGGER_CACHE.remove(tag);
        return debugInfo;
    }


    /**
     * VirtualMachine to connect
     */
    private final VirtualMachine virtualMachine;

    /**
     * ThreadReference, 记录断点所停在的线程, 对于断点的操作归根到底都是对于单个线程的操作
     */
    private ThreadReference threadReference;

    /**
     * 当前正在处理的EventSet
     */
    private EventSet currentEventSet;

    /**
     * 当前正在处理的EventRequest(因为在处理后面的事件时, 需要将之前的事件给移除掉, 因此需要抽成为字段)
     */
    private EventRequest currentEventRequest;

    /**
     * 创建一个JVMDebugger实例, 设置成为Private, 使用static工厂方法去进行构建
     *
     * @param hostname 要去连接的目标VM主机名
     * @param port     要去连接的目标VM的JDWP端口号
     * @see #getDebugger(String, String, int)
     * @see #getDebugger(String)
     */
    private JVMDebugger(final String hostname, final int port) {
        // -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5000
        try {
            this.virtualMachine = connectToVM(hostname, port);
        } catch (IllegalConnectorArgumentsException ex) {
            throw new IllegalStateException("Illegal connector arguments", ex);
        } catch (IOException ex) {
            throw new IllegalStateException("Connect to VM(hostname=" + hostname + ", port=" + port + ") failed", ex);
        }
    }

    /**
     * 给指定的类的指定的行上去添加断点
     *
     * @param className  打断点的目标类
     * @param lineNumber 目标行号
     * @return DebugInfo
     */
    public DebugInfo breakpoint(final String className, final int lineNumber) throws Exception {
        createBreakpoint(className, lineNumber);
        return debugVM();
    }

    /**
     * 执行单步跳过
     *
     * @param stepType 单步类型(步入/步过/步出)
     * @return DebugInfo
     */
    public DebugInfo step(final StepType stepType) throws Exception {
        createStepEvent(stepType);
        return debugVM();
    }


    /**
     * 和目标VM断开
     */
    private void disconnect() {
        virtualMachine.dispose();
    }

    private DebugInfo debugVM() throws Exception {
        final EventQueue eventQueue = virtualMachine.eventQueue();

        // 如果没有事件触发, 那么在这里阻塞住...
        this.currentEventSet = eventQueue.remove();

        final DebugInfo debugInfo = new DebugInfo();

        // 当有事件触发时, 就可以在这里去拿到具体的事件类型...
        final EventIterator eventIterator = this.currentEventSet.eventIterator();

        if (eventIterator.hasNext()) {
            final Event event = eventIterator.next();
            // 如果是VM断开连接事件(对面VM关闭了...)
            if (event instanceof VMDisconnectEvent) {
                debugInfo.end = true;
                return debugInfo;

                // 如果是单步调试的事件的话...那么我们需要去处理断点事件
            } else if (event instanceof StepEvent) {
                this.threadReference = ((StepEvent) event).thread();

                // 如果是断点事件的话...那么我们需要去处理断点事件
            } else if (event instanceof BreakpointEvent) {
                this.threadReference = ((BreakpointEvent) event).thread();
            }

            // 在拿到ThreadReference之后, 我们去对该线程的栈帧去进行处理...
            try {
                assert threadReference != null;

                // 获取顶层的栈帧去进行计算
                final StackFrame stackFrame = threadReference.frame(0);
                final Location currentStackFrameLocation = stackFrame.location();
                final List<Field> fields = currentStackFrameLocation.declaringType().allFields();
                final List<LocalVariable> localVariables = stackFrame.visibleVariables();

                // 记录当前所在的类名/方法名/行号
                debugInfo.current = makeStackTraceElement(currentStackFrameLocation);

                // 记录整个线程栈的调用情况...
                for (final StackFrame frame : threadReference.frames()) {
                    debugInfo.stackTraces.add(makeStackTraceElement(frame.location()));
                }

                // 我们在这里去获取到ClassReference, 用于去统计static变量信息, 对于static变量都是通过Class去进行访问的
                for (Field field : fields) {
                    if (!field.isStatic()) {
                        continue;
                    }
                    final Value value = currentStackFrameLocation.declaringType().getValue(field);
                    final Object parsedValue = parseValue(value, 0);
                    debugInfo.staticObjects.add(new DebugInfo.ObjectInfo(field.name(), field.typeName(), parsedValue));
                }

                // 当前栈帧的this对象的相关信息(Note: 这里需要重新获取顶层栈帧, 因为上面计算static变量当前的线程栈已经执行过方法了, 会导致之前的栈帧已经被销毁)
                final ObjectReference currentObject = threadReference.frame(0).thisObject();

                // 如果this不为空, 那么计算当前栈帧的this对象的字段信息(如果this为空, 那么就算了, 不计算了)
                if (currentObject != null) {
                    for (final Field field : fields) {
                        if (field.isStatic()) {
                            continue;
                        }
                        final Value value = currentObject.getValue(field);
                        final Object parsedValue = parseValue(value, 0);
                        debugInfo.fieldObjects.add(new DebugInfo.ObjectInfo(field.name(), field.typeName(), parsedValue));
                    }
                }

                // 计算当前栈帧的局部变量表的相关信息
                for (final LocalVariable localVariable : localVariables) {
                    // Note: 这里需要重新获取顶层栈帧, 因为上面计算字段值导致该线程已经执行过方法了, 从而会导致之前的栈帧已经被销毁
                    final Object parsedValue = parseValue(threadReference.frame(0).getValue(localVariable), 0);
                    debugInfo.localVariables.add(new DebugInfo.ObjectInfo(localVariable.name(), localVariable.typeName(), parsedValue));
                }
                return debugInfo;
            } catch (Exception ex) {
                debugInfo.end = true;
                return debugInfo;
            }
        }
        return debugInfo;
    }

    private Object parseValue(final Value value, final int depth) throws ClassNotLoadedException, IncompatibleThreadStateException, InvocationException, InvalidTypeException {
        if (value instanceof IntegerValue) {
            return ((IntegerValue) value).value();
        } else if (value instanceof ByteValue) {
            return ((ByteValue) value).value();
        } else if (value instanceof ShortValue) {
            return ((ShortValue) value).value();
        } else if (value instanceof LongValue) {
            return ((LongValue) value).value();
        } else if (value instanceof BooleanValue) {
            return ((BooleanValue) value).value();
        } else if (value instanceof DoubleValue) {
            return ((DoubleValue) value).value();
        } else if (value instanceof FloatValue) {
            return ((FloatValue) value).value();
        } else if (value instanceof CharValue) {
            return ((CharValue) value).value();
        } else if (value instanceof StringReference) {
            return ((StringReference) value).value();

            // 如果是对象引用类型(ObjectReference), 那么需要进行解析...
        } else if (value instanceof ObjectReference) {
            final Method toArrayMethod = COLLECTION_TO_ARRAY_METHOD_FUNC.apply((ObjectReference) value);
            final Method entrySetMethod = MAP_ENTRY_SET_METHOD_FUNC.apply((ObjectReference) value);

            // 如果是基础数据的包装类的话
            if (PRIMITIVE_WRAPPER_PREDICATE.test(value.type())) {
                final Field field = ((ObjectReference) value).referenceType().fieldByName("value");
                return parseValue(((ObjectReference) value).getValue(field), depth);

                // 如果是Array的话, 那么直接可以解析Values数组...
            } else if (value instanceof ArrayReference) {
                final List<Object> list = new ArrayList<>();
                for (Value elementValue : ((ArrayReference) value).getValues()) {
                    list.add(parseValue(elementValue, depth));
                }
                return list;

                // 如果是Collection的话, 通过解析toArray的结果去完成...
            } else if (isCollection(value.type()) && toArrayMethod != null) {
                final Value collectionArray = ((ObjectReference) value).invokeMethod(threadReference, toArrayMethod, Collections.emptyList(), 0);
                return parseValue(collectionArray, depth);

                // 如果是Map的话, 通过解析entrySet的方式去完成...
            } else if (isMap(value.type()) && entrySetMethod != null) {
                final Value mapEntrySet = ((ObjectReference) value).invokeMethod(threadReference, entrySetMethod, Collections.emptyList(), 0);
                return parseValue(mapEntrySet, depth);

                // 如果是其他类型的ObjectReference的话
            } else {
                final Map<String, Object> result = new LinkedHashMap<>();
                final String className = ((ObjectReference) value).referenceType().name();
                result.put("className", className);

                // 如果是MapEntry, 那么只去计算Key/Value信息就足够了...别的字段别计算了
                if (isMapEntry(value.type())) {
                    final Field keyField = ((ObjectReference) value).referenceType().fieldByName("key");
                    final Field valueField = ((ObjectReference) value).referenceType().fieldByName("value");

                    final Object parsedKeyValue = parseValue(((ObjectReference) value).getValue(keyField), depth);
                    final Object parsedValueValue = parseValue(((ObjectReference) value).getValue(valueField), depth);
                    result.put("key", parsedKeyValue);
                    result.put("value", parsedValueValue);
                    return result;
                }

                if (Objects.equals(className, "java.lang.Object")) {
                    return result;
                }
                try {
                    final List<Field> fields = ((ObjectReference) value).referenceType().allFields();
                    for (final Field field : fields) {
                        // 不去计算static字段和合成字段
                        if (field.isStatic() || field.isSynthetic()) {
                            continue;
                        }

                        // Note: 这里需要去作为递归的终止条件, 待优化
                        if (depth >= 2) {
                            continue;
                        }
                        final Value fieldValue = ((ObjectReference) value).getValue(field);
                        result.put(field.name(), parseValue(fieldValue, depth + 1));
                    }
                    return result;
                } catch (Exception ex) {
                    return result;
                }
            }
        }
        return null;
    }

    private boolean isMap(final Type type) {
        return isAncestor(type, "java.util.Map");
    }

    private boolean isMapEntry(final Type type) {
        return isAncestor(type, "java.util.Map$Entry");
    }

    private boolean isCollection(final Type type) {
        return isAncestor(type, "java.util.Collection");
    }

    /**
     * 检查目标Type的所有接口、所有父类当中, 是否存在有和assignTo相符合的类?
     *
     * @param type     待检查的目标Type
     * @param assignTo 待比对的类名
     * @return 如果Type存在有assignTo去作为父类/接口, return true; 否则return false
     */
    private boolean isAncestor(final Type type, final String assignTo) {
        if (Objects.isNull(type) || Objects.isNull(assignTo)) {
            return false;
        }
        if (Objects.equals(type.name(), assignTo)) {
            return true;
        }
        if (type instanceof ClassType) {
            for (InterfaceType interfaceType : ((ClassType) type).interfaces()) {
                if (isAncestor(interfaceType, assignTo)) {
                    return true;
                }
            }
            final ClassType superclass = ((ClassType) type).superclass();
            return isAncestor(superclass, assignTo);
        }
        return false;
    }

    private DebugInfo.StackTraceElement makeStackTraceElement(final Location location) {
        return new DebugInfo.StackTraceElement(location.declaringType().name(), location.method().name(), location.lineNumber());
    }

    /**
     * 创建一个单步调试的事件, 为VM添加一个{@link StepRequest}
     *
     * @param stepType StepType(步入/步过/步出)
     */
    private void createStepEvent(StepType stepType) {
        // 获取到目标VM的RequestHandler
        final EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();


        // Note: 先把之前的request移除掉, 不然重复添加EventRequest会出现"Only one step request allowed per thread"异常
        if (this.currentEventRequest != null) {
            eventRequestManager.deleteEventRequest(this.currentEventRequest);
        }

        this.currentEventRequest = eventRequestManager.createStepRequest(threadReference, StepRequest.STEP_LINE, stepType.getIndex());
        this.currentEventRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
        this.currentEventRequest.enable();

        // 如果存在有正在处理的断点事件, 那么需要将该事件先去释放掉, 不然当前断点事件卡不住
        if (this.currentEventSet != null) {
            this.currentEventSet.resume();
        }
    }

    /**
     * 给VM的指定类上添加断点, 添加一个{@link BreakpointRequest},
     * 创建断点会挂起目标VM所有的线程, 因此如果不使用断点的话, 那么需要将断点去释放...
     *
     * @param className  类名
     * @param lineNumber 要打断点的行号
     */
    private void createBreakpoint(final String className, final int lineNumber) throws AbsentInformationException {
        // 获取到目标VM的RequestHandler
        final EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();

        // Note: 先把之前的request移除掉, 不然重复添加EventRequest会出现"Only one step request allowed per thread"异常
        if (this.currentEventRequest != null) {
            eventRequestManager.deleteEventRequest(this.currentEventRequest);
        }

        // 根据ClassName, 从VM当中去寻找到ReferenceTypes
        final List<ReferenceType> referenceTypes = virtualMachine.classesByName(className);
        if (Objects.isNull(referenceTypes) || referenceTypes.isEmpty()) {
            throw new IllegalStateException("Cannot find target Class " + className);
        }
        final ClassType classType = (ClassType) referenceTypes.get(0);

        // 根据行号, 找到该类的对应行号的位置
        final List<Location> locations = classType.locationsOfLine(lineNumber);
        if (Objects.isNull(locations) || locations.isEmpty()) {
            throw new IllegalStateException("Cannot find target Location for given lineNumber " + lineNumber + " in " + className);
        }
        final Location location = locations.get(0);

        // 为给定的ClassName、给定的LineNumber去打上断点...
        this.currentEventRequest = eventRequestManager.createBreakpointRequest(location);

        // 设定挂起策略(挂起全部/挂起处理请求的目标线程/不挂起), 对于设置断点来说, 我们选择去挂起所有的线程...
        currentEventRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
        currentEventRequest.enable();

        // 如果存在有正在处理的断点事件, 那么需要将该事件先去释放掉, 不然当前断点事件卡不住
        if (this.currentEventSet != null) {
            this.currentEventSet.resume();
        }
    }

    /**
     * 连接到目标JVM
     *
     * @param hostname hostname
     * @param port     port
     * @return 连接的目标VM
     *
     * @throws IllegalConnectorArgumentsException 连接参数错误
     * @throws IOException                        连接VM失败
     */
    private VirtualMachine connectToVM(final String hostname, final int port) throws IllegalConnectorArgumentsException, IOException {
        final VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
        final List<AttachingConnector> attachingConnectors = virtualMachineManager.attachingConnectors();

        SocketAttachingConnector socketAttachingConnector = null;

        // 这里有两类Connector, 一类是基于Process, 也就是进程PID的; -->com.sun.tools.jdi.ProcessAttachingConnector
        // 另外一类是基于Socket的, 基于Socket实现通信 -->com.sun.tools.jdi.SocketAttachingConnector
        for (final AttachingConnector attachingConnector : attachingConnectors) {
            if (attachingConnector instanceof SocketAttachingConnector) {
                socketAttachingConnector = (SocketAttachingConnector) attachingConnector;
            }
        }
        if (socketAttachingConnector == null) {
            throw new IllegalStateException("Cannot find SocketAttachingConnector");
        }
        final Map<String, Connector.Argument> args = socketAttachingConnector.defaultArguments();
        args.get("hostname").setValue(hostname);
        args.get("port").setValue(String.valueOf(port));
        return socketAttachingConnector.attach(args);
    }
}

2.封装Debug的返回内容

package com.wanna.web.debug;

import java.util.ArrayList;
import java.util.List;

/**
 * 封装Debug请求的响应信息
 *
 * @author jianchao.jia
 * @version v1.0
 * @date 2023/1/22
 */
public class DebugInfo {

    /**
     * tag
     */
    String tag;

    /**
     * 当前方法所在的栈帧相关信息
     */
    StackTraceElement current;

    /**
     * 是否已经结束?
     */
    boolean end;

    /**
     * StackTrace信息
     */
    List<StackTraceElement> stackTraces = new ArrayList<>();

    /**
     * 字段变量信息
     */
    List<ObjectInfo> fieldObjects = new ArrayList<>();

    /**
     * static变量信息
     */
    List<ObjectInfo> staticObjects = new ArrayList<>();

    /**
     * 局部变量信息
     */
    List<ObjectInfo> localVariables = new ArrayList<>();

    public StackTraceElement getCurrent() {
        return current;
    }

    public void setCurrent(StackTraceElement current) {
        this.current = current;
    }

    public String getTag() {
        return tag;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public boolean isEnd() {
        return end;
    }

    public void setEnd(boolean end) {
        this.end = end;
    }

    public List<StackTraceElement> getStackTraces() {
        return stackTraces;
    }

    public void setStackTraces(List<StackTraceElement> stackTraces) {
        this.stackTraces = stackTraces;
    }

    public List<ObjectInfo> getFieldObjects() {
        return fieldObjects;
    }

    public void setFieldObjects(List<ObjectInfo> fieldObjects) {
        this.fieldObjects = fieldObjects;
    }

    public List<ObjectInfo> getStaticObjects() {
        return staticObjects;
    }

    public void setStaticObjects(List<ObjectInfo> staticObjects) {
        this.staticObjects = staticObjects;
    }

    public List<ObjectInfo> getLocalVariables() {
        return localVariables;
    }

    public void setLocalVariables(List<ObjectInfo> localVariables) {
        this.localVariables = localVariables;
    }


    @Override
    public String toString() {
        return "DebugInfo{" +
                "current=" + current +
                ", stackTraces=" + stackTraces +
                ", fieldObjects=" + fieldObjects +
                ", localVariables=" + localVariables +
                '}';
    }

    public static class StackTraceElement {

        String className;

        String methodName;

        int lineNumber;

        public StackTraceElement(String className, String methodName, int lineNumber) {
            this.className = className;
            this.methodName = methodName;
            this.lineNumber = lineNumber;
        }

        public String getClassName() {
            return className;
        }

        public void setClassName(String className) {
            this.className = className;
        }

        public String getMethodName() {
            return methodName;
        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }

        public int getLineNumber() {
            return lineNumber;
        }

        public void setLineNumber(int lineNumber) {
            this.lineNumber = lineNumber;
        }

        @Override
        public String toString() {
            return "(className='" + className + '\'' +
                    ", methodName='" + methodName + '\'' +
                    ", lineNumber=" + lineNumber + ")";
        }
    }

    public static class ObjectInfo {

        String name;

        String type;

        Object value;

        public ObjectInfo(String name, String type, Object value) {
            this.name = name;
            this.type = type;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return "name='" + name + '\'' +
                    ", type='" + type + '\'' +
                    ", value=" + value;
        }
    }
}

3.单步调试的类型枚举值

package com.wanna.web.debug;

/**
 * 单步调试的类型枚举值
 *
 * @author jianchao.jia
 * @version v1.0
 * @date 2023/1/23
 */
public enum StepType {

    /**
     * 步入
     */
    STEP_INTO(1),

    /**
     * 步过
     */
    STEP_OVER(2),

    /**
     * 步出
     */
    STEP_OUT(3);

    private final int index;

    StepType(int index) {
        this.index = index;
    }

    public int getIndex() {
        return index;
    }

    /**
     * 根据index去获取StepType的枚举值
     *
     * @param index stepType index
     * @return StepType
     */
    public static StepType valueOf(int index) {
        switch (index) {
            case 1:
                return STEP_INTO;
            case 2:
                return STEP_OVER;
            case 3:
                return STEP_OUT;
            default:
                throw new IllegalStateException("Illegal StepType index");
        }
    }
}

4.写个Controller去进行测试

/**
 * @author jianchao.jia
 * @version v1.0
 * @date 2023/1/22
 */
@RestController
public class DebugController {

    @RequestMapping("/debug")
    public Object breakpoint(@RequestParam(required = false, defaultValue = "wanna") final String tag,
                             @RequestParam final String hostname,
                             @RequestParam(required = false, defaultValue = "5000") final int port,
                             @RequestParam final String className,
                             @RequestParam final int lineNumber) throws Exception {
        // 获取到JVMDebugger
        final JVMDebugger jvmDebugger = JVMDebugger.getDebugger(tag, hostname, port);

        // 给目标类上去打断点...
        final DebugInfo debugInfo = jvmDebugger.breakpoint(className, lineNumber);
        debugInfo.setTag(tag);
        return debugInfo;
    }

    @RequestMapping("/step_into")
    public Object stepInto(@RequestParam String tag) throws Exception {
        final JVMDebugger debugger = JVMDebugger.getDebugger(tag);
        return debugger.step(StepType.STEP_INTO);
    }

    @RequestMapping("/step_over")
    public Object stepOver(@RequestParam String tag) throws Exception {
        final JVMDebugger debugger = JVMDebugger.getDebugger(tag);
        return debugger.step(StepType.STEP_OVER);
    }

    @RequestMapping("/step_out")
    public Object stepOut(@RequestParam String tag) throws Exception {
        final JVMDebugger debugger = JVMDebugger.getDebugger(tag);
        return debugger.step(StepType.STEP_OUT);
    }

    @RequestMapping("/disconnect")
    public Object disconnect(@RequestParam final String tag) {
        return JVMDebugger.removeDebugger(tag);
    }
}
Comment