You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:07:46