Skip to content

VSCode 调试适配器

Debug Adapter Protocol 架构与调试扩展开发

目录


DAP 架构

三层架构

通信流程

  1. 用户点击「开始调试」→ VSCode 根据 launch.jsontype 找到对应调试扩展
  2. 扩展通过 DebugAdapterDescriptorFactory 提供 DA 进程的启动方式
  3. VSCode 启动 Debug Adapter 进程,建立 JSON-RPC 通信
  4. DA 与真实调试器(如 Node.js、GDB)通信,将 DAP 请求转换为调试器命令
  5. 调试器返回结果,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
            }
          }
        ]
      }
    ]
  }
}

字段说明

字段作用
typelaunch.json 中的 type,唯一标识
programDebug Adapter 入口文件
runtime运行环境,如 node
configurationAttributeslaunch/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 本身?

  1. 先以 server 模式启动 DA,监听端口
  2. 在 launch.json 中加 "debugServer": 4711
  3. VSCode 不再启动新进程,直接连接该端口
  4. 在 DA 源码中设断点即可调试

Q4: launch 与 attach 的区别?

  • launch:由 DA 启动目标进程
  • attach:连接到已运行的进程(需目标支持远程调试,如 Node.js --inspect

前端面试知识库