跳转至

调试协议适配与远程调试进阶

调试器与 XDebugger 介绍了调试器扩展架构。本章进一步讨论调试协议适配:如何把 Debug Adapter Protocol、自定义 socket 协议、语言运行时调试 API 或远程调试服务接到 IntelliJ Platform 的 Execution/XDebugger 模型上。

官方 SDK 对 XDebugger 的高层文档较少,实际实现通常需要结合 Execution、Run Configurations、Plugin Compatibility、目标语言插件源码和 API 文档一起做。本文给出工程结构和风险清单,避免直接绑定某个不稳定实现。

架构分层

RunConfiguration
  -> ProgramRunner / RunProfileState
    -> Debug backend process or remote connection
      -> Protocol adapter
        -> XDebugProcess
          -> Breakpoints / StackFrames / Variables / Evaluator

建议把协议适配拆成独立层:

职责
Run Configuration 保存用户配置、校验目标、选择 run/debug
Process launcher 启动本地进程、容器、远程连接或 debug adapter
Protocol client 处理 JSON-RPC、socket、stdio、认证、重连
Debug model mapper 把协议对象映射为 XDebugger 模型
UI integration Console、断点、变量树、表达式求值

不要在 XDebugProcess 中塞满协议细节。它应该协调调试会话,而不是直接承担所有 IO、序列化和状态机。

DAP 与自定义协议

Debug Adapter Protocol 常见对象:

  • initialize / launch / attach
  • setBreakpoints
  • configurationDone
  • threads
  • stackTrace
  • scopes
  • variables
  • evaluate
  • continue / pause / next / stepIn / stepOut
  • stopped / continued / terminated / output events

映射到 IntelliJ:

DAP / 协议概念 IntelliJ 概念
launch / attach Run Configuration + ProgramRunner
stopped event session paused,创建 stack frame
stackTrace XStackFrame 列表
source path + line XSourcePosition
scopes / variables XValue tree
evaluate XDebuggerEvaluator
setBreakpoints XBreakpointHandler
output Console / ProcessHandler

自定义协议也按这张表设计,不要把协议对象直接暴露给 UI。

断点同步

断点同步要处理这些情况:

  • IDE 中新增、删除、启用、禁用断点。
  • 调试会话启动前已有断点。
  • 后端返回断点已验证或不可用。
  • 源文件路径映射失败。
  • 条件断点、日志断点、命中次数。
  • 远程源代码和本地源代码不一致。

建议维护一个断点映射表:

XBreakpoint id -> protocol breakpoint id / source path / line / verified state

如果后端支持 verified breakpoint,应该把不可命中原因反馈到 IDE UI 或 console,而不是静默失败。

Source Path Mapping

远程调试最常见的问题是源位置映射:

问题 处理
远程路径和本地项目路径不同 提供 path mappings 设置
容器路径不同 默认从工作目录推断,可手动覆盖
生成代码 尽量使用 source map 或语言级 symbol mapping
临时脚本 创建 light virtual file 或只显示 console 位置
行列基准不同 统一 0-based / 1-based 转换

映射失败不要让调试器崩溃。应在 console 中给出后端路径和配置建议。

变量树与性能

变量树必须懒加载:

  • 第一层只加载当前 scope。
  • 子对象展开时再请求 variables。
  • 大数组分页或限制数量。
  • 字符串和集合要设置合理预览。
  • 远程请求要可取消。

不要一次性把整个对象图发到前端。大型对象可能包含循环引用、惰性属性、远程 getter 或昂贵计算。

表达式求值

表达式求值要明确上下文:

  • 当前线程。
  • 当前 stack frame。
  • 当前语言或模板上下文。
  • 用户输入是否需要包裹成语句或表达式。
  • 后端是否允许副作用。

错误要按用户可理解的方式展示,例如语法错误、上下文不可用、线程已恢复、后端超时、远程连接断开。

远程连接生命周期

远程调试要处理:

  • 连接超时。
  • 认证失败。
  • 端口占用。
  • debug adapter 进程退出。
  • 网络断开。
  • IDE Stop 被重复调用。
  • 后端已结束但 IDE session 尚未清理。

stop() 必须幂等。重复 stop、进程自然退出、连接断开和用户关闭 IDE 都应走同一套资源释放路径。

Split Mode 注意事项

调试后端通常属于 backend。UI、console 和少量交互在 frontend。Split Mode 下:

  • 不要把 socket client 放到 frontend 访问远程项目路径。
  • 前端只发用户意图和展示结果。
  • 后端持有 protocol client 和项目上下文。
  • RPC DTO 中不要包含 PSI、VirtualFile、Swing 组件。
  • 延迟环境下变量展开和表达式求值要有进度与取消。

测试矩阵

至少覆盖:

  • 本地 launch。
  • attach 到已有进程。
  • 无效路径断点。
  • 条件断点。
  • stop 后资源释放。
  • 后端异常退出。
  • 远程路径映射。
  • 变量树大对象展开。
  • 表达式求值错误。
  • Split Mode 延迟模拟。

参考来源