如何让JDI虚拟机EventQueue接收ClassPreparedEvent并自动设断点?
问题描述
我正在为学校项目开发一款调试器,目标是实现代码单步调试及方法跳过功能。目前使用com.sun.jdi包尝试了多种方案均未成功,JDI事件队列接收不到ClassPreparedEvent,导致无法自动设置断点。以下是我的调试器类、被调试类代码及运行输出,恳请提供解决方案或替代思路。
调试器类代码
package JPIDebugger; import java.util.Map; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.LaunchingConnector; import com.sun.jdi.event.*; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.ClassPrepareRequest; import com.sun.jdi.*; public class JPIDebuggerClass { public static void main(String[] args) throws Exception { int[] breakPointLines = {6, 9}; try { // 启动被调试VM LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager() .defaultConnector(); Map<String, Connector.Argument> arguments = launchingConnector.defaultArguments(); arguments.get("main").setValue("JPIDebugger.Debuggee"); VirtualMachine vm = launchingConnector.launch(arguments); // 注册类加载事件请求 ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); classPrepareRequest.addClassFilter("JPIDebugger.Debuggee"); classPrepareRequest.enable(); while (true) { EventQueue eventQueue = vm.eventQueue(); EventSet eventSet = eventQueue.remove(); EventIterator eventIterator = eventSet.eventIterator(); System.out.println(eventIterator.toString() + " Event Set is received."); while (eventIterator.hasNext()) { Event event = eventIterator.next(); System.out.println(event.toString() + " is the event"); if (event instanceof ClassPrepareEvent) { System.out.println("Class is prepared"); ClassType classType = (ClassType) ((ClassPrepareEvent) event).referenceType(); for (int lineNumber : breakPointLines) { Location location = classType.locationsOfLine(lineNumber).get(0); BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location); bpReq.enable(); } } if (event instanceof BreakpointEvent) { System.out.println("Breakpoint is hit"); LocatableEvent locatableEvent = (LocatableEvent) event; StackFrame stackFrame = locatableEvent.thread().frame(0); if (stackFrame.location().toString().contains("JPIDebugger.Debuggee")) { Map<LocalVariable, Value> visibleVariables = stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Variables at " + stackFrame.location().toString() + " > "); for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) { System.out.println(entry.getKey().name() + " = " + entry.getValue()); } } } vm.resume(); } } } catch (Exception e) { e.printStackTrace(); } } }
被调试类代码
package JPIDebugger; public class Debuggee { public static void main(String[] args) { String jpda = "Java Platform Debugger Architecture"; System.out.println("Hi Everyone, Welcome to " + jpda); // 此处设断点 String jdi = "Java Debug Interface"; // 此处设断点并单步调试 String text = "Today, we'll dive into " + jdi; System.out.println(text); } }
运行输出
com.sun.tools.jdi.EventSetImpl$Itr@2957fcb0 Event Set is received. VMStartEvent in thread main is the event com.sun.tools.jdi.EventSetImpl$Itr@3b764bce Event Set is received. VMDeathEvent is the event com.sun.tools.jdi.EventSetImpl$Itr@45fe3ee3 Event Set is received. VMDisconnectEvent is the event com.sun.jdi.VMDisconnectedException at jdk.jdi/com.sun.tools.jdi.EventQueueImpl.removeUnfiltered(EventQueueImpl.java:200) at jdk.jdi/com.sun.tools.jdi.EventQueueImpl.remove(EventQueueImpl.java:97) at jdk.jdi/com.sun.tools.jdi.EventQueueImpl.remove(EventQueueImpl.java:83) at JPIDebugger.JPIDebuggerClass.main(JPIDebuggerClass.java:31)
问题根源与解决方案
1. 核心问题
代码在处理VMStartEvent后直接调用vm.resume(),导致被调试VM立刻执行完所有逻辑,而此时ClassPrepareRequest刚注册完成,根本来不及捕获Debuggee类的加载事件。
2. 修复方案
调整事件处理的暂停/恢复逻辑,针对不同事件做差异化处理:
- VM启动事件:不立即恢复VM,保持暂停状态等待类加载事件触发
- 类加载事件:设置完断点后再恢复VM
- 断点触发事件:处理完变量打印后恢复VM
- VM结束/断开事件:直接退出循环终止调试
3. 修改后的核心代码
while (true) { EventQueue eventQueue = vm.eventQueue(); EventSet eventSet = eventQueue.remove(); EventIterator eventIterator = eventSet.eventIterator(); System.out.println(eventIterator.toString() + " Event Set is received."); // 统一控制是否恢复VM,默认不恢复 boolean shouldResume = false; while (eventIterator.hasNext()) { Event event = eventIterator.next(); System.out.println(event.toString() + " is the event"); if (event instanceof ClassPrepareEvent) { System.out.println("Class is prepared"); ClassType classType = (ClassType) ((ClassPrepareEvent) event).referenceType(); for (int lineNumber : breakPointLines) { Location location = classType.locationsOfLine(lineNumber).get(0); BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location); bpReq.enable(); } // 设置断点后允许恢复VM shouldResume = true; } if (event instanceof BreakpointEvent) { System.out.println("Breakpoint is hit"); LocatableEvent locatableEvent = (LocatableEvent) event; StackFrame stackFrame = locatableEvent.thread().frame(0); if (stackFrame.location().toString().contains("JPIDebugger.Debuggee")) { Map<LocalVariable, Value> visibleVariables = stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Variables at " + stackFrame.location().toString() + " > "); for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) { System.out.println(entry.getKey().name() + " = " + entry.getValue()); } } // 断点处理完成后允许恢复VM shouldResume = true; } if (event instanceof VMStartEvent) { // VM启动时保持暂停,等待类加载 shouldResume = false; } if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) { // VM结束或断开时退出循环 return; } } // 统一执行恢复逻辑 if (shouldResume) { vm.resume(); } }
4. 额外注意事项
- 编译被调试类时必须添加
-g参数,确保生成调试信息,否则locationsOfLine可能无法找到对应行号的代码位置 - 可以给事件请求设置暂停策略,确保事件触发时VM暂停:
classPrepareRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
内容的提问来源于stack exchange,提问作者Felix
相关产品推荐
相关产品推荐

