基于Java JDI自研调试器:如何处理被调试控制台应用的输入?
我来帮你解决这个JDI调试器的卡顿问题——你遇到的核心矛盾是调试器和被调试程序共享同一个控制台输入流,导致被调试程序等待输入时,调试器的readLine()也会被阻塞。下面是针对你提出的两个方案的详细实现方法:
方案1:在独立控制台窗口启动被调试程序
这个方案最直接,让被调试程序在单独的窗口运行,彻底隔离两者的输入输出,不会互相干扰。
在Windows环境下,你可以修改connectAndLaunchVM方法的启动逻辑,用cmd /c start唤起新窗口:
public static VirtualMachine connectAndLaunchVM() throws IOException, IllegalConnectorArgumentsException { // 获取默认的启动连接器 LaunchingConnector connector = Bootstrap.virtualMachineManager().defaultConnector(); Map<String, Connector.Argument> args = connector.defaultArguments(); // 设置被调试程序的主类(替换成你的目标程序主类) args.get("main").setValue("com.yourproject.TargetMain"); // 关键:修改启动命令,让被调试程序在新窗口运行 String originalCmd = args.get("command").value(); // 用cmd /c start 包裹原命令,引号内是新窗口的标题 args.get("command").setValue("cmd /c start \"被调试程序\" " + originalCmd); // 启动并返回虚拟机 return connector.launch(args); }
启动后,被调试程序会在新的CMD窗口中运行,你可以直接在那个窗口输入内容,调试器则在原窗口正常记录代码行和属性,完全不会卡顿。如果是Linux/macOS环境,只需把启动命令换成xterm -e(Linux)或open -a Terminal(macOS)即可。
方案2:在调试器中转发被调试程序的IO流
如果不想用独立窗口,你可以通过独立线程转发被调试程序的输入输出——把调试器控制台的输入转发给被调试程序,同时把被调试程序的输出打印到调试器控制台。
注意:IO处理必须放在单独线程中,绝对不能和JDI的事件循环共用一个线程,否则会再次出现卡顿。
代码实现(嵌入你的主方法)
VirtualMachine vm = null; try { vm = log.connectAndLaunchVM(); log.enableClassPrepareRequests(vm); // 获取被调试程序对应的Process对象 Process debuggedProcess = vm.process(); // 线程1:转发被调试程序的输出到调试器控制台 new Thread(() -> { try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(debuggedProcess.getInputStream()))) { String outputLine; while ((outputLine = outputReader.readLine()) != null) { // 给被调试程序的输出加前缀,方便区分 System.out.println("[被调试程序输出] " + outputLine); } } catch (IOException e) { System.out.println("被调试程序输出流已关闭:" + e.getMessage()); } }).start(); // 线程2:转发调试器控制台的输入到被调试程序 new Thread(() -> { try (BufferedWriter inputWriter = new BufferedWriter(new OutputStreamWriter(debuggedProcess.getOutputStream())); BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) { String inputLine; while ((inputLine = consoleReader.readLine()) != null) { inputWriter.write(inputLine); inputWriter.newLine(); // 必须加换行符,匹配被调试程序的readLine()逻辑 inputWriter.flush(); // 立即写入,避免缓冲堆积 } } catch (IOException e) { System.out.println被调试程序输入流已关闭:" + e.getMessage()); } }).start(); // 原有的JDI事件循环逻辑 EventSet eventSet; while ((eventSet = vm.eventQueue().remove()) != null){ for (Event e : eventSet){ if(e instanceof ClassPrepareEvent) { log.handleClassPrepareEvents(vm, (ClassPrepareEvent) e); } if(e instanceof StepEvent){ log.printLocalVariables((StepEvent) e); } vm.resume(); } } } catch (VMDisconnectedException e){ System.out.println("虚拟机已断开连接。"); } catch (Exception e){ e.printStackTrace(); }
方案2的注意事项
- 写入被调试程序输入时,一定要调用
flush(),否则输入会留在缓冲中无法被被调试程序读取 - 给被调试程序的输出加前缀,能避免和调试器自身的输出混淆
- 当被调试程序退出时,IO流会自动关闭,转发线程也会随之终止
内容的提问来源于stack exchange,提问作者GreenTea26
相关产品推荐
相关产品推荐

