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);
}
}