VSCode 调试适配器
Debug Adapter Protocol 架构与调试扩展开发
目录
- DAP 架构
- contributes.debuggers
- DebugConfigurationProvider
- DebugAdapterDescriptorFactory
- vscode.debug 命名空间
- 高频面试题
DAP 架构
三层架构
通信流程
- 用户点击「开始调试」→ VSCode 根据
launch.json的type找到对应调试扩展 - 扩展通过
DebugAdapterDescriptorFactory提供 DA 进程的启动方式 - VSCode 启动 Debug Adapter 进程,建立 JSON-RPC 通信
- DA 与真实调试器(如 Node.js、GDB)通信,将 DAP 请求转换为调试器命令
- 调试器返回结果,DA 转换为 DAP 响应,VSCode 更新 UI
为什么需要 DAP?
- 语言无关:VSCode 只需实现一套调试 UI
- 复用性:同一 Debug Adapter 可用于其他支持 DAP 的 IDE
- 进程隔离:DA 独立进程,崩溃不影响编辑器
contributes.debuggers
基础贡献
json
{
"contributes": {
"breakpoints": [
{ "language": "javascript" },
{ "language": "typescript" }
],
"debuggers": [
{
"type": "myDebug",
"label": "My Debugger",
"program": "./out/debugAdapter.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
"required": ["program"],
"properties": {
"program": {
"type": "string",
"description": "Program to debug",
"default": "${workspaceFolder}/index.js"
}
}
},
"attach": {
"required": ["port"],
"properties": {
"port": {
"type": "number",
"description": "Port to attach to"
}
}
}
},
"initialConfigurations": [
{
"type": "myDebug",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/index.js"
}
],
"configurationSnippets": [
{
"label": "Node.js: Attach",
"body": {
"type": "myDebug",
"request": "attach",
"name": "Attach to Process",
"port": 9229
}
}
]
}
]
}
}字段说明
| 字段 | 作用 |
|---|---|
type | launch.json 中的 type,唯一标识 |
program | Debug Adapter 入口文件 |
runtime | 运行环境,如 node |
configurationAttributes | launch/attach 的 JSON Schema |
initialConfigurations | 新建 launch.json 时的默认配置 |
configurationSnippets | 用户可选的配置片段 |
DebugConfigurationProvider
动态提供配置
typescript
import * as vscode from 'vscode';
// 提供初始配置(用户首次创建 launch.json)
const initialProvider: vscode.DebugConfigurationProvider = {
provideDebugConfigurations(
folder: vscode.WorkspaceFolder | undefined,
token?: vscode.CancellationToken
): vscode.ProviderResult<vscode.DebugConfiguration[]> {
return [
{
type: 'myDebug',
request: 'launch',
name: 'Dynamic Launch',
program: '${workspaceFolder}/main.js'
}
];
}
};
// 解析/修改用户配置(启动前调用)
const resolveProvider: vscode.DebugConfigurationProvider = {
resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
config: vscode.DebugConfiguration,
token?: vscode.CancellationToken
): vscode.ProviderResult<vscode.DebugConfiguration> {
if (!config.program) {
return vscode.window.showInputBox({
placeHolder: 'Enter program path'
}).then((value) => {
if (value) {
config.program = value;
return config;
}
return undefined; // 取消启动
});
}
return config;
}
};
// 注册
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
'myDebug',
initialProvider,
vscode.DebugConfigurationProviderTriggerKind.Initial
),
vscode.debug.registerDebugConfigurationProvider(
'myDebug',
resolveProvider
)
);激活事件
json
{
"activationEvents": [
"onDebugInitialConfigurations",
"onDebugDynamicConfigurations"
]
}DebugAdapterDescriptorFactory
控制 DA 启动方式
typescript
import * as vscode from 'vscode';
import * as path from 'path';
import * as net from 'net';
class MyDebugAdapterDescriptorFactory
implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(
session: vscode.DebugSession,
executable: vscode.DebugAdapterExecutable | undefined
): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
const config = session.configuration;
// 方式 1:连接已有 DA 服务器(用于调试 DA 本身)
if (config.debugServer) {
return new vscode.DebugAdapterServer(config.debugServer);
}
// 方式 2:启动新进程
const adapterPath = path.join(__dirname, 'debugAdapter.js');
return new vscode.DebugAdapterExecutable('node', [adapterPath]);
}
}
// 注册
context.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory(
'myDebug',
new MyDebugAdapterDescriptorFactory()
)
);三种 Descriptor
typescript
// 启动可执行文件
new vscode.DebugAdapterExecutable('node', ['adapter.js']);
// 连接已有服务器
new vscode.DebugAdapterServer(4711);
// 使用 Stream(stdin/stdout)
new vscode.DebugAdapterInlineImplementation(adapter);vscode.debug 命名空间
常用 API
typescript
// 当前调试会话
const session = vscode.debug.activeDebugSession;
if (session) {
console.log('Type:', session.type);
console.log('Config:', session.configuration);
}
// 断点
const breakpoints = vscode.debug.breakpoints;
vscode.debug.addBreakpoints(breakpoints);
vscode.debug.removeBreakpoints(breakpoints);
// 启动调试
await vscode.debug.startDebugging(
vscode.workspace.workspaceFolders?.[0],
'Launch Program'
);
// 停止调试
vscode.debug.stopDebugging(session);
// 监听会话
vscode.debug.onDidStartDebugSession((session) => {
console.log('Started:', session.name);
});
vscode.debug.onDidTerminateDebugSession((session) => {
console.log('Terminated:', session.name);
});自定义请求
typescript
// 向 DA 发送自定义 DAP 请求
const response = await session.customRequest('custom/request', {
someData: 'value'
});高频面试题
Q1: DAP 架构是怎样的?
VSCode(DAP 客户端)↔ Debug Adapter(独立进程)↔ 实际调试器。DA 将 DAP 的 JSON-RPC 请求转换为调试器命令,实现语言无关的调试 UI。
Q2: DebugConfigurationProvider 与 DebugAdapterDescriptorFactory 的区别?
- DebugConfigurationProvider:提供/解析 launch.json 配置,在启动前修改
config - DebugAdapterDescriptorFactory:决定如何启动 DA 进程(可执行文件、连接服务器、Stream)
Q3: 如何调试 Debug Adapter 本身?
- 先以 server 模式启动 DA,监听端口
- 在 launch.json 中加
"debugServer": 4711 - VSCode 不再启动新进程,直接连接该端口
- 在 DA 源码中设断点即可调试
Q4: launch 与 attach 的区别?
- launch:由 DA 启动目标进程
- attach:连接到已运行的进程(需目标支持远程调试,如 Node.js
--inspect)