VS Code 调试适配器协议 DAP 是什么?架构原理和使用详解
调试适配器协议(DAP)是 VS Code 定义的一套标准通信协议,让编辑器和调试器解耦:编辑器只管发 DAP 请求,调试器只管响应——中间的适配器负责翻译。这样 VS Code 不需要内置每个调试器,只要有人写了对应的适配器,就能调试任何语言。
三层架构
VS Code (客户端) <--DAP协议--> 调试适配器 <--私有协议--> 调试器 UI/交互 JSON-RPC 适配器翻译 GDB/LLDB/Node...
没有 DAP 之前,每个编辑器要为每个调试器写一套集成代码(M*N 问题)。有了 DAP,编辑器只实现 DAP 客户端,调试器只实现 DAP 适配器(M+N 问题)。VS Code、JetBrains、Vim 都能复用同一个适配器。
核心工作流
一次调试会话的典型流程:
- initialize — 客户端告诉适配器自己的能力,适配器返回支持的功能
- launch 或 attach — 启动新程序或附加到已有进程
- setBreakpoints — 设置断点
- configurationDone — 配置完成,开始执行
- 适配器发 stopped 事件 — 程序在断点处暂停
- 客户端发 stackTrace / scopes / variables — 查看调用栈和变量
- continue / next / stepIn / stepOut — 控制执行
- terminated 事件 — 调试结束
所有请求和事件都是 JSON 格式,通过 stdin/stdout 传输。这意味着适配器可以是任何语言写的——Node.js、Python、Rust 都行。
用户视角:怎么用 DAP
普通开发者不需要直接写 DAP 请求。你在 VS Code 里按 F5 调试,背后就是 DAP 在工作。你只需要在 launch.json 里配置好调试器类型:
json{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Node", "program": "${workspaceFolder}/app.js" } ] }
type 字段决定了用哪个适配器。常见类型:node(内置)、python(ms-python 扩展)、cppdbg(C/C++ 扩展)、chrome(浏览器调试)。
开发者视角:怎么写适配器
如果你要为自己的语言或运行时写调试支持,用 @vscode/debugadapter 包:
typescriptimport { DebugSession, InitializedEvent, StoppedEvent } from '@vscode/debugadapter'; class MyDebugSession extends DebugSession { protected initializeRequest(response): void { response.body = { supportsConfigurationDoneRequest: true, supportsEvaluateForHovers: true }; this.sendResponse(response); this.sendEvent(new InitializedEvent()); } protected launchRequest(response, args): void { // 启动你的调试器,连接目标进程 this.sendResponse(response); } protected setBreakPointsRequest(response, args): void { // 把断点信息翻译成你的调试器能理解的格式 response.body = { breakpoints: args.breakpoints.map(bp => ({ verified: true, line: bp.line })) }; this.sendResponse(response); } }
关键在于:launchRequest 和 setBreakPointsRequest 里你需要把 DAP 请求翻译成你的调试器的私有命令。适配器就是翻译层。
追问
DAP 和 LSP 是什么关系?
LSP(Language Server Protocol)管编辑:代码补全、跳转定义、诊断。DAP 管调试:断点、单步、变量查看。两者互补,都是微软提出的解耦协议。一个语言扩展通常同时实现 LSP(编辑体验)和 DAP(调试体验)。
launch 和 attach 有什么区别?
launch 是 VS Code 启动目标程序并开始调试——适合开发阶段。attach 是连接到一个已经运行的进程——适合调试线上问题或容器内的服务。attach 模式需要指定进程 ID 或端口(如 9229 用于 Node.js 的 inspector)。
为什么有时候调试器启动很慢?
适配器启动时要初始化调试器、加载符号表、设置断点——符号表特别大的时候(如 C++ 大项目)这一步可能要几秒到几十秒。可以在 launch.json 里设 preLaunchTask: null 跳过不必要的预构建任务,或用 skipFiles 过滤掉不想单步进入的库代码。