You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VS Code插件开发:如何跟踪调试过程中添加的断点?

解决VSCode插件调试过程中新增断点跟踪问题

核心问题分析

  1. 原代码用数组索引作为断点ID的映射键,调试中新增断点会打乱索引顺序,导致hitBreakpointIds无法匹配到正确的数据库断点记录。
  2. 未正确利用onDidChangeBreakpoints事件处理动态增删的断点,也未关联VSCode断点实例与调试适配器返回的断点ID。

具体解决方案

1. 重构断点映射结构

将原map拆分为两个独立映射,分别存储不同维度的关联关系:

private vscodeBreakpointToDbId: Record<string, string> = {}; // VSCode断点ID → 数据库断点ID
private debugAdapterBreakpointToVscodeId: Record<string | number, string> = {}; // 调试适配器断点ID → VSCode断点ID
private currentSessionId: string | null = null; // 当前会话ID

2. 完善动态断点变更处理

实现procesBreakpointDuringDebug方法,处理调试过程中断点的新增与移除:

private async procesBreakpointDuringDebug(event: vscode.BreakpointsChangeEvent): Promise<void> {
    if (!this.currentSessionId) return;

    // 处理新增的断点
    for (const breakpoint of event.added) {
        if (breakpoint instanceof vscode.SourceBreakpoint && breakpoint.enabled) {
            const activeSession = vscode.debug.activeDebugSession;
            if (!activeSession) continue;

            const bp = await insertBp(breakpoint, activeSession.id, {});
            await insertEvent(BREAKPOINT.SET, { 
                breakpoint_id: bp.breakpoint_id, 
                session_id: this.currentSessionId 
            });
            this.vscodeBreakpointToDbId[breakpoint.id] = bp.breakpoint_id;
        }
    }

    // 处理移除的断点(可选:记录移除事件)
    for (const breakpoint of event.removed) {
        if (breakpoint instanceof vscode.SourceBreakpoint) {
            const dbBpId = this.vscodeBreakpointToDbId[breakpoint.id];
            if (dbBpId) {
                await insertEvent(BREAKPOINT.REMOVE, { 
                    breakpoint_id: dbBpId, 
                    session_id: this.currentSessionId 
                });
                delete this.vscodeBreakpointToDbId[breakpoint.id];
                // 清理调试适配器ID映射
                Object.keys(this.debugAdapterBreakpointToVscodeId).forEach(key => {
                    if (this.debugAdapterBreakpointToVscodeId[key] === breakpoint.id) {
                        delete this.debugAdapterBreakpointToVscodeId[key];
                    }
                });
            }
        }
    }
}

3. 监听调试适配器响应,关联断点ID

修改createDebugAdapterTracker,添加onDidReceiveMessage方法处理setBreakpoints响应,建立调试端与VSCode端的断点ID关联:

private createDebugAdapterTracker(session: vscode.DebugSession) {
    return {
        onDidSendMessage: this.handleDebugAdapterMessage.bind(this),
        onDidReceiveMessage: this.handleDebugAdapterResponse.bind(this),
        onError: (error: any) => console.error("Debug Adapter Error:", error),
        onExit: (code: any, signal: any) => console.log(`Debug Adapter closed: code=${code}, signal=${signal}`)
    };
}

private async handleDebugAdapterResponse(message: any): Promise<void> {
    // 处理setBreakpoints响应,关联两端断点ID
    if (message.type === 'response' && message.command === 'setBreakpoints') {
        const breakpoints = message.body.breakpoints || [];
        const sourcePath = message.arguments.source.path;

        // 匹配对应文件的VSCode断点
        const vscodeBreakpoints = vscode.debug.breakpoints.filter(bp => 
            bp instanceof vscode.SourceBreakpoint && 
            bp.source?.path === sourcePath
        );

        // 按顺序关联ID(调试适配器返回的断点顺序与请求一致)
        breakpoints.forEach((debugBp: any, index: number) => {
            const vscodeBp = vscodeBreakpoints[index];
            if (vscodeBp && debugBp.id) {
                this.debugAdapterBreakpointToVscodeId[debugBp.id] = vscodeBp.id;
            }
        });
    }
}

4. 调整断点触发时的匹配逻辑

修改processBreakpointHit,通过调试适配器ID反向查找数据库断点记录:

private async processBreakpointHit(message: any): Promise<void> {
    if (!this.currentSessionId) return;

    const breakpointIds = message.body.hitBreakpointIds || [];
    console.log(`Breakpoint triggered IDs: ${breakpointIds}`);

    for (const debugBpId of breakpointIds) {
        const vscodeBpId = this.debugAdapterBreakpointToVscodeId[debugBpId];
        if (vscodeBpId) {
            const dbBpId = this.vscodeBreakpointToDbId[vscodeBpId];
            if (dbBpId) {
                await insertEvent(BREAKPOINT.TRIGGER, { 
                    breakpoint_id: dbBpId, 
                    session_id: this.currentSessionId 
                });
            }
        }
    }

    // 保留 fallback 逻辑
    if (breakpointIds.length === 0) {
        await this.handleFallbackBreakpoint(message);
    }
}

5. 修正会话初始化与清理逻辑

更新会话相关方法,替换原map的使用:

private async finalizeSession(session: vscode.DebugSession, ss: any): Promise<void> {
    this.currentSessionId = ss.session_id;
    ss.is_debug = !session.configuration?.noDebug;
    await ss.save();
}

private async handleTerminateDebugSession(session: vscode.DebugSession): Promise<void> {
    if (this.currentSessionId && this.deletedSession === null) {
        this.deletedSession = session.id;
        await Event.create({ session_id: this.currentSessionId, type: SESSION.END });
    }
    // 清理所有映射
    this.vscodeBreakpointToDbId = {};
    this.debugAdapterBreakpointToVscodeId = {};
    this.currentSessionId = null;
}

6. 修正初始断点处理逻辑

修改processBreakpoints,改用VSCode断点ID作为映射键:

private async processBreakpoints(session: vscode.DebugSession, ss: any): Promise<void> {
    this.currentSessionId = ss.session_id;
    for (const breakpoint of vscode.debug.breakpoints) {
        if (breakpoint instanceof vscode.SourceBreakpoint) {
            let bp = await insertBp(breakpoint, session.id, {});
            await insertEvent(BREAKPOINT.SET, { breakpoint_id: bp.breakpoint_id, session_id: ss.session_id });
            this.vscodeBreakpointToDbId[breakpoint.id] = bp.breakpoint_id;
        }
    }
}

关键说明

  • 利用VSCode断点实例的id属性作为唯一标识,彻底避免数组索引带来的顺序问题。
  • 通过监听调试适配器的setBreakpoints响应,建立调试端与VSCode端的断点ID关联,确保断点触发时能精准匹配数据库记录。
  • 动态处理断点的增删事件,保证调试过程中新增的断点也能被完整跟踪。

内容的提问来源于stack exchange,提问作者I try so hard but I cry harder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:55:59