在开发或者进行业务测试的时候,可能会遇到当前某些场景需要远程调试来定位或者跳过某些逻辑进行操作。通常可以使用IDE工具来配合远程调试,比如参考下面的文章:
Java项目远程debug_java远程debug-CSDN博客
但实际上Java提供了jdi(Java debug interface)来供调试,下面是一段完成的调试代码以供参考:
package org.debugger;
import com.sun.jdi.*;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
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.tools.jdi.SocketAttachingConnector;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class VmRewriteService {
final String BoolType = "com.sun.tools.jdi.BooleanValueImpl";
final String StringType = "com.sun.tools.jdi.StringReferenceImpl";
VirtualMachine virtualMachine;
public void RunDebugger(String host, String className, String line, String focusType, String hopVal) throws Exception {
String port = "8181"; // 默认使用8181端口
connectVm(host, port);
System.out.println("[JvmRemoteDebugger] - connect " + host + ":" + port + " success...");
rewriteVal(className, Integer.parseInt(line), focusType, genHopeVal(focusType, hopVal));
}
public void Stop() {
if (virtualMachine != null) {
virtualMachine.dispose();
}
}
private Value genHopeVal(String focusType, String hopVal) {
if (focusType.equals(BoolType)) {
return virtualMachine.mirrorOf(hopVal != null && hopVal.toLowerCase(Locale.ROOT).equals("true"));
}
if (focusType.equals(StringType)) {
return virtualMachine.mirrorOf(hopVal);
}
return null;
}
private void connectVm(String host, String port) throws Exception {
VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
List<AttachingConnector> connectors = vmm.attachingConnectors();
SocketAttachingConnector sac = null;
for (AttachingConnector ac : connectors) {
if (ac instanceof SocketAttachingConnector) {
sac = (SocketAttachingConnector) ac;
}
}
if (sac == null) {
throw new Exception("[JvmRemoteDebugger] - 未找到socket连接器");
}
//设置主机地址,端口信息
Map<String, Connector.Argument> arguments = sac.defaultArguments();
Connector.Argument hostArg = arguments.get("hostname");
Connector.Argument portArg = arguments.get("port");
Connector.Argument timeoutArg = arguments.get("timeout");
hostArg.setValue(host);
portArg.setValue(port);
timeoutArg.setValue("6000"); // 连接超时时间6S
virtualMachine = sac.attach(arguments);
}
/**
* rewrite 操作
*/
private void rewriteVal(String className, int line, String focusType, Value hopVal) throws Exception {
//相应的请求调用通过requestManager来完成
EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
//创建一个代码判断,因此需要获取相应的类,以及具体的断点位置,即相应的代码行。
ClassType clazz = (ClassType) virtualMachine.classesByName(className).get(0);
//设置断点代码位置
System.out.println("[JvmRemoteDebugger] - allLineLocations:" + clazz.allLineLocations());
Location location = clazz.locationsOfLine(line).get(0);
System.out.println("[JvmRemoteDebugger] - location:" + location);
//创建新断点并设置阻塞模式为线程阻塞,即只有当前线程被阻塞
BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(location);
//设置阻塞并启动
breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
breakpointRequest.enable();
//获取vm的事件队列
EventQueue eventQueue = virtualMachine.eventQueue();
boolean isRunning = true;
while (isRunning) {
//不断地读取事件并处理断点记录事件
EventSet eventSet = eventQueue.remove();
EventIterator eventIterator = eventSet.eventIterator();
while (eventIterator.hasNext()) {
Event event = eventIterator.next();
try {
execute(event, focusType, hopVal);
} catch (ClassCastException e) {
// 断点获取不到事件原因是断开连接了,此时清除掉记录
System.out.println("[JvmRemoteDebugger] - event lost delete vm");
isRunning = false;
break;
}
}
//将相应线程resume,表示继续运行
eventSet.resume();
}
}
/**
* 处理监听到事件
*/
private void execute(Event event, String focusType, Value val) throws Exception {
//获取的event为一个抽象的事件记录,可以通过类型判断转型为具体的事件,这里我们转型为BreakpointEvent,即断点记录,
BreakpointEvent breakpointEvent = (BreakpointEvent) event;
//并通过断点处的线程拿到线程帧,进而获取相应的变量信息,并打印记录。
ThreadReference threadReference = breakpointEvent.thread();
StackFrame stackFrame = threadReference.frame(0);
List<LocalVariable> localVariables = stackFrame.visibleVariables();
//输出当前线程栈帧保存的变量数据
localVariables.forEach(t -> {
Value value = stackFrame.getValue(t);
System.out.println(value.getClass().getName());
if (value.getClass().getName().equals(focusType)) {
try {
// 重写当前帧变量的值
stackFrame.setValue(t, val);
System.out.println("[JvmRemoteDebugger] - rewrite:" + focusType + "," + val);
} catch (InvalidTypeException | ClassNotLoadedException e) {
e.printStackTrace();
}
}
System.out.println("[JvmRemoteDebugger] - local->" + value.type() + "," + value.getClass() + "," + value);
});
}
}
以上代码可打印当前断点的变量和对变量是bool、string类的进行重写