From 5ccec739e304c9878a7df4c67dad237c8bb9fa24 Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Wed, 21 Feb 2018 16:36:34 +0000 Subject: [PATCH 01/26] Add multi-process support --- package.json | 5 +++++ src/backend/backend.ts | 2 +- src/backend/mi2/mi2.ts | 25 ++++++++++++++++----- src/gdb.ts | 4 ++++ src/mibase.ts | 50 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index bea5c72..c0a6bdc 100644 --- a/package.json +++ b/package.json @@ -204,6 +204,11 @@ "description": "Prints all GDB calls to the console", "default": false }, + "multiProcess": { + "type": "boolean", + "description": "Allow multiple process debugging", + "default": false + }, "showDevDebugOutput": { "type": "boolean", "description": "Prints all GDB responses to the console", diff --git a/src/backend/backend.ts b/src/backend/backend.ts index e26bd36..7b0a682 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -64,7 +64,7 @@ export interface IBackend { start(runToStart: boolean): Thenable; stop(): void; detach(): void; - interrupt(): Thenable; + interrupt(all: boolean): Thenable; continue(): Thenable; next(): Thenable; step(): Thenable; diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 1a5c2b7..232f077 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -269,6 +269,16 @@ export class MI2 extends EventEmitter implements IBackend { cmds.push(this.sendCommand("enable-pretty-printing")); if (this.frameFilters) cmds.push(this.sendCommand("enable-frame-filters")); + if (this.multiProcess) { + cmds.push( + this.sendCommand("gdb-set follow-fork-mode parent"), + this.sendCommand("gdb-set detach-on-fork off"), + this.sendCommand("gdb-set non-stop on"), + this.sendCommand("gdb-set schedule-multiple on"), + + this.sendCommand("interpreter-exec console \"handle SIGSYS nostop noprint\"") + ); + } for (const cmd of this.extraCommands) { cmds.push(this.sendCommand(cmd)); } @@ -462,7 +472,7 @@ export class MI2 extends EventEmitter implements IBackend { this.emit("exited-normally", parsed); break; case "exited": // exit with error code != 0 - this.log("stderr", "Program exited with code " + parsed.record("exit-code")); + this.log("stderr", "Inferior exited with code " + parsed.record("exit-code")); this.emit("exited-normally", parsed); break; // case "exited-signalled": // consider handling that explicit possible @@ -483,6 +493,10 @@ export class MI2 extends EventEmitter implements IBackend { this.emit("thread-created", parsed); } else if (record.asyncClass === "thread-exited") { this.emit("thread-exited", parsed); + } else if (record.asyncClass == "thread-group-started") { + this.emit("thread-group-started", parsed); + } else if (record.asyncClass == "thread-group-exited") { + this.emit("thread-group-exited", parsed); } } } @@ -546,21 +560,21 @@ export class MI2 extends EventEmitter implements IBackend { this.sendRaw("-target-detach"); } - interrupt(): Thenable { + interrupt(all: boolean = true): Thenable { if (trace) this.log("stderr", "interrupt"); return new Promise((resolve, reject) => { - this.sendCommand("exec-interrupt").then((info) => { + this.sendCommand("exec-interrupt" + (all ? " --all" : "")).then((info) => { resolve(info.resultRecords.resultClass === "done"); }, reject); }); } - continue(reverse: boolean = false): Thenable { + continue(reverse: boolean = false, all: boolean = true): Thenable { if (trace) this.log("stderr", "continue"); return new Promise((resolve, reject) => { - this.sendCommand("exec-continue" + (reverse ? " --reverse" : "")).then((info) => { + this.sendCommand("exec-continue" + (reverse ? " --reverse" : "") + (all ? " --all" : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); @@ -1023,6 +1037,7 @@ export class MI2 extends EventEmitter implements IBackend { prettyPrint: boolean = true; frameFilters: boolean = true; + multiProcess: boolean = false; printCalls: boolean; debugOutput: boolean; features: string[]; diff --git a/src/gdb.ts b/src/gdb.ts index c1e178c..d7732b9 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -19,6 +19,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum valuesFormatting: ValuesFormattingMode; frameFilters: boolean; printCalls: boolean; + multiProcess: boolean; showDevDebugOutput: boolean; registerLimit: string; } @@ -39,6 +40,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum valuesFormatting: ValuesFormattingMode; frameFilters: boolean; printCalls: boolean; + multiProcess: boolean; showDevDebugOutput: boolean; registerLimit: string; } @@ -75,6 +77,7 @@ class GDBDebugSession extends MI2DebugSession { this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.frameFilters = !!args.frameFilters; this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.multiProcess = !!args.multiProcess; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.stopAtEntry = args.stopAtEntry; this.miDebugger.registerLimit = args.registerLimit ?? ""; @@ -121,6 +124,7 @@ class GDBDebugSession extends MI2DebugSession { this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.frameFilters = !!args.frameFilters; this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.multiProcess = !!args.multiProcess; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.stopAtEntry = args.stopAtEntry; this.miDebugger.registerLimit = args.registerLimit ?? ""; diff --git a/src/mibase.ts b/src/mibase.ts index eb22088..2926341 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -44,6 +44,8 @@ export class MI2DebugSession extends DebugSession { protected miDebugger: MI2; protected commandServer: net.Server; protected serverPath: string; + protected threadGroupPids = new Map(); + protected threadToPid = new Map(); public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { super(debuggerLinesStartAt1, isServer); @@ -64,6 +66,9 @@ export class MI2DebugSession extends DebugSession { this.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this)); this.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this)); this.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent()))); + this.miDebugger.on("thread-group-started", this.threadGroupStartedEvent.bind(this)); + this.miDebugger.on("thread-group-exited", this.threadGroupExitedEvent.bind(this)); + this.sendEvent(new InitializedEvent()); try { this.commandServer = net.createServer(c => { c.on("data", data => { @@ -164,22 +169,41 @@ export class MI2DebugSession extends DebugSession { } protected threadCreatedEvent(info: MINode) { - this.sendEvent(new ThreadEvent("started", info.record("id"))); + let threadId = parseInt(info.record("id"), 10); + + let threadPid = this.threadGroupPids.get(info.record("group-id")); + this.threadToPid.set(threadId, threadPid); + + this.sendEvent(new ThreadEvent("started", threadId)); } protected threadExitedEvent(info: MINode) { - this.sendEvent(new ThreadEvent("exited", info.record("id"))); + let threadId = parseInt(info.record("id"), 10); + + this.threadToPid.delete(info.record("group-id")); + + this.sendEvent(new ThreadEvent("exited", threadId)); + } + + protected threadGroupStartedEvent(info: MINode) { + this.threadGroupPids.set(info.record("id"), info.record("pid")); + } + + protected threadGroupExitedEvent(info: MINode) { + this.threadGroupPids.delete(info.record("id")); } - protected quitEvent() { - this.quit = true; - this.sendEvent(new TerminatedEvent()); + protected quitEvent(info?: MINode) { + if (this.threadGroupPids.size == 0) { + this.quit = true; + this.sendEvent(new TerminatedEvent()); - if (this.serverPath) - fs.unlink(this.serverPath, (err) => { + if (this.serverPath) + fs.unlink(this.serverPath, (err) => { // eslint-disable-next-line no-console console.error("Failed to unlink debug server"); - }); + }); + } } protected launchError(err: any) { @@ -286,7 +310,13 @@ export class MI2DebugSession extends DebugSession { }; for (const thread of threads) { const threadName = thread.name || thread.targetId || ""; - response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName)); + + if (this.threadGroupPids.size > 1) { + let pid = this.threadToPid.get(thread.id); + response.body.threads.push(new Thread(thread.id, `(${pid}) ${thread.id}:${threadName}`)); + } else { + response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); + } } this.sendResponse(response); }).catch((error: MIError) => { @@ -672,6 +702,7 @@ export class MI2DebugSession extends DebugSession { protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { this.miDebugger.continue(true).then(done => { + response.body.allThreadsContinued = true; this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); @@ -680,6 +711,7 @@ export class MI2DebugSession extends DebugSession { protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { this.miDebugger.continue().then(done => { + response.body.allThreadsContinued = true; this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); From 92c61ff0cd0ed68089390fc4ee694bb7ad7309d7 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Fri, 21 Nov 2025 19:48:17 +0100 Subject: [PATCH 02/26] Improvements to multi-processing Apparently there have been some improvements to the debug adapter protocol specification to get some assumptions out of the way but they are not implemented in VSCode. Momentarily this is the best support I think is possible for multi-processing without needing DAP v1.51.x. When resuming and stepping some assumptions had to be made about which thread group needed to be continued/stepped, the reason being is that it is indistinguishable if the command came from the top-ribbon or the process/threads window. --- src/backend/backend.ts | 4 ++-- src/backend/mi2/mi2.ts | 14 +++++++------- src/mibase.ts | 25 +++++++++++++++++-------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/backend/backend.ts b/src/backend/backend.ts index 7b0a682..4aeed3e 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -64,8 +64,8 @@ export interface IBackend { start(runToStart: boolean): Thenable; stop(): void; detach(): void; - interrupt(all: boolean): Thenable; - continue(): Thenable; + interrupt(threadId?: number): Thenable; + continue(reverse?: boolean, threadId?: number): Thenable; next(): Thenable; step(): Thenable; stepOut(): Thenable; diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 232f077..2390973 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -250,7 +250,7 @@ export class MI2 extends EventEmitter implements IBackend { target = debuggerPath.join(cwd, target); const cmds = [ - this.sendCommand("gdb-set target-async on", true), + this.sendCommand("gdb-set mi-async on", true), new Promise(resolve => { this.sendCommand("list-features").then(done => { this.features = done.result("features"); @@ -560,21 +560,21 @@ export class MI2 extends EventEmitter implements IBackend { this.sendRaw("-target-detach"); } - interrupt(all: boolean = true): Thenable { + interrupt(threadId?: number): Thenable { if (trace) - this.log("stderr", "interrupt"); + this.log("stderr", "interrupt" + (threadId ? " --thread-group i" + threadId : "")); return new Promise((resolve, reject) => { - this.sendCommand("exec-interrupt" + (all ? " --all" : "")).then((info) => { + this.sendCommand("exec-interrupt" + (threadId ? " --thread-group i" + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "done"); }, reject); }); } - continue(reverse: boolean = false, all: boolean = true): Thenable { + continue(reverse: boolean = false, threadId?: number): Thenable { if (trace) - this.log("stderr", "continue"); + this.log("stderr", "continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")); return new Promise((resolve, reject) => { - this.sendCommand("exec-continue" + (reverse ? " --reverse" : "") + (all ? " --all" : "")).then((info) => { + this.sendCommand("exec-continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); diff --git a/src/mibase.ts b/src/mibase.ts index 2926341..f79ff8f 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -692,8 +692,8 @@ export class MI2DebugSession extends DebugSession { } } - protected override pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { - this.miDebugger.interrupt().then(done => { + protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { + this.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 3, `Could not pause: ${msg}`); @@ -701,8 +701,12 @@ export class MI2DebugSession extends DebugSession { } protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { - this.miDebugger.continue(true).then(done => { - response.body.allThreadsContinued = true; + this.miDebugger.continue(true, args.threadId).then(done => { + if (!response.hasOwnProperty("body")) { + response.body = Object(); + } + + response.body.allThreadsContinued = false; this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); @@ -710,8 +714,13 @@ export class MI2DebugSession extends DebugSession { } protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { - this.miDebugger.continue().then(done => { - response.body.allThreadsContinued = true; + this.miDebugger.continue(false, args.threadId).then(done => { + if (!response.hasOwnProperty("body")) { + response.body = Object(); + } + + response.body.allThreadsContinued = false; + this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); @@ -726,7 +735,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { this.miDebugger.step().then(done => { this.sendResponse(response); }, msg => { @@ -734,7 +743,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { this.miDebugger.stepOut().then(done => { this.sendResponse(response); }, msg => { From 96a190963df2d10c40b2677006b866dc05b7a0b4 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sat, 22 Nov 2025 12:02:51 +0100 Subject: [PATCH 03/26] POC for multiple debug sessions --- src/backend/mi2/mi2inferior.ts | 101 +++++++++++++++++++++++++++++ src/gdb.ts | 2 +- src/mibase.ts | 90 ++++++++++++++++++++------ src/miinferior.ts | 115 +++++++++++++++++++++++++++++++++ 4 files changed, 287 insertions(+), 21 deletions(-) create mode 100644 src/backend/mi2/mi2inferior.ts create mode 100644 src/miinferior.ts diff --git a/src/backend/mi2/mi2inferior.ts b/src/backend/mi2/mi2inferior.ts new file mode 100644 index 0000000..ac37e90 --- /dev/null +++ b/src/backend/mi2/mi2inferior.ts @@ -0,0 +1,101 @@ +import { Breakpoint, IBackend, SSHArguments, Stack, Thread, Variable } from "../backend"; +import { MI2 } from "./mi2"; + +export class MI2Inferior implements IBackend { + load(cwd: string, target: string, procArgs: string, separateConsole: string, autorun: string[]): Thenable { + return this.superior.load(cwd, target, procArgs, separateConsole, autorun); + } + + ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean, autorun: string[]): Thenable { + return this.superior.ssh(args, cwd, target, separateConsole, procArgs, attach, autorun); + } + + attach(cwd: string, executable: string, target: string, autorun: string[]): Thenable { + return this.superior.attach(cwd, executable, target, autorun); + } + + connect(cwd: string, executable: string, target: string, autorun: string[]): Thenable { + return this.superior.attach(cwd, executable, target, autorun); + } + + start(runToStart: boolean): Thenable { + return this.superior.start(runToStart); + } + + stop(): void { + this.superior.stop(); + } + + detach(): void { + this.superior.detach(); + } + + interrupt(threadId?: number): Thenable { + return this.superior.interrupt(threadId); + } + + continue(reverse?: boolean, threadId?: number): Thenable { + return this.superior.continue(reverse, threadId); + } + + next(): Thenable { + return this.superior.next(); + } + step(): Thenable { + return this.superior.step(); + } + + stepOut(): Thenable { + return this.superior.stepOut(); + } + + loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> { + return this.superior.loadBreakPoints(breakpoints); + } + + addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> { + return this.superior.addBreakPoint(breakpoint); + } + + removeBreakPoint(breakpoint: Breakpoint): Thenable { + return this.superior.removeBreakPoint(breakpoint); + } + + clearBreakPoints(source?: string): Thenable { + return this.superior.clearBreakPoints(source); + } + + getThreads(): Thenable { + return this.superior.getThreads(); + } + + getStack(startFrame: number, maxLevels: number, thread: number): Thenable { + return this.superior.getStack(startFrame, maxLevels, thread); + } + + getStackVariables(thread: number, frame: number): Thenable { + return this.superior.getStackVariables(thread, frame); + } + + evalExpression(name: string, thread: number, frame: number): Thenable { + return this.superior.evalExpression(name, thread, frame); + } + + isReady(): boolean { + return this.superior.isReady(); + } + + changeVariable(name: string, rawValue: string): Thenable { + return this.superior.changeVariable(name, rawValue); + } + + examineMemory(from: number, to: number): Thenable { + return this.superior.examineMemory(from, to); + } + + private superior: MI2; + + contructor(superior: MI2) { + this.superior = superior + } +}; \ No newline at end of file diff --git a/src/gdb.ts b/src/gdb.ts index d7732b9..b16b51d 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -45,7 +45,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum registerLimit: string; } -class GDBDebugSession extends MI2DebugSession { +export class GDBDebugSession extends MI2DebugSession { protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; diff --git a/src/mibase.ts b/src/mibase.ts index f79ff8f..30be947 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,4 +1,5 @@ import * as DebugAdapter from 'vscode-debugadapter'; +import * as Net from 'net'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; @@ -11,6 +12,7 @@ import * as net from "net"; import * as os from "os"; import * as fs from "fs"; import { SourceFileMap } from "./source_file_map"; +import { MI2InferiorSession } from "./miinferior" class ExtendedVariable { constructor(public name: string, public options: { "arg": any }) { @@ -41,11 +43,12 @@ export class MI2DebugSession extends DebugSession { protected sourceFileMap: SourceFileMap; protected started: boolean; protected crashed: boolean; - protected miDebugger: MI2; + public miDebugger: MI2; protected commandServer: net.Server; protected serverPath: string; protected threadGroupPids = new Map(); protected threadToPid = new Map(); + protected inferiorServers = new Array(); public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { super(debuggerLinesStartAt1, isServer); @@ -185,8 +188,55 @@ export class MI2DebugSession extends DebugSession { this.sendEvent(new ThreadEvent("exited", threadId)); } + private openInferiorDebugServer(superiorServer: MI2DebugSession) { + function randomIntFromInterval(min: number, max: number) { // min and max included + return Math.floor(Math.random() * (max - min + 1) + min); + } + + const port = 1337 + randomIntFromInterval(1, 1000); + + console.error(`waiting for debug protocol on port ${port}`); + + const server = Net.createServer((socket) => { + console.error('>> accepted connection from client'); + socket.on('end', () => { + console.error('>> client connection closed\n'); + }); + const session = new MI2InferiorSession(superiorServer); + session.setRunAsServer(true); + session.start(socket, socket); + }).listen(port); + + this.inferiorServers.push(server); + + return server; + } + protected threadGroupStartedEvent(info: MINode) { + let pid = parseInt(info.record("pid"), 10); + + this.miDebugger.log("stdout", "threadGroupStartedEvent") + this.miDebugger.log("stdout", pid.toString()) + this.threadGroupPids.set(info.record("id"), info.record("pid")); + + // If there are more than 1 threadgroups active, start a new debugger session in VSCode + // This makes the UI all fancy with subprocesses and threads etc. + if (this.threadGroupPids.size > 1) { + // Open a new port for the new DebugSession to attach to + const server = this.openInferiorDebugServer(this); + const serverAddress = (server.address() as Net.AddressInfo).port; + + this.startDebuggingRequest({ + request: "attach", + configuration: { + type: "gdb-inferior", + target: info.record("pid"), + cwd: "${workspaceRoot}", + debugServer: serverAddress + } + }, 1000, () => {}) + } } protected threadGroupExitedEvent(info: MINode) { @@ -212,7 +262,7 @@ export class MI2DebugSession extends DebugSession { this.quitEvent(); } - protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + public override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { if (this.attached) this.miDebugger.detach(); else @@ -222,7 +272,7 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); } - protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + public override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { try { if (this.useVarObjects) { let name = args.name; @@ -249,7 +299,7 @@ export class MI2DebugSession extends DebugSession { } } - protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { + public override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { const all: Thenable<[boolean, Breakpoint]>[] = []; args.breakpoints.forEach(brk => { all.push(this.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition })); @@ -269,7 +319,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + public override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { let path = args.source.path; if (this.isSSH) { // convert local path to ssh path @@ -299,7 +349,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { + public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { if (!this.miDebugger) { this.sendResponse(response); return; @@ -336,7 +386,7 @@ export class MI2DebugSession extends DebugSession { return [frameId & 0xffff, frameId >> 16]; } - protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { this.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { const ret: StackFrame[] = []; stack.forEach(element => { @@ -370,7 +420,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + public override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { const promises: Thenable[] = []; let entryPoint: string | undefined = undefined; let runToStart: boolean = false; @@ -439,7 +489,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { const scopes = new Array(); const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); @@ -466,7 +516,7 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); } - protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; const id: VariableScope | string | VariableObject | ExtendedVariable = this.variableHandles.get(args.variablesReference); @@ -692,7 +742,7 @@ export class MI2DebugSession extends DebugSession { } } - protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { + public override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { this.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -700,7 +750,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + public override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { this.miDebugger.continue(true, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); @@ -713,7 +763,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + public override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { this.miDebugger.continue(false, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); @@ -727,7 +777,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + public override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { this.miDebugger.step(true).then(done => { this.sendResponse(response); }, msg => { @@ -735,7 +785,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { + public override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { this.miDebugger.step().then(done => { this.sendResponse(response); }, msg => { @@ -743,7 +793,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { + public override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { this.miDebugger.stepOut().then(done => { this.sendResponse(response); }, msg => { @@ -751,7 +801,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + public override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { this.miDebugger.next().then(done => { this.sendResponse(response); }, msg => { @@ -759,7 +809,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); if (args.context === "watch" || args.context === "hover") { this.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { @@ -795,7 +845,7 @@ export class MI2DebugSession extends DebugSession { } } - protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { const path: string = this.isSSH ? this.sourceFileMap.toRemotePath(args.source.path) : args.source.path; this.miDebugger.goto(path, args.line).then(done => { response.body = { @@ -812,7 +862,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { this.sendResponse(response); } diff --git a/src/miinferior.ts b/src/miinferior.ts new file mode 100644 index 0000000..0f3bd35 --- /dev/null +++ b/src/miinferior.ts @@ -0,0 +1,115 @@ +import { MI2DebugSession } from './mibase'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; + +export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { + parentSession: MI2DebugSession | DebugSession +} + +export class MI2InferiorSession extends DebugSession { + superiorSession: MI2DebugSession; + + constructor(superiorSession: MI2DebugSession) { + super(false, true); + + this.superiorSession = superiorSession; + } + + protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + // Same capabilities as GDBDebugSession + response.body.supportsGotoTargetsRequest = true; + response.body.supportsHitConditionalBreakpoints = true; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsConditionalBreakpoints = true; + response.body.supportsFunctionBreakpoints = true; + response.body.supportsEvaluateForHovers = true; + response.body.supportsSetVariable = true; + response.body.supportsStepBack = true; + response.body.supportsLogPoints = true; + this.sendResponse(response); + } + + protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + // Attached to server + const pargs = JSON.stringify(args); + + this.superiorSession.miDebugger.log("stdout", "Received attach request!"); + this.superiorSession.miDebugger.log("stdout", pargs); + } + + protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + this.superiorSession.disconnectRequest(response, args); + } + + protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + this.superiorSession.setVariableRequest(response, args); + } + + protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { + this.superiorSession.setFunctionBreakPointsRequest(response, args); + } + + protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + this.superiorSession.setBreakPointsRequest(response, args); + } + + protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { + this.superiorSession.threadsRequest(response); + } + + protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + this.superiorSession.stackTraceRequest(response, args); + } + + protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + this.superiorSession.configurationDoneRequest(response, args); + } + + protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + this.superiorSession.scopesRequest(response, args); + } + + protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + this.superiorSession.variablesRequest(response, args); + } + + protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { + this.superiorSession.pauseRequest(response, args); + } + + protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + this.superiorSession.reverseContinueRequest(response, args); + } + + protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + this.superiorSession.continueRequest(response, args); + } + + protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + this.superiorSession.stepBackRequest(response, args); + } + + protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { + this.superiorSession.stepInRequest(response, args); + } + + protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { + this.superiorSession.stepOutRequest(response, args); + } + + protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.superiorSession.nextRequest(response, args); + } + + protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + this.superiorSession.evaluateRequest(response, args); + } + + protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + this.superiorSession.gotoTargetsRequest(response, args); + } + + protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + this.superiorSession.gotoRequest(response, args); + } +} From 8fd02d8aada161cc14fffb20d06a1a853b43f6ed Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sat, 22 Nov 2025 13:38:32 +0100 Subject: [PATCH 04/26] Fixed passing of events. Now it properly shows two stack traces in subprocesses Still some polishing is needed since it is not very stable --- src/mibase.ts | 117 ++++++++++++++++++++++++++++++++++++++++------ src/miinferior.ts | 68 +++++++++++++++++++++++++-- 2 files changed, 166 insertions(+), 19 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 30be947..7198e71 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -46,8 +46,10 @@ export class MI2DebugSession extends DebugSession { public miDebugger: MI2; protected commandServer: net.Server; protected serverPath: string; + protected sessionPid: string | undefined = undefined; protected threadGroupPids = new Map(); - protected threadToPid = new Map(); + public threadToPid = new Map(); + protected mi2Inferiors = new Array(); protected inferiorServers = new Array(); public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { @@ -144,30 +146,72 @@ export class MI2DebugSession extends DebugSession { } protected handleBreakpoint(info: MINode) { + let threadId = info.record("thread-id"); + + this.miDebugger.log("stdout", `Got theadid ${threadId}`); + + let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.sendEvent(event); + + this.miDebugger.log("stdout", `Handling breakpoint for threadPid == this.sessionPid (${threadPid} == ${this.sessionPid})`) + + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } protected handleBreak(info?: MINode) { + let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") === "all" : true; - this.sendEvent(event); + + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } protected handlePause(info: MINode) { + let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + const event = new StoppedEvent("user request", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.sendEvent(event); + + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } protected stopEvent(info: MINode) { if (!this.started) this.crashed = true; if (!this.quit) { + let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + const event = new StoppedEvent("exception", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.sendEvent(event); + + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } } @@ -177,15 +221,28 @@ export class MI2DebugSession extends DebugSession { let threadPid = this.threadGroupPids.get(info.record("group-id")); this.threadToPid.set(threadId, threadPid); - this.sendEvent(new ThreadEvent("started", threadId)); + if (threadPid == this.sessionPid) { + this.sendEvent(new ThreadEvent("started", threadId)); + } else { + this.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("started", threadId)); + }); + } } protected threadExitedEvent(info: MINode) { let threadId = parseInt(info.record("id"), 10); + let threadPid = this.threadGroupPids.get(info.record("group-id")); this.threadToPid.delete(info.record("group-id")); - this.sendEvent(new ThreadEvent("exited", threadId)); + if (threadPid == this.sessionPid) { + this.sendEvent(new ThreadEvent("exited", threadId)); + } else { + this.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("exited", threadId)); + }); + } } private openInferiorDebugServer(superiorServer: MI2DebugSession) { @@ -205,6 +262,8 @@ export class MI2DebugSession extends DebugSession { const session = new MI2InferiorSession(superiorServer); session.setRunAsServer(true); session.start(socket, socket); + + this.mi2Inferiors.push(session); }).listen(port); this.inferiorServers.push(server); @@ -213,7 +272,12 @@ export class MI2DebugSession extends DebugSession { } protected threadGroupStartedEvent(info: MINode) { - let pid = parseInt(info.record("pid"), 10); + let pid = info.record("pid"); + + if (typeof this.sessionPid === "undefined") { + this.miDebugger.log("stdout", `Updated this.sessionPid to ${pid}`) + this.sessionPid = pid; + } this.miDebugger.log("stdout", "threadGroupStartedEvent") this.miDebugger.log("stdout", pid.toString()) @@ -240,6 +304,17 @@ export class MI2DebugSession extends DebugSession { } protected threadGroupExitedEvent(info: MINode) { + let pid = this.threadGroupPids.get(info.record("id")); + + if (pid == this.sessionPid) { + this.miDebugger.log("stdout", "this.sesionPid = undefind"); + // Session has no thread group anymore. Next started thread group will be debugged by this session + this.sessionPid = undefined; + } + + this.miDebugger.log("stdout", "threadGroupExitedEvent"); + this.miDebugger.log("stdout", pid); + this.threadGroupPids.delete(info.record("id")); } @@ -350,6 +425,8 @@ export class MI2DebugSession extends DebugSession { } public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { + this.miDebugger.log("stdout", `Received superior thread request for ${this.sessionPid}`); + if (!this.miDebugger) { this.sendResponse(response); return; @@ -361,10 +438,11 @@ export class MI2DebugSession extends DebugSession { for (const thread of threads) { const threadName = thread.name || thread.targetId || ""; - if (this.threadGroupPids.size > 1) { - let pid = this.threadToPid.get(thread.id); - response.body.threads.push(new Thread(thread.id, `(${pid}) ${thread.id}:${threadName}`)); - } else { + let pid = this.threadToPid.get(thread.id); + + this.miDebugger.log("stdout", `pid == this.sessionPid (${pid} == ${this.sessionPid})`) + + if (pid == this.sessionPid) { response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); } } @@ -386,7 +464,9 @@ export class MI2DebugSession extends DebugSession { return [frameId & 0xffff, frameId >> 16]; } - public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + public inferiorStackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments, + cb_good: (res: DebugProtocol.StackTraceResponse) => any, + cb_bad: (res: DebugProtocol.StackTraceResponse, codeOrMessage: number, err: string) => any) { this.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { const ret: StackFrame[] = []; stack.forEach(element => { @@ -414,12 +494,19 @@ export class MI2DebugSession extends DebugSession { response.body = { stackFrames: ret }; - this.sendResponse(response); + cb_good(response); }, err => { - this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`); + cb_bad(response, 12, `Failed to get Stack Trace: ${err.toString()}`); }); } + public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + this.inferiorStackTraceRequest(response, args, + (r: DebugProtocol.StackTraceResponse) => this.sendResponse(r), + (r: DebugProtocol.StackTraceResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) + ) + } + public override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { const promises: Thenable[] = []; let entryPoint: string | undefined = undefined; diff --git a/src/miinferior.ts b/src/miinferior.ts index 0f3bd35..af0b2fd 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -1,13 +1,17 @@ import { MI2DebugSession } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; +import { MIError } from './backend/backend'; +import { setFlagsFromString } from 'v8'; export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { - parentSession: MI2DebugSession | DebugSession + parentSession: MI2DebugSession | DebugSession, + target: string } export class MI2InferiorSession extends DebugSession { superiorSession: MI2DebugSession; + sessionPid: string | undefined; constructor(superiorSession: MI2DebugSession) { super(false, true); @@ -16,6 +20,7 @@ export class MI2InferiorSession extends DebugSession { } protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + this.superiorSession.miDebugger.log("stdout", `intiialize request via inferior ${this.sessionPid}`); // Same capabilities as GDBDebugSession response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; @@ -30,86 +35,141 @@ export class MI2InferiorSession extends DebugSession { } protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + this.superiorSession.miDebugger.log("stdout", `attach request via inferior ${this.sessionPid}`); // Attached to server const pargs = JSON.stringify(args); + this.sessionPid = args.target; + this.superiorSession.miDebugger.log("stdout", "Received attach request!"); - this.superiorSession.miDebugger.log("stdout", pargs); + this.superiorSession.miDebugger.log("stdout", this.sessionPid); } protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + this.superiorSession.miDebugger.log("stdout", `disconnect request via inferior ${this.sessionPid}`); this.superiorSession.disconnectRequest(response, args); } protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + this.superiorSession.miDebugger.log("stdout", `set variables request via inferior ${this.sessionPid}`); this.superiorSession.setVariableRequest(response, args); } protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { + this.superiorSession.miDebugger.log("stdout", `set function breakpoints request via inferior ${this.sessionPid}`); this.superiorSession.setFunctionBreakPointsRequest(response, args); } protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + this.superiorSession.miDebugger.log("stdout", `set breakpoints request via inferior ${this.sessionPid}`); this.superiorSession.setBreakPointsRequest(response, args); } protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.superiorSession.threadsRequest(response); + this.superiorSession.miDebugger.log("stdout", `Received inferior thread request for ${this.sessionPid}`); + + if (!this.superiorSession.miDebugger) { + this.sendResponse(response); + return; + } + this.superiorSession.miDebugger.getThreads().then(threads => { + response.body = { + threads: [] + }; + for (const thread of threads) { + const threadName = thread.name || thread.targetId || ""; + + + let pid = this.superiorSession.threadToPid.get(thread.id); + + + if (pid == this.sessionPid) { + this.superiorSession.miDebugger.log("stdout", `Found thead via inferior ${threadName}`) + this.superiorSession.miDebugger.log("stdout", `pid == this.sessionPid (${pid} == ${this.sessionPid})`) + response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); + } + } + this.sendResponse(response); + }).catch((error: MIError) => { + if (error.message === 'Selected thread is running.') { + this.sendResponse(response); + return; + } + this.sendErrorResponse(response, 17, `Could not get threads: ${error}`); + }); } protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - this.superiorSession.stackTraceRequest(response, args); + this.superiorSession.miDebugger.log("stdout", `Stack trace request via inferior ${this.sessionPid}`); + this.superiorSession.inferiorStackTraceRequest(response, args, + (r: DebugProtocol.StackTraceResponse) => this.sendResponse(r), + (r: DebugProtocol.StackTraceResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) + ) + this.superiorSession.miDebugger.log("stdout", `Stack trace request via inferior ${this.sessionPid} completed`); } protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + this.superiorSession.miDebugger.log("stdout", `configuration done request via inferior ${this.sessionPid}`); this.superiorSession.configurationDoneRequest(response, args); } protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + this.superiorSession.miDebugger.log("stdout", `scopes request via inferior ${this.sessionPid}`); this.superiorSession.scopesRequest(response, args); } protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + this.superiorSession.miDebugger.log("stdout", `variables request via inferior ${this.sessionPid}`); this.superiorSession.variablesRequest(response, args); } protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { + this.superiorSession.miDebugger.log("stdout", `pause request via inferior ${this.sessionPid}`); this.superiorSession.pauseRequest(response, args); } protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + this.superiorSession.miDebugger.log("stdout", `reverse continue request via inferior ${this.sessionPid}`); this.superiorSession.reverseContinueRequest(response, args); } protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + this.superiorSession.miDebugger.log("stdout", `continue request via inferior ${this.sessionPid}`); this.superiorSession.continueRequest(response, args); } protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + this.superiorSession.miDebugger.log("stdout", `step back request via inferior ${this.sessionPid}`); this.superiorSession.stepBackRequest(response, args); } protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { + this.superiorSession.miDebugger.log("stdout", `step in request via inferior ${this.sessionPid}`); this.superiorSession.stepInRequest(response, args); } protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { + this.superiorSession.miDebugger.log("stdout", `step out request via inferior ${this.sessionPid}`); this.superiorSession.stepOutRequest(response, args); } protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.superiorSession.miDebugger.log("stdout", `next request via inferior ${this.sessionPid}`); this.superiorSession.nextRequest(response, args); } protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + this.superiorSession.miDebugger.log("stdout", `evaluate request via inferior ${this.sessionPid}`); this.superiorSession.evaluateRequest(response, args); } protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + this.superiorSession.miDebugger.log("stdout", `goto targets request via inferior ${this.sessionPid}`); this.superiorSession.gotoTargetsRequest(response, args); } protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + this.superiorSession.miDebugger.log("stdout", `goto request via inferior ${this.sessionPid}`); this.superiorSession.gotoRequest(response, args); } } From d81d63d206921ea6fd9d55499fb6b07404e3f3bb Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sat, 22 Nov 2025 15:01:14 +0100 Subject: [PATCH 05/26] Reuse threadRequest code between superior and inferior --- src/mibase.ts | 30 +++++++++++++++++++++--------- src/miinferior.ts | 35 +++++------------------------------ 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 7198e71..01e5c16 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -424,11 +424,13 @@ export class MI2DebugSession extends DebugSession { }); } - public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.miDebugger.log("stdout", `Received superior thread request for ${this.sessionPid}`); - + public inferiorThreadsRequest(response: DebugProtocol.ThreadsResponse, + sessionPid: string, + cb_good: (res: DebugProtocol.ThreadsResponse) => any, + cb_bad: (res: DebugProtocol.ThreadsResponse, codeOrMessage: number, err: string) => any + ): void { if (!this.miDebugger) { - this.sendResponse(response); + cb_good(response); return; } this.miDebugger.getThreads().then(threads => { @@ -440,22 +442,32 @@ export class MI2DebugSession extends DebugSession { let pid = this.threadToPid.get(thread.id); - this.miDebugger.log("stdout", `pid == this.sessionPid (${pid} == ${this.sessionPid})`) + this.miDebugger.log("stdout", `pid == this.sessionPid (${pid} == ${sessionPid})`) - if (pid == this.sessionPid) { + if (pid == sessionPid) { response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); } } - this.sendResponse(response); + cb_good(response); }).catch((error: MIError) => { if (error.message === 'Selected thread is running.') { - this.sendResponse(response); + cb_good(response); return; } - this.sendErrorResponse(response, 17, `Could not get threads: ${error}`); + cb_bad(response, 17, `Could not get threads: ${error}`); }); } + public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { + this.miDebugger.log("stdout", `Received superior thread request for ${this.sessionPid}`); + this.inferiorThreadsRequest( + response, + this.sessionPid, + (r: DebugProtocol.ThreadsResponse) => this.sendResponse(r), + (r: DebugProtocol.ThreadsResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) + ) + } + // Supports 65535 threads. protected threadAndLevelToFrameId(threadId: number, level: number) { return level << 16 | threadId; diff --git a/src/miinferior.ts b/src/miinferior.ts index af0b2fd..c97c83d 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -67,36 +67,11 @@ export class MI2InferiorSession extends DebugSession { protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { this.superiorSession.miDebugger.log("stdout", `Received inferior thread request for ${this.sessionPid}`); - - if (!this.superiorSession.miDebugger) { - this.sendResponse(response); - return; - } - this.superiorSession.miDebugger.getThreads().then(threads => { - response.body = { - threads: [] - }; - for (const thread of threads) { - const threadName = thread.name || thread.targetId || ""; - - - let pid = this.superiorSession.threadToPid.get(thread.id); - - - if (pid == this.sessionPid) { - this.superiorSession.miDebugger.log("stdout", `Found thead via inferior ${threadName}`) - this.superiorSession.miDebugger.log("stdout", `pid == this.sessionPid (${pid} == ${this.sessionPid})`) - response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); - } - } - this.sendResponse(response); - }).catch((error: MIError) => { - if (error.message === 'Selected thread is running.') { - this.sendResponse(response); - return; - } - this.sendErrorResponse(response, 17, `Could not get threads: ${error}`); - }); + this.superiorSession.inferiorThreadsRequest(response, + this.sessionPid, + (r: DebugProtocol.ThreadsResponse) => this.sendResponse(r), + (r: DebugProtocol.ThreadsResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) + ) } protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { From 99d76f13a919e11f3ff3a55bdbc573a043275f5d Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 14:08:23 +0100 Subject: [PATCH 06/26] Stability improvements --- src/backend/mi2/mi2.ts | 12 ++--- src/mibase.ts | 118 +++++++++++++++++++++++++++-------------- src/miinferior.ts | 71 ++++++++++++++++++++----- 3 files changed, 142 insertions(+), 59 deletions(-) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 2390973..d19f29a 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -580,31 +580,31 @@ export class MI2 extends EventEmitter implements IBackend { }); } - next(reverse: boolean = false): Thenable { + next(reverse: boolean = false, threadId?: number): Thenable { if (trace) this.log("stderr", "next"); return new Promise((resolve, reject) => { - this.sendCommand("exec-next" + (reverse ? " --reverse" : "")).then((info) => { + this.sendCommand("exec-next" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); } - step(reverse: boolean = false): Thenable { + step(reverse: boolean = false, threadId?: number): Thenable { if (trace) this.log("stderr", "step"); return new Promise((resolve, reject) => { - this.sendCommand("exec-step" + (reverse ? " --reverse" : "")).then((info) => { + this.sendCommand("exec-step" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); } - stepOut(reverse: boolean = false): Thenable { + stepOut(reverse: boolean = false, threadId?: number): Thenable { if (trace) this.log("stderr", "stepOut"); return new Promise((resolve, reject) => { - this.sendCommand("exec-finish" + (reverse ? " --reverse" : "")).then((info) => { + this.sendCommand("exec-finish" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); diff --git a/src/mibase.ts b/src/mibase.ts index 01e5c16..87fd979 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -296,6 +296,7 @@ export class MI2DebugSession extends DebugSession { configuration: { type: "gdb-inferior", target: info.record("pid"), + name: "Child session", cwd: "${workspaceRoot}", debugServer: serverAddress } @@ -588,7 +589,9 @@ export class MI2DebugSession extends DebugSession { }); } - public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + public inferiorScopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments, + cb_good: (r: DebugProtocol.Response) => void + ) { const scopes = new Array(); const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); @@ -612,10 +615,17 @@ export class MI2DebugSession extends DebugSession { response.body = { scopes: scopes }; - this.sendResponse(response); + cb_good(response); } - public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + this.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) + } + + public async inferiorVariablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, + cb_good: (r: DebugProtocol.Response) => void, + cb_bad: (r: DebugProtocol.Response, n: number, m: string) => void + ) { const variables: DebugProtocol.Variable[] = []; const id: VariableScope | string | VariableObject | ExtendedVariable = this.variableHandles.get(args.variablesReference); @@ -711,9 +721,9 @@ export class MI2DebugSession extends DebugSession { response.body = { variables: variables }; - this.sendResponse(response); + cb_good(response); } catch (err) { - this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + cb_bad(response, 1, `Could not expand variable: ${err}`); } } else if (typeof id === "string") { // Variable members @@ -744,13 +754,13 @@ export class MI2DebugSession extends DebugSession { response.body = { variables: expanded }; - this.sendResponse(response); + cb_good(response); } } catch (e) { - this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`); + cb_bad(response, 2, `Could not expand variable: ${e}`); } } catch (err) { - this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + cb_bad(response, 1, `Could not expand variable: ${err}`); } } else if (typeof id === "object") { if (id instanceof VariableObject) { @@ -767,9 +777,9 @@ export class MI2DebugSession extends DebugSession { response.body = { variables: vars }; - this.sendResponse(response); + cb_good(response); } catch (err) { - this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + cb_bad(response, 1, `Could not expand variable: ${err}`); } } else if (id instanceof ExtendedVariable) { const varReq = id; @@ -781,7 +791,7 @@ export class MI2DebugSession extends DebugSession { response.body = { variables: strArr }; - this.sendResponse(response); + cb_good(response); }; const addOne = async () => { // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. @@ -821,26 +831,33 @@ export class MI2DebugSession extends DebugSession { } } } catch (e) { - this.sendErrorResponse(response, 14, `Could not expand variable: ${e}`); + cb_bad(response, 14, `Could not expand variable: ${e}`); } }; addOne(); } else - this.sendErrorResponse(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); + cb_bad(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); } else { response.body = { variables: id }; - this.sendResponse(response); + cb_good(response); } } else { response.body = { variables: variables }; - this.sendResponse(response); + cb_good(response); } } + public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + return this.inferiorVariablesRequest(response, args, + (r: DebugProtocol.VariablesResponse) => this.sendResponse(r), + (r: DebugProtocol.VariablesResponse, n: number, m: string) => this.sendErrorResponse(r, n, m) + ) + } + public override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { this.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); @@ -869,7 +886,6 @@ export class MI2DebugSession extends DebugSession { } response.body.allThreadsContinued = false; - this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); @@ -908,7 +924,10 @@ export class MI2DebugSession extends DebugSession { }); } - public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + public inferiorEvaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments, + cb_good: (r: DebugProtocol.Response) => void, + cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void + ): void { const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); if (args.context === "watch" || args.context === "hover") { this.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { @@ -916,13 +935,13 @@ export class MI2DebugSession extends DebugSession { variablesReference: 0, result: res.result("value") }; - this.sendResponse(response); + cb_good(response); }, msg => { if (args.context === "hover") { // suppress error for hover as the user may just play with the mouse - this.sendResponse(response); + cb_good(response); } else { - this.sendErrorResponse(response, 7, msg.toString()); + cb_bad(response, 7, msg.toString()); } }); } else { @@ -937,35 +956,45 @@ export class MI2DebugSession extends DebugSession { result: JSON.stringify(output), variablesReference: 0 }; - this.sendResponse(response); + cb_good(response); }, msg => { - this.sendErrorResponse(response, 8, msg.toString()); + cb_bad(response, 8, msg.toString()); }); } } - public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { - const path: string = this.isSSH ? this.sourceFileMap.toRemotePath(args.source.path) : args.source.path; - this.miDebugger.goto(path, args.line).then(done => { - response.body = { - targets: [{ - id: 1, - label: args.source.name, - column: args.column, - line: args.line - }] - }; - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 16, `Could not jump: ${msg}`); - }); + public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + this.inferiorEvaluateRequest(response, args, + (r: DebugProtocol.Response) => this.sendResponse(r), + (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) + ) } + + public inferiorGotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments, + cb_good: (r: DebugProtocol.Response) => void, + cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void + ): void { + const path: string = this.isSSH ? this.sourceFileMap.toRemotePath(args.source.path) : args.source.path; + this.miDebugger.goto(path, args.line).then(done => { + response.body = { + targets: [{ + id: 1, + label: args.source.name, + column: args.column, + line: args.line + }] + }; + cb_good(response); + }, msg => { + cb_bad(response, 16, `Could not jump: ${msg}`); + }); + } - public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { - this.sendResponse(response); - } + public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + this.sendResponse(response); + } - protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void { + protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void { if (configMap === undefined) { this.sourceFileMap = new SourceFileMap({ [fallbackGDB]: fallbackIDE }); } else { @@ -973,6 +1002,13 @@ export class MI2DebugSession extends DebugSession { } } + public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + this.inferiorGotoTargetsRequest( + response, args, + (r: DebugProtocol.Response) => this.sendResponse(r), + (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) + ); + } } function prettyStringArray(strings: any) { diff --git a/src/miinferior.ts b/src/miinferior.ts index c97c83d..391160f 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -90,61 +90,108 @@ export class MI2InferiorSession extends DebugSession { protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { this.superiorSession.miDebugger.log("stdout", `scopes request via inferior ${this.sessionPid}`); - this.superiorSession.scopesRequest(response, args); + this.superiorSession.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) } protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { this.superiorSession.miDebugger.log("stdout", `variables request via inferior ${this.sessionPid}`); - this.superiorSession.variablesRequest(response, args); + this.superiorSession.inferiorVariablesRequest(response, args, + (r: DebugProtocol.Response) => this.sendResponse(r), + (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) + ); } protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { this.superiorSession.miDebugger.log("stdout", `pause request via inferior ${this.sessionPid}`); - this.superiorSession.pauseRequest(response, args); + this.superiorSession.miDebugger.interrupt(args.threadId).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 3, `Could not pause: ${msg}`); + }); } protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { this.superiorSession.miDebugger.log("stdout", `reverse continue request via inferior ${this.sessionPid}`); - this.superiorSession.reverseContinueRequest(response, args); + this.superiorSession.miDebugger.continue(true, args.threadId).then(done => { + if (!response.hasOwnProperty("body")) { + response.body = Object(); + } + + response.body.allThreadsContinued = false; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); + }); } protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { this.superiorSession.miDebugger.log("stdout", `continue request via inferior ${this.sessionPid}`); - this.superiorSession.continueRequest(response, args); + this.superiorSession.miDebugger.continue(false, args.threadId).then(done => { + if (!response.hasOwnProperty("body")) { + response.body = Object(); + } + + response.body.allThreadsContinued = false; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); + }); } protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { this.superiorSession.miDebugger.log("stdout", `step back request via inferior ${this.sessionPid}`); - this.superiorSession.stepBackRequest(response, args); + this.superiorSession.miDebugger.step(true, args.threadId).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`); + }); } protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { this.superiorSession.miDebugger.log("stdout", `step in request via inferior ${this.sessionPid}`); - this.superiorSession.stepInRequest(response, args); + this.superiorSession.miDebugger.step(false, args.threadId).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 4, `Could not step in: ${msg}`); + }); } protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { this.superiorSession.miDebugger.log("stdout", `step out request via inferior ${this.sessionPid}`); - this.superiorSession.stepOutRequest(response, args); + this.superiorSession.miDebugger.stepOut(false, args.threadId).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 5, `Could not step out: ${msg}`); + }); } protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { this.superiorSession.miDebugger.log("stdout", `next request via inferior ${this.sessionPid}`); - this.superiorSession.nextRequest(response, args); + this.superiorSession.miDebugger.next(false, args.threadId).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 6, `Could not step over: ${msg}`); + }); } protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { this.superiorSession.miDebugger.log("stdout", `evaluate request via inferior ${this.sessionPid}`); - this.superiorSession.evaluateRequest(response, args); + this.superiorSession.inferiorEvaluateRequest(response, args, + (r: DebugProtocol.Response) => this.sendResponse(r), + (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) + ); } protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { this.superiorSession.miDebugger.log("stdout", `goto targets request via inferior ${this.sessionPid}`); - this.superiorSession.gotoTargetsRequest(response, args); + this.superiorSession.inferiorGotoTargetsRequest(response, args, + (r: DebugProtocol.Response) => this.sendResponse(r), + (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) + ); } protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { this.superiorSession.miDebugger.log("stdout", `goto request via inferior ${this.sessionPid}`); - this.superiorSession.gotoRequest(response, args); + this.sendResponse(response); } } From 4df215d049539dbc535e8a4769b516a57384a6e6 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 17:04:56 +0100 Subject: [PATCH 07/26] Remove dependency on vscode-debugadapter-node changes --- src/mibase.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 87fd979..69979ea 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -291,7 +291,8 @@ export class MI2DebugSession extends DebugSession { const server = this.openInferiorDebugServer(this); const serverAddress = (server.address() as Net.AddressInfo).port; - this.startDebuggingRequest({ + // Necessary until vscode-debugadapter-node supports `startDebuggingRequest` + this.sendRequest('startDebugging', { request: "attach", configuration: { type: "gdb-inferior", @@ -300,7 +301,18 @@ export class MI2DebugSession extends DebugSession { cwd: "${workspaceRoot}", debugServer: serverAddress } - }, 1000, () => {}) + }, 1000, () => {}); + + // this.startDebuggingRequest({ + // request: "attach", + // configuration: { + // type: "gdb-inferior", + // target: info.record("pid"), + // name: "Child session", + // cwd: "${workspaceRoot}", + // debugServer: serverAddress + // } + // }, 1000, () => {}) } } From 1807c8d7968c45ab3cdb2b8751d1ae13105cf9b4 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 17:25:15 +0100 Subject: [PATCH 08/26] Fixed improved thread-specific commands --- src/backend/mi2/mi2.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index d19f29a..931a3cf 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -562,9 +562,9 @@ export class MI2 extends EventEmitter implements IBackend { interrupt(threadId?: number): Thenable { if (trace) - this.log("stderr", "interrupt" + (threadId ? " --thread-group i" + threadId : "")); + this.log("stderr", "interrupt" + (threadId ? " --thread " + threadId : "")); return new Promise((resolve, reject) => { - this.sendCommand("exec-interrupt" + (threadId ? " --thread-group i" + threadId : "")).then((info) => { + this.sendCommand("exec-interrupt" + (threadId ? " --thread " + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "done"); }, reject); }); @@ -572,9 +572,9 @@ export class MI2 extends EventEmitter implements IBackend { continue(reverse: boolean = false, threadId?: number): Thenable { if (trace) - this.log("stderr", "continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")); + this.log("stderr", "continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : "")); return new Promise((resolve, reject) => { - this.sendCommand("exec-continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { + this.sendCommand("exec-continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); @@ -584,7 +584,7 @@ export class MI2 extends EventEmitter implements IBackend { if (trace) this.log("stderr", "next"); return new Promise((resolve, reject) => { - this.sendCommand("exec-next" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { + this.sendCommand("exec-next" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); @@ -594,7 +594,7 @@ export class MI2 extends EventEmitter implements IBackend { if (trace) this.log("stderr", "step"); return new Promise((resolve, reject) => { - this.sendCommand("exec-step" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { + this.sendCommand("exec-step" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); @@ -604,7 +604,7 @@ export class MI2 extends EventEmitter implements IBackend { if (trace) this.log("stderr", "stepOut"); return new Promise((resolve, reject) => { - this.sendCommand("exec-finish" + (reverse ? " --reverse" : "") + (threadId ? " --thread-group i" + threadId : "")).then((info) => { + this.sendCommand("exec-finish" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : "")).then((info) => { resolve(info.resultRecords.resultClass === "running"); }, reject); }); From a67e92e0ea6cf19aa7b0eec1350eb4c9fd6fa913 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 19:20:30 +0100 Subject: [PATCH 09/26] More improvements --- src/mibase.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 69979ea..d97d48b 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -905,7 +905,7 @@ export class MI2DebugSession extends DebugSession { } public override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { - this.miDebugger.step(true).then(done => { + this.miDebugger.step(true, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`); @@ -913,7 +913,7 @@ export class MI2DebugSession extends DebugSession { } public override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { - this.miDebugger.step().then(done => { + this.miDebugger.step(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 4, `Could not step in: ${msg}`); @@ -921,7 +921,7 @@ export class MI2DebugSession extends DebugSession { } public override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { - this.miDebugger.stepOut().then(done => { + this.miDebugger.stepOut(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 5, `Could not step out: ${msg}`); @@ -929,7 +929,7 @@ export class MI2DebugSession extends DebugSession { } public override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { - this.miDebugger.next().then(done => { + this.miDebugger.next(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 6, `Could not step over: ${msg}`); From 4f08c34748864ee285e41588a7586e6adc6b92cb Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 19:22:58 +0100 Subject: [PATCH 10/26] Removed logging --- src/backend/mi2/mi2.ts | 1 - src/mibase.ts | 15 --------------- src/miinferior.ts | 25 ------------------------- 3 files changed, 41 deletions(-) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 931a3cf..7715dd7 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -472,7 +472,6 @@ export class MI2 extends EventEmitter implements IBackend { this.emit("exited-normally", parsed); break; case "exited": // exit with error code != 0 - this.log("stderr", "Inferior exited with code " + parsed.record("exit-code")); this.emit("exited-normally", parsed); break; // case "exited-signalled": // consider handling that explicit possible diff --git a/src/mibase.ts b/src/mibase.ts index d97d48b..7c5be42 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -148,15 +148,11 @@ export class MI2DebugSession extends DebugSession { protected handleBreakpoint(info: MINode) { let threadId = info.record("thread-id"); - this.miDebugger.log("stdout", `Got theadid ${threadId}`); - let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.miDebugger.log("stdout", `Handling breakpoint for threadPid == this.sessionPid (${threadPid} == ${this.sessionPid})`) - if (threadPid == this.sessionPid) { this.sendEvent(event); } else { @@ -275,13 +271,9 @@ export class MI2DebugSession extends DebugSession { let pid = info.record("pid"); if (typeof this.sessionPid === "undefined") { - this.miDebugger.log("stdout", `Updated this.sessionPid to ${pid}`) this.sessionPid = pid; } - this.miDebugger.log("stdout", "threadGroupStartedEvent") - this.miDebugger.log("stdout", pid.toString()) - this.threadGroupPids.set(info.record("id"), info.record("pid")); // If there are more than 1 threadgroups active, start a new debugger session in VSCode @@ -320,14 +312,10 @@ export class MI2DebugSession extends DebugSession { let pid = this.threadGroupPids.get(info.record("id")); if (pid == this.sessionPid) { - this.miDebugger.log("stdout", "this.sesionPid = undefind"); // Session has no thread group anymore. Next started thread group will be debugged by this session this.sessionPid = undefined; } - this.miDebugger.log("stdout", "threadGroupExitedEvent"); - this.miDebugger.log("stdout", pid); - this.threadGroupPids.delete(info.record("id")); } @@ -455,8 +443,6 @@ export class MI2DebugSession extends DebugSession { let pid = this.threadToPid.get(thread.id); - this.miDebugger.log("stdout", `pid == this.sessionPid (${pid} == ${sessionPid})`) - if (pid == sessionPid) { response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); } @@ -472,7 +458,6 @@ export class MI2DebugSession extends DebugSession { } public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.miDebugger.log("stdout", `Received superior thread request for ${this.sessionPid}`); this.inferiorThreadsRequest( response, this.sessionPid, diff --git a/src/miinferior.ts b/src/miinferior.ts index 391160f..30aeb19 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -20,7 +20,6 @@ export class MI2InferiorSession extends DebugSession { } protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { - this.superiorSession.miDebugger.log("stdout", `intiialize request via inferior ${this.sessionPid}`); // Same capabilities as GDBDebugSession response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; @@ -35,38 +34,29 @@ export class MI2InferiorSession extends DebugSession { } protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { - this.superiorSession.miDebugger.log("stdout", `attach request via inferior ${this.sessionPid}`); // Attached to server const pargs = JSON.stringify(args); this.sessionPid = args.target; - - this.superiorSession.miDebugger.log("stdout", "Received attach request!"); - this.superiorSession.miDebugger.log("stdout", this.sessionPid); } protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { - this.superiorSession.miDebugger.log("stdout", `disconnect request via inferior ${this.sessionPid}`); this.superiorSession.disconnectRequest(response, args); } protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { - this.superiorSession.miDebugger.log("stdout", `set variables request via inferior ${this.sessionPid}`); this.superiorSession.setVariableRequest(response, args); } protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { - this.superiorSession.miDebugger.log("stdout", `set function breakpoints request via inferior ${this.sessionPid}`); this.superiorSession.setFunctionBreakPointsRequest(response, args); } protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { - this.superiorSession.miDebugger.log("stdout", `set breakpoints request via inferior ${this.sessionPid}`); this.superiorSession.setBreakPointsRequest(response, args); } protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.superiorSession.miDebugger.log("stdout", `Received inferior thread request for ${this.sessionPid}`); this.superiorSession.inferiorThreadsRequest(response, this.sessionPid, (r: DebugProtocol.ThreadsResponse) => this.sendResponse(r), @@ -75,26 +65,21 @@ export class MI2InferiorSession extends DebugSession { } protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - this.superiorSession.miDebugger.log("stdout", `Stack trace request via inferior ${this.sessionPid}`); this.superiorSession.inferiorStackTraceRequest(response, args, (r: DebugProtocol.StackTraceResponse) => this.sendResponse(r), (r: DebugProtocol.StackTraceResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) ) - this.superiorSession.miDebugger.log("stdout", `Stack trace request via inferior ${this.sessionPid} completed`); } protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { - this.superiorSession.miDebugger.log("stdout", `configuration done request via inferior ${this.sessionPid}`); this.superiorSession.configurationDoneRequest(response, args); } protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { - this.superiorSession.miDebugger.log("stdout", `scopes request via inferior ${this.sessionPid}`); this.superiorSession.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) } protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { - this.superiorSession.miDebugger.log("stdout", `variables request via inferior ${this.sessionPid}`); this.superiorSession.inferiorVariablesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r), (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) @@ -102,7 +87,6 @@ export class MI2InferiorSession extends DebugSession { } protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { - this.superiorSession.miDebugger.log("stdout", `pause request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -111,7 +95,6 @@ export class MI2InferiorSession extends DebugSession { } protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { - this.superiorSession.miDebugger.log("stdout", `reverse continue request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.continue(true, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); @@ -125,7 +108,6 @@ export class MI2InferiorSession extends DebugSession { } protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { - this.superiorSession.miDebugger.log("stdout", `continue request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.continue(false, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); @@ -139,7 +121,6 @@ export class MI2InferiorSession extends DebugSession { } protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { - this.superiorSession.miDebugger.log("stdout", `step back request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.step(true, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -148,7 +129,6 @@ export class MI2InferiorSession extends DebugSession { } protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { - this.superiorSession.miDebugger.log("stdout", `step in request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.step(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -157,7 +137,6 @@ export class MI2InferiorSession extends DebugSession { } protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { - this.superiorSession.miDebugger.log("stdout", `step out request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.stepOut(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -166,7 +145,6 @@ export class MI2InferiorSession extends DebugSession { } protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { - this.superiorSession.miDebugger.log("stdout", `next request via inferior ${this.sessionPid}`); this.superiorSession.miDebugger.next(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -175,7 +153,6 @@ export class MI2InferiorSession extends DebugSession { } protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { - this.superiorSession.miDebugger.log("stdout", `evaluate request via inferior ${this.sessionPid}`); this.superiorSession.inferiorEvaluateRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r), (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) @@ -183,7 +160,6 @@ export class MI2InferiorSession extends DebugSession { } protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { - this.superiorSession.miDebugger.log("stdout", `goto targets request via inferior ${this.sessionPid}`); this.superiorSession.inferiorGotoTargetsRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r), (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) @@ -191,7 +167,6 @@ export class MI2InferiorSession extends DebugSession { } protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { - this.superiorSession.miDebugger.log("stdout", `goto request via inferior ${this.sessionPid}`); this.sendResponse(response); } } From 4198fde73d7de14cb7b36fea20c3e1d27955a526 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 21:15:22 +0100 Subject: [PATCH 11/26] Large refactoring --- src/backend/mi2/mi2inferior.ts | 101 ----- src/gdb.ts | 54 +-- src/lldb.ts | 40 +- src/mago.ts | 30 +- src/mibase.ts | 694 ++++----------------------------- src/miinferior.ts | 533 ++++++++++++++++++++++--- 6 files changed, 630 insertions(+), 822 deletions(-) delete mode 100644 src/backend/mi2/mi2inferior.ts diff --git a/src/backend/mi2/mi2inferior.ts b/src/backend/mi2/mi2inferior.ts deleted file mode 100644 index ac37e90..0000000 --- a/src/backend/mi2/mi2inferior.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Breakpoint, IBackend, SSHArguments, Stack, Thread, Variable } from "../backend"; -import { MI2 } from "./mi2"; - -export class MI2Inferior implements IBackend { - load(cwd: string, target: string, procArgs: string, separateConsole: string, autorun: string[]): Thenable { - return this.superior.load(cwd, target, procArgs, separateConsole, autorun); - } - - ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean, autorun: string[]): Thenable { - return this.superior.ssh(args, cwd, target, separateConsole, procArgs, attach, autorun); - } - - attach(cwd: string, executable: string, target: string, autorun: string[]): Thenable { - return this.superior.attach(cwd, executable, target, autorun); - } - - connect(cwd: string, executable: string, target: string, autorun: string[]): Thenable { - return this.superior.attach(cwd, executable, target, autorun); - } - - start(runToStart: boolean): Thenable { - return this.superior.start(runToStart); - } - - stop(): void { - this.superior.stop(); - } - - detach(): void { - this.superior.detach(); - } - - interrupt(threadId?: number): Thenable { - return this.superior.interrupt(threadId); - } - - continue(reverse?: boolean, threadId?: number): Thenable { - return this.superior.continue(reverse, threadId); - } - - next(): Thenable { - return this.superior.next(); - } - step(): Thenable { - return this.superior.step(); - } - - stepOut(): Thenable { - return this.superior.stepOut(); - } - - loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> { - return this.superior.loadBreakPoints(breakpoints); - } - - addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> { - return this.superior.addBreakPoint(breakpoint); - } - - removeBreakPoint(breakpoint: Breakpoint): Thenable { - return this.superior.removeBreakPoint(breakpoint); - } - - clearBreakPoints(source?: string): Thenable { - return this.superior.clearBreakPoints(source); - } - - getThreads(): Thenable { - return this.superior.getThreads(); - } - - getStack(startFrame: number, maxLevels: number, thread: number): Thenable { - return this.superior.getStack(startFrame, maxLevels, thread); - } - - getStackVariables(thread: number, frame: number): Thenable { - return this.superior.getStackVariables(thread, frame); - } - - evalExpression(name: string, thread: number, frame: number): Thenable { - return this.superior.evalExpression(name, thread, frame); - } - - isReady(): boolean { - return this.superior.isReady(); - } - - changeVariable(name: string, rawValue: string): Thenable { - return this.superior.changeVariable(name, rawValue); - } - - examineMemory(from: number, to: number): Thenable { - return this.superior.examineMemory(from, to); - } - - private superior: MI2; - - contructor(superior: MI2) { - this.superior = superior - } -}; \ No newline at end of file diff --git a/src/gdb.ts b/src/gdb.ts index b16b51d..6188705 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -1,4 +1,4 @@ -import { MI2DebugSession, RunCommand } from './mibase'; +import { MI2DebugSession, RunCommand, SharedState } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2, escape } from "./backend/mi2/mi2"; @@ -46,6 +46,10 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum } export class GDBDebugSession extends MI2DebugSession { + constructor(debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(new SharedState(), debuggerLinesStartAt1, isServer) + } + protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; @@ -65,22 +69,22 @@ export class GDBDebugSession extends MI2DebugSession { this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); return; } - this.miDebugger = new MI2(dbgCommand, ["-q", "--interpreter=mi2"], args.debugger_args, args.env); + this.shared.miDebugger = new MI2(dbgCommand, ["-q", "--interpreter=mi2"], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; this.attached = false; this.initialRunCommand = RunCommand.RUN; - this.isSSH = false; + this.shared.isSSH = false; this.started = false; this.crashed = false; this.setValuesFormattingMode(args.valuesFormatting); - this.miDebugger.frameFilters = !!args.frameFilters; - this.miDebugger.printCalls = !!args.printCalls; - this.miDebugger.multiProcess = !!args.multiProcess; - this.miDebugger.debugOutput = !!args.showDevDebugOutput; - this.stopAtEntry = args.stopAtEntry; - this.miDebugger.registerLimit = args.registerLimit ?? ""; + this.shared.miDebugger.frameFilters = !!args.frameFilters; + this.shared.miDebugger.printCalls = !!args.printCalls; + this.shared.miDebugger.multiProcess = !!args.multiProcess; + this.shared.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.shared.stopAtEntry = args.stopAtEntry; + this.shared.miDebugger.registerLimit = args.registerLimit ?? ""; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) args.ssh.forwardX11 = true; @@ -92,15 +96,15 @@ export class GDBDebugSession extends MI2DebugSession { args.ssh.x11host = "localhost"; if (args.ssh.remotex11screen === undefined) args.ssh.remotex11screen = 0; - this.isSSH = true; + this.shared.isSSH = true; this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd); - this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false, args.autorun || []).then(() => { + this.shared.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 105, `Failed to SSH: ${err.toString()}`); }); } else { - this.miDebugger.load(args.cwd, args.target, args.arguments, args.terminal, args.autorun || []).then(() => { + this.shared.miDebugger.load(args.cwd, args.target, args.arguments, args.terminal, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 103, `Failed to load MI Debugger: ${err.toString()}`); @@ -114,20 +118,20 @@ export class GDBDebugSession extends MI2DebugSession { this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); return; } - this.miDebugger = new MI2(dbgCommand, ["-q", "--interpreter=mi2"], args.debugger_args, args.env); + this.shared.miDebugger = new MI2(dbgCommand, ["-q", "--interpreter=mi2"], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; this.attached = !args.remote; this.initialRunCommand = args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; - this.isSSH = false; + this.shared.isSSH = false; this.setValuesFormattingMode(args.valuesFormatting); - this.miDebugger.frameFilters = !!args.frameFilters; - this.miDebugger.printCalls = !!args.printCalls; - this.miDebugger.multiProcess = !!args.multiProcess; - this.miDebugger.debugOutput = !!args.showDevDebugOutput; - this.stopAtEntry = args.stopAtEntry; - this.miDebugger.registerLimit = args.registerLimit ?? ""; + this.shared.miDebugger.frameFilters = !!args.frameFilters; + this.shared.miDebugger.printCalls = !!args.printCalls; + this.shared.miDebugger.multiProcess = !!args.multiProcess; + this.shared.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.shared.stopAtEntry = args.stopAtEntry; + this.shared.miDebugger.registerLimit = args.registerLimit ?? ""; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) args.ssh.forwardX11 = true; @@ -139,22 +143,22 @@ export class GDBDebugSession extends MI2DebugSession { args.ssh.x11host = "localhost"; if (args.ssh.remotex11screen === undefined) args.ssh.remotex11screen = 0; - this.isSSH = true; + this.shared.isSSH = true; this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd); - this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, "", undefined, true, args.autorun || []).then(() => { + this.shared.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, "", undefined, true, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 104, `Failed to SSH: ${err.toString()}`); }); } else { if (args.remote) { - this.miDebugger.connect(args.cwd, args.executable, args.target, args.autorun || []).then(() => { + this.shared.miDebugger.connect(args.cwd, args.executable, args.target, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 102, `Failed to attach: ${err.toString()}`); }); } else { - this.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { + this.shared.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 101, `Failed to attach: ${err.toString()}`); @@ -167,7 +171,7 @@ export class GDBDebugSession extends MI2DebugSession { protected setPathSubstitutions(substitutions: { [index: string]: string }): void { if (substitutions) { Object.keys(substitutions).forEach(source => { - this.miDebugger.extraCommands.push("gdb-set substitute-path \"" + escape(source) + "\" \"" + escape(substitutions[source]) + "\""); + this.shared.miDebugger.extraCommands.push("gdb-set substitute-path \"" + escape(source) + "\" \"" + escape(substitutions[source]) + "\""); }); } } diff --git a/src/lldb.ts b/src/lldb.ts index 0d7bbd4..1db5b76 100644 --- a/src/lldb.ts +++ b/src/lldb.ts @@ -1,4 +1,4 @@ -import { MI2DebugSession, RunCommand } from './mibase'; +import { MI2DebugSession, RunCommand, SharedState } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2_LLDB } from "./backend/mi2/mi2lldb"; @@ -39,6 +39,10 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum } class LLDBDebugSession extends MI2DebugSession { + constructor(debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(new SharedState(), debuggerLinesStartAt1, isServer) + } + protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; @@ -55,20 +59,20 @@ class LLDBDebugSession extends MI2DebugSession { this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); return; } - this.miDebugger = new MI2_LLDB(dbgCommand, [], args.debugger_args, args.env); + this.shared.miDebugger = new MI2_LLDB(dbgCommand, [], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; this.attached = false; this.initialRunCommand = RunCommand.RUN; - this.isSSH = false; + this.shared.isSSH = false; this.started = false; this.crashed = false; this.setValuesFormattingMode(args.valuesFormatting); - this.miDebugger.printCalls = !!args.printCalls; - this.miDebugger.debugOutput = !!args.showDevDebugOutput; - this.stopAtEntry = args.stopAtEntry; - this.miDebugger.registerLimit = args.registerLimit ?? ""; + this.shared.miDebugger.printCalls = !!args.printCalls; + this.shared.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.shared.stopAtEntry = args.stopAtEntry; + this.shared.miDebugger.registerLimit = args.registerLimit ?? ""; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) args.ssh.forwardX11 = true; @@ -80,15 +84,15 @@ class LLDBDebugSession extends MI2DebugSession { args.ssh.x11host = "localhost"; if (args.ssh.remotex11screen === undefined) args.ssh.remotex11screen = 0; - this.isSSH = true; + this.shared.isSSH = true; this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd); - this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false, args.autorun || []).then(() => { + this.shared.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 106, `Failed to SSH: ${err.toString()}`); }); } else { - this.miDebugger.load(args.cwd, args.target, args.arguments, undefined, args.autorun || []).then(() => { + this.shared.miDebugger.load(args.cwd, args.target, args.arguments, undefined, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 107, `Failed to load MI Debugger: ${err.toString()}`); @@ -102,19 +106,19 @@ class LLDBDebugSession extends MI2DebugSession { this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); return; } - this.miDebugger = new MI2_LLDB(dbgCommand, [], args.debugger_args, args.env); + this.shared.miDebugger = new MI2_LLDB(dbgCommand, [], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; this.attached = true; this.initialRunCommand = args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; - this.isSSH = false; + this.shared.isSSH = false; this.setValuesFormattingMode(args.valuesFormatting); - this.miDebugger.printCalls = !!args.printCalls; - this.miDebugger.debugOutput = !!args.showDevDebugOutput; - this.stopAtEntry = args.stopAtEntry; - this.miDebugger.registerLimit = args.registerLimit ?? ""; - this.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { + this.shared.miDebugger.printCalls = !!args.printCalls; + this.shared.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.shared.stopAtEntry = args.stopAtEntry; + this.shared.miDebugger.registerLimit = args.registerLimit ?? ""; + this.shared.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 108, `Failed to attach: ${err.toString()}`); @@ -125,7 +129,7 @@ class LLDBDebugSession extends MI2DebugSession { protected setPathSubstitutions(substitutions: { [index: string]: string }): void { if (substitutions) { Object.keys(substitutions).forEach(source => { - this.miDebugger.extraCommands.push("settings append target.source-map " + source + " " + substitutions[source]); + this.shared.miDebugger.extraCommands.push("settings append target.source-map " + source + " " + substitutions[source]); }); } } diff --git a/src/mago.ts b/src/mago.ts index 20f7f2f..4371030 100644 --- a/src/mago.ts +++ b/src/mago.ts @@ -1,4 +1,4 @@ -import { MI2DebugSession, RunCommand } from './mibase'; +import { MI2DebugSession, RunCommand, SharedState } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2_Mago } from "./backend/mi2/mi2mago"; @@ -34,8 +34,8 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum } class MagoDebugSession extends MI2DebugSession { - public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { - super(debuggerLinesStartAt1, isServer); + constructor(debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(new SharedState(), debuggerLinesStartAt1, isServer) } protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { @@ -57,19 +57,19 @@ class MagoDebugSession extends MI2DebugSession { this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); return; } - this.miDebugger = new MI2_Mago(dbgCommand, ["-q"], args.debugger_args, args.env); + this.shared.miDebugger = new MI2_Mago(dbgCommand, ["-q"], args.debugger_args, args.env); this.initDebugger(); this.quit = false; this.attached = false; this.initialRunCommand = RunCommand.RUN; - this.isSSH = false; + this.shared.isSSH = false; this.started = false; this.crashed = false; this.setValuesFormattingMode(args.valuesFormatting); - this.miDebugger.printCalls = !!args.printCalls; - this.miDebugger.debugOutput = !!args.showDevDebugOutput; - this.miDebugger.registerLimit = args.registerLimit ?? ""; - this.miDebugger.load(args.cwd, args.target, args.arguments, undefined, args.autorun || []).then(() => { + this.shared.miDebugger.printCalls = !!args.printCalls; + this.shared.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.shared.miDebugger.registerLimit = args.registerLimit ?? ""; + this.shared.miDebugger.load(args.cwd, args.target, args.arguments, undefined, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 109, `Failed to load MI Debugger: ${err.toString()}`); @@ -82,17 +82,17 @@ class MagoDebugSession extends MI2DebugSession { this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); return; } - this.miDebugger = new MI2_Mago(dbgCommand, ["-q"], args.debugger_args, args.env); + this.shared.miDebugger = new MI2_Mago(dbgCommand, ["-q"], args.debugger_args, args.env); this.initDebugger(); this.quit = false; this.attached = true; this.initialRunCommand = args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; - this.isSSH = false; + this.shared.isSSH = false; this.setValuesFormattingMode(args.valuesFormatting); - this.miDebugger.printCalls = !!args.printCalls; - this.miDebugger.debugOutput = !!args.showDevDebugOutput; - this.miDebugger.registerLimit = args.registerLimit ?? ""; - this.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { + this.shared.miDebugger.printCalls = !!args.printCalls; + this.shared.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.shared.miDebugger.registerLimit = args.registerLimit ?? ""; + this.shared.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { this.sendResponse(response); }, err => { this.sendErrorResponse(response, 110, `Failed to attach: ${err.toString()}`); diff --git a/src/mibase.ts b/src/mibase.ts index 7c5be42..d0b4c33 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -4,7 +4,6 @@ import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEv import { DebugProtocol } from 'vscode-debugprotocol'; import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; import { MINode } from './backend/mi_parse'; -import { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; import { execSync } from 'child_process'; import * as systemPath from "path"; @@ -12,14 +11,16 @@ import * as net from "net"; import * as os from "os"; import * as fs from "fs"; import { SourceFileMap } from "./source_file_map"; -import { MI2InferiorSession } from "./miinferior" +import { MI2InferiorServer, MI2InferiorSession } from "./miinferior" -class ExtendedVariable { +export enum RunCommand { CONTINUE, RUN, NONE } + +export class ExtendedVariable { constructor(public name: string, public options: { "arg": any }) { } } -class VariableScope { +export class VariableScope { constructor(public readonly name: string, public readonly threadId: number, public readonly level: number) { } @@ -28,51 +29,46 @@ class VariableScope { } } -export enum RunCommand { CONTINUE, RUN, NONE } +export class SharedState { + miDebugger: MI2; + threadGroupPids = new Map(); + threadToPid = new Map(); + mi2Inferiors = new Array(); + inferiorServers = new Array(); + variableHandles = new Handles(); + variableHandlesReverse: { [id: string]: number } = {}; + scopeHandlesReverse: { [key: string]: number } = {}; + stopAtEntry: boolean | string; + isSSH: boolean; + sourceFileMap: SourceFileMap; +} -export class MI2DebugSession extends DebugSession { - protected variableHandles = new Handles(); - protected variableHandlesReverse: { [id: string]: number } = {}; - protected scopeHandlesReverse: { [key: string]: number } = {}; - protected useVarObjects: boolean; - protected quit: boolean; - protected attached: boolean; +export class MI2DebugSession extends MI2InferiorSession { protected initialRunCommand: RunCommand; - protected stopAtEntry: boolean | string; - protected isSSH: boolean; - protected sourceFileMap: SourceFileMap; - protected started: boolean; - protected crashed: boolean; - public miDebugger: MI2; protected commandServer: net.Server; protected serverPath: string; - protected sessionPid: string | undefined = undefined; - protected threadGroupPids = new Map(); - public threadToPid = new Map(); - protected mi2Inferiors = new Array(); - protected inferiorServers = new Array(); - - public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { - super(debuggerLinesStartAt1, isServer); + + public constructor(shared: SharedState, debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(shared, debuggerLinesStartAt1, isServer); } protected initDebugger() { - this.miDebugger.on("launcherror", this.launchError.bind(this)); - this.miDebugger.on("quit", this.quitEvent.bind(this)); - this.miDebugger.on("exited-normally", this.quitEvent.bind(this)); - this.miDebugger.on("stopped", this.stopEvent.bind(this)); - this.miDebugger.on("msg", this.handleMsg.bind(this)); - this.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this)); - this.miDebugger.on("watchpoint", this.handleBreak.bind(this)); // consider to parse old/new, too (otherwise it is in the console only) - this.miDebugger.on("step-end", this.handleBreak.bind(this)); - //this.miDebugger.on("step-out-end", this.handleBreak.bind(this)); // was combined into step-end - this.miDebugger.on("step-other", this.handleBreak.bind(this)); - this.miDebugger.on("signal-stop", this.handlePause.bind(this)); - this.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this)); - this.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this)); - this.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent()))); - this.miDebugger.on("thread-group-started", this.threadGroupStartedEvent.bind(this)); - this.miDebugger.on("thread-group-exited", this.threadGroupExitedEvent.bind(this)); + this.shared.miDebugger.on("launcherror", this.launchError.bind(this)); + this.shared.miDebugger.on("quit", this.quitEvent.bind(this)); + this.shared.miDebugger.on("exited-normally", this.quitEvent.bind(this)); + this.shared.miDebugger.on("stopped", this.stopEvent.bind(this)); + this.shared.miDebugger.on("msg", this.handleMsg.bind(this)); + this.shared.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this)); + this.shared.miDebugger.on("watchpoint", this.handleBreak.bind(this)); // consider to parse old/new, too (otherwise it is in the console only) + this.shared.miDebugger.on("step-end", this.handleBreak.bind(this)); + //this.shared.miDebugger.on("step-out-end", this.handleBreak.bind(this)); // was combined into step-end + this.shared.miDebugger.on("step-other", this.handleBreak.bind(this)); + this.shared.miDebugger.on("signal-stop", this.handlePause.bind(this)); + this.shared.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this)); + this.shared.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this)); + this.shared.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent()))); + this.shared.miDebugger.on("thread-group-started", this.threadGroupStartedEvent.bind(this)); + this.shared.miDebugger.on("thread-group-exited", this.threadGroupExitedEvent.bind(this)); this.sendEvent(new InitializedEvent()); try { this.commandServer = net.createServer(c => { @@ -85,7 +81,7 @@ export class MI2DebugSession extends DebugSession { func = rawCmd.substring(0, spaceIndex); args = JSON.parse(rawCmd.substring(spaceIndex + 1)); } - Promise.resolve((this.miDebugger as any)[func].apply(this.miDebugger, args)).then(data => { + Promise.resolve((this.shared.miDebugger as any)[func].apply(this.shared.miDebugger, args)).then(data => { c.write(data.toString()); }); }); @@ -124,16 +120,16 @@ export class MI2DebugSession extends DebugSession { switch (mode) { case "disabled": this.useVarObjects = true; - this.miDebugger.prettyPrint = false; + this.shared.miDebugger.prettyPrint = false; break; case "prettyPrinters": this.useVarObjects = true; - this.miDebugger.prettyPrint = true; + this.shared.miDebugger.prettyPrint = true; break; case "parseText": default: this.useVarObjects = false; - this.miDebugger.prettyPrint = false; + this.shared.miDebugger.prettyPrint = false; } } @@ -148,7 +144,7 @@ export class MI2DebugSession extends DebugSession { protected handleBreakpoint(info: MINode) { let threadId = info.record("thread-id"); - let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + let threadPid = this.shared.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; @@ -156,14 +152,14 @@ export class MI2DebugSession extends DebugSession { if (threadPid == this.sessionPid) { this.sendEvent(event); } else { - this.mi2Inferiors.forEach(inferior => { + this.shared.mi2Inferiors.forEach(inferior => { if (threadPid == inferior.sessionPid) inferior.sendEvent(event) }); } } protected handleBreak(info?: MINode) { - let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + let threadPid = this.shared.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") === "all" : true; @@ -171,14 +167,14 @@ export class MI2DebugSession extends DebugSession { if (threadPid == this.sessionPid) { this.sendEvent(event); } else { - this.mi2Inferiors.forEach(inferior => { + this.shared.mi2Inferiors.forEach(inferior => { if (threadPid == inferior.sessionPid) inferior.sendEvent(event) }); } } protected handlePause(info: MINode) { - let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + let threadPid = this.shared.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("user request", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; @@ -186,7 +182,7 @@ export class MI2DebugSession extends DebugSession { if (threadPid == this.sessionPid) { this.sendEvent(event); } else { - this.mi2Inferiors.forEach(inferior => { + this.shared.mi2Inferiors.forEach(inferior => { if (threadPid == inferior.sessionPid) inferior.sendEvent(event) }); } @@ -196,7 +192,7 @@ export class MI2DebugSession extends DebugSession { if (!this.started) this.crashed = true; if (!this.quit) { - let threadPid = this.threadToPid.get(parseInt(info.record("thread-id"), 10)); + let threadPid = this.shared.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("exception", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; @@ -204,7 +200,7 @@ export class MI2DebugSession extends DebugSession { if (threadPid == this.sessionPid) { this.sendEvent(event); } else { - this.mi2Inferiors.forEach(inferior => { + this.shared.mi2Inferiors.forEach(inferior => { if (threadPid == inferior.sessionPid) inferior.sendEvent(event) }); } @@ -214,13 +210,13 @@ export class MI2DebugSession extends DebugSession { protected threadCreatedEvent(info: MINode) { let threadId = parseInt(info.record("id"), 10); - let threadPid = this.threadGroupPids.get(info.record("group-id")); - this.threadToPid.set(threadId, threadPid); + let threadPid = this.shared.threadGroupPids.get(info.record("group-id")); + this.shared.threadToPid.set(threadId, threadPid); if (threadPid == this.sessionPid) { this.sendEvent(new ThreadEvent("started", threadId)); } else { - this.mi2Inferiors.forEach(inferior => { + this.shared.mi2Inferiors.forEach(inferior => { if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("started", threadId)); }); } @@ -229,13 +225,13 @@ export class MI2DebugSession extends DebugSession { protected threadExitedEvent(info: MINode) { let threadId = parseInt(info.record("id"), 10); - let threadPid = this.threadGroupPids.get(info.record("group-id")); - this.threadToPid.delete(info.record("group-id")); + let threadPid = this.shared.threadGroupPids.get(info.record("group-id")); + this.shared.threadToPid.delete(info.record("group-id")); if (threadPid == this.sessionPid) { this.sendEvent(new ThreadEvent("exited", threadId)); } else { - this.mi2Inferiors.forEach(inferior => { + this.shared.mi2Inferiors.forEach(inferior => { if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("exited", threadId)); }); } @@ -255,14 +251,14 @@ export class MI2DebugSession extends DebugSession { socket.on('end', () => { console.error('>> client connection closed\n'); }); - const session = new MI2InferiorSession(superiorServer); + const session = new MI2InferiorServer(this.shared, false, true); session.setRunAsServer(true); session.start(socket, socket); - this.mi2Inferiors.push(session); + this.shared.mi2Inferiors.push(session); }).listen(port); - this.inferiorServers.push(server); + this.shared.inferiorServers.push(server); return server; } @@ -274,11 +270,11 @@ export class MI2DebugSession extends DebugSession { this.sessionPid = pid; } - this.threadGroupPids.set(info.record("id"), info.record("pid")); + this.shared.threadGroupPids.set(info.record("id"), info.record("pid")); // If there are more than 1 threadgroups active, start a new debugger session in VSCode // This makes the UI all fancy with subprocesses and threads etc. - if (this.threadGroupPids.size > 1) { + if (this.shared.threadGroupPids.size > 1) { // Open a new port for the new DebugSession to attach to const server = this.openInferiorDebugServer(this); const serverAddress = (server.address() as Net.AddressInfo).port; @@ -309,18 +305,18 @@ export class MI2DebugSession extends DebugSession { } protected threadGroupExitedEvent(info: MINode) { - let pid = this.threadGroupPids.get(info.record("id")); + let pid = this.shared.threadGroupPids.get(info.record("id")); if (pid == this.sessionPid) { // Session has no thread group anymore. Next started thread group will be debugged by this session this.sessionPid = undefined; } - this.threadGroupPids.delete(info.record("id")); + this.shared.threadGroupPids.delete(info.record("id")); } protected quitEvent(info?: MINode) { - if (this.threadGroupPids.size == 0) { + if (this.shared.threadGroupPids.size == 0) { this.quit = true; this.sendEvent(new TerminatedEvent()); @@ -340,45 +336,18 @@ export class MI2DebugSession extends DebugSession { public override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { if (this.attached) - this.miDebugger.detach(); + this.shared.miDebugger.detach(); else - this.miDebugger.stop(); + this.shared.miDebugger.stop(); this.commandServer.close(); this.commandServer = undefined; this.sendResponse(response); } - public override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { - try { - if (this.useVarObjects) { - let name = args.name; - const parent = this.variableHandles.get(args.variablesReference); - if (parent instanceof VariableScope) { - name = VariableScope.variableName(args.variablesReference, name); - } else if (parent instanceof VariableObject) { - name = `${parent.name}.${name}`; - } - - const res = await this.miDebugger.varAssign(name, args.value); - response.body = { - value: res.result("value") - }; - } else { - await this.miDebugger.changeVariable(args.name, args.value); - response.body = { - value: args.value - }; - } - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 11, `Could not continue: ${err}`); - } - } - public override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { const all: Thenable<[boolean, Breakpoint]>[] = []; args.breakpoints.forEach(brk => { - all.push(this.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition })); + all.push(this.shared.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition })); }); Promise.all(all).then(brkpoints => { const finalBrks: DebugProtocol.Breakpoint[] = []; @@ -397,13 +366,13 @@ export class MI2DebugSession extends DebugSession { public override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { let path = args.source.path; - if (this.isSSH) { + if (this.shared.isSSH) { // convert local path to ssh path - path = this.sourceFileMap.toRemotePath(path); + path = this.shared.sourceFileMap.toRemotePath(path); } - this.miDebugger.clearBreakPoints(path).then(() => { + this.shared.miDebugger.clearBreakPoints(path).then(() => { const all = args.breakpoints.map(brk => { - return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition, logMessage: brk.logMessage }); + return this.shared.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition, logMessage: brk.logMessage }); }); Promise.all(all).then(brkpoints => { const finalBrks: DebugProtocol.Breakpoint[] = []; @@ -425,98 +394,6 @@ export class MI2DebugSession extends DebugSession { }); } - public inferiorThreadsRequest(response: DebugProtocol.ThreadsResponse, - sessionPid: string, - cb_good: (res: DebugProtocol.ThreadsResponse) => any, - cb_bad: (res: DebugProtocol.ThreadsResponse, codeOrMessage: number, err: string) => any - ): void { - if (!this.miDebugger) { - cb_good(response); - return; - } - this.miDebugger.getThreads().then(threads => { - response.body = { - threads: [] - }; - for (const thread of threads) { - const threadName = thread.name || thread.targetId || ""; - - let pid = this.threadToPid.get(thread.id); - - if (pid == sessionPid) { - response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); - } - } - cb_good(response); - }).catch((error: MIError) => { - if (error.message === 'Selected thread is running.') { - cb_good(response); - return; - } - cb_bad(response, 17, `Could not get threads: ${error}`); - }); - } - - public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.inferiorThreadsRequest( - response, - this.sessionPid, - (r: DebugProtocol.ThreadsResponse) => this.sendResponse(r), - (r: DebugProtocol.ThreadsResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) - ) - } - - // Supports 65535 threads. - protected threadAndLevelToFrameId(threadId: number, level: number) { - return level << 16 | threadId; - } - protected frameIdToThreadAndLevel(frameId: number) { - return [frameId & 0xffff, frameId >> 16]; - } - - public inferiorStackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments, - cb_good: (res: DebugProtocol.StackTraceResponse) => any, - cb_bad: (res: DebugProtocol.StackTraceResponse, codeOrMessage: number, err: string) => any) { - this.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { - const ret: StackFrame[] = []; - stack.forEach(element => { - let source = undefined; - let path = element.file; - if (path) { - if (this.isSSH) { - // convert ssh path to local path - path = this.sourceFileMap.toLocalPath(path); - } else if (process.platform === "win32") { - if (path.startsWith("\\cygdrive\\") || path.startsWith("/cygdrive/")) { - path = path[10] + ":" + path.substring(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt - } - } - source = new Source(element.fileName, path); - } - - ret.push(new StackFrame( - this.threadAndLevelToFrameId(args.threadId, element.level), - element.function + (element.address ? "@" + element.address : ""), - source, - element.line, - 0)); - }); - response.body = { - stackFrames: ret - }; - cb_good(response); - }, err => { - cb_bad(response, 12, `Failed to get Stack Trace: ${err.toString()}`); - }); - } - - public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - this.inferiorStackTraceRequest(response, args, - (r: DebugProtocol.StackTraceResponse) => this.sendResponse(r), - (r: DebugProtocol.StackTraceResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) - ) - } - public override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { const promises: Thenable[] = []; let entryPoint: string | undefined = undefined; @@ -525,28 +402,28 @@ export class MI2DebugSession extends DebugSession { switch (this.initialRunCommand) { case RunCommand.CONTINUE: case RunCommand.NONE: - if (typeof this.stopAtEntry === 'boolean' && this.stopAtEntry) + if (typeof this.shared.stopAtEntry === 'boolean' && this.shared.stopAtEntry) entryPoint = "main"; // sensible default - else if (typeof this.stopAtEntry === 'string') - entryPoint = this.stopAtEntry; + else if (typeof this.shared.stopAtEntry === 'string') + entryPoint = this.shared.stopAtEntry; break; case RunCommand.RUN: - if (typeof this.stopAtEntry === 'boolean' && this.stopAtEntry) { - if (this.miDebugger.features.includes("exec-run-start-option")) + if (typeof this.shared.stopAtEntry === 'boolean' && this.shared.stopAtEntry) { + if (this.shared.miDebugger.features.includes("exec-run-start-option")) runToStart = true; else entryPoint = "main"; // sensible fallback - } else if (typeof this.stopAtEntry === 'string') - entryPoint = this.stopAtEntry; + } else if (typeof this.shared.stopAtEntry === 'string') + entryPoint = this.shared.stopAtEntry; break; default: throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]); } if (entryPoint) - promises.push(this.miDebugger.setEntryBreakPoint(entryPoint)); + promises.push(this.shared.miDebugger.setEntryBreakPoint(entryPoint)); switch (this.initialRunCommand) { case RunCommand.CONTINUE: - promises.push(this.miDebugger.continue().then(() => { + promises.push(this.shared.miDebugger.continue().then(() => { // Some debuggers will provide an out-of-band status that they are stopped // when attaching (e.g., gdb), so the client assumes we are stopped and gets // confused if we start running again on our own. @@ -559,7 +436,7 @@ export class MI2DebugSession extends DebugSession { })); break; case RunCommand.RUN: - promises.push(this.miDebugger.start(runToStart).then(() => { + promises.push(this.shared.miDebugger.start(runToStart).then(() => { this.started = true; if (this.crashed) this.handlePause(undefined); @@ -586,433 +463,34 @@ export class MI2DebugSession extends DebugSession { }); } - public inferiorScopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments, - cb_good: (r: DebugProtocol.Response) => void - ) { - const scopes = new Array(); - const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); - - const createScope = (scopeName: string, expensive: boolean): Scope => { - const key: string = scopeName + ":" + threadId + ":" + level; - let handle: number; - - if (this.scopeHandlesReverse.hasOwnProperty(key)) { - handle = this.scopeHandlesReverse[key]; - } else { - handle = this.variableHandles.create(new VariableScope(scopeName, threadId, level)); - this.scopeHandlesReverse[key] = handle; - } - - return new Scope(scopeName, handle, expensive); - }; - scopes.push(createScope("Locals", false)); - scopes.push(createScope("Registers", false)); - response.body = { - scopes: scopes - }; - cb_good(response); - } - public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { - this.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) - } - public async inferiorVariablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, - cb_good: (r: DebugProtocol.Response) => void, - cb_bad: (r: DebugProtocol.Response, n: number, m: string) => void - ) { - const variables: DebugProtocol.Variable[] = []; - const id: VariableScope | string | VariableObject | ExtendedVariable = this.variableHandles.get(args.variablesReference); - - const createVariable = (arg: string | VariableObject, options?: any) => { - if (options) - return this.variableHandles.create(new ExtendedVariable(typeof arg === 'string' ? arg : arg.name, options)); - else - return this.variableHandles.create(arg); - }; - - const findOrCreateVariable = (varObj: VariableObject): number => { - let id: number; - if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) { - id = this.variableHandlesReverse[varObj.name]; - } else { - id = createVariable(varObj); - this.variableHandlesReverse[varObj.name] = id; - } - return varObj.isCompound() ? id : 0; - }; - - if (id instanceof VariableScope) { - try { - if (id.name === "Registers") { - const registers = await this.miDebugger.getRegisters(); - for (const reg of registers) { - variables.push({ - name: reg.name, - value: reg.valueStr, - variablesReference: 0 - }); - } - } else { - const stack: Variable[] = await this.miDebugger.getStackVariables(id.threadId, id.level); - for (const variable of stack) { - if (this.useVarObjects) { - try { - const varObjName = VariableScope.variableName(args.variablesReference, variable.name); - let varObj: VariableObject; - try { - const changes = await this.miDebugger.varUpdate(varObjName); - const changelist = changes.result("changelist"); - changelist.forEach((change: any) => { - const name = MINode.valueOf(change, "name"); - const vId = this.variableHandlesReverse[name]; - const v = this.variableHandles.get(vId) as any; - v.applyChanges(change); - }); - const varId = this.variableHandlesReverse[varObjName]; - varObj = this.variableHandles.get(varId) as any; - } catch (err) { - if (err instanceof MIError && (err.message === "Variable object not found" || err.message.endsWith("does not exist"))) { - varObj = await this.miDebugger.varCreate(id.threadId, id.level, variable.name, varObjName); - const varId = findOrCreateVariable(varObj); - varObj.exp = variable.name; - varObj.id = varId; - } else { - throw err; - } - } - variables.push(varObj.toProtocolVariable()); - } catch (err) { - variables.push({ - name: variable.name, - value: `<${err}>`, - variablesReference: 0 - }); - } - } else { - if (variable.valueStr !== undefined) { - let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw); - if (expanded) { - if (typeof expanded[0] === "string") - expanded = [ - { - name: "", - value: prettyStringArray(expanded), - variablesReference: 0 - } - ]; - variables.push(expanded[0]); - } - } else - variables.push({ - name: variable.name, - type: variable.type, - value: variable.type, - variablesReference: createVariable(variable.name) - }); - } - } - } - response.body = { - variables: variables - }; - cb_good(response); - } catch (err) { - cb_bad(response, 1, `Could not expand variable: ${err}`); - } - } else if (typeof id === "string") { - // Variable members - let variable; - try { - // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. - variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0); - try { - let variableValue = variable.result("value"); - const pattern = /'([^']*)' /g; - variableValue = variableValue.replace(pattern, (_: any, char: string, count: string) => { - const repeatCount = parseInt(count, 10) + 1; - const repeatedArray = Array(repeatCount).fill(char); - return `{${repeatedArray.map(item => `'${item}'`).join(', ')}}`; - }); - let expanded = expandValue(createVariable, variableValue, id, variable); - if (!expanded) { - this.sendErrorResponse(response, 2, `Could not expand variable`); - } else { - if (typeof expanded[0] === "string") - expanded = [ - { - name: "", - value: prettyStringArray(expanded), - variablesReference: 0 - } - ]; - response.body = { - variables: expanded - }; - cb_good(response); - } - } catch (e) { - cb_bad(response, 2, `Could not expand variable: ${e}`); - } - } catch (err) { - cb_bad(response, 1, `Could not expand variable: ${err}`); - } - } else if (typeof id === "object") { - if (id instanceof VariableObject) { - // Variable members - let children: VariableObject[]; - try { - children = await this.miDebugger.varListChildren(id.name); - const vars = children.map(child => { - const varId = findOrCreateVariable(child); - child.id = varId; - return child.toProtocolVariable(); - }); - response.body = { - variables: vars - }; - cb_good(response); - } catch (err) { - cb_bad(response, 1, `Could not expand variable: ${err}`); - } - } else if (id instanceof ExtendedVariable) { - const varReq = id; - if (varReq.options.arg) { - const strArr: DebugProtocol.Variable[] = []; - let argsPart = true; - let arrIndex = 0; - const submit = () => { - response.body = { - variables: strArr - }; - cb_good(response); - }; - const addOne = async () => { - // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. - const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0); - try { - const expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable); - if (!expanded) { - this.sendErrorResponse(response, 15, `Could not expand variable`); - } else { - if (typeof expanded === "string") { - if (expanded === "") { - if (argsPart) - argsPart = false; - else - return submit(); - } else if (expanded[0] !== '"') { - strArr.push({ - name: "[err]", - value: expanded, - variablesReference: 0 - }); - return submit(); - } - strArr.push({ - name: `[${(arrIndex++)}]`, - value: expanded, - variablesReference: 0 - }); - addOne(); - } else { - strArr.push({ - name: "[err]", - value: expanded, - variablesReference: 0 - }); - submit(); - } - } - } catch (e) { - cb_bad(response, 14, `Could not expand variable: ${e}`); - } - }; - addOne(); - } else - cb_bad(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); - } else { - response.body = { - variables: id - }; - cb_good(response); - } - } else { - response.body = { - variables: variables - }; - cb_good(response); - } - } - public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { - return this.inferiorVariablesRequest(response, args, - (r: DebugProtocol.VariablesResponse) => this.sendResponse(r), - (r: DebugProtocol.VariablesResponse, n: number, m: string) => this.sendErrorResponse(r, n, m) - ) - } - public override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { - this.miDebugger.interrupt(args.threadId).then(done => { - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 3, `Could not pause: ${msg}`); - }); - } - public override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { - this.miDebugger.continue(true, args.threadId).then(done => { - if (!response.hasOwnProperty("body")) { - response.body = Object(); - } - response.body.allThreadsContinued = false; - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); - }); - } - public override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { - this.miDebugger.continue(false, args.threadId).then(done => { - if (!response.hasOwnProperty("body")) { - response.body = Object(); - } - response.body.allThreadsContinued = false; - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); - }); - } - public override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { - this.miDebugger.step(true, args.threadId).then(done => { - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`); - }); - } - public override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { - this.miDebugger.step(false, args.threadId).then(done => { - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 4, `Could not step in: ${msg}`); - }); - } - public override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { - this.miDebugger.stepOut(false, args.threadId).then(done => { - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 5, `Could not step out: ${msg}`); - }); - } - public override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { - this.miDebugger.next(false, args.threadId).then(done => { - this.sendResponse(response); - }, msg => { - this.sendErrorResponse(response, 6, `Could not step over: ${msg}`); - }); - } + - public inferiorEvaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments, - cb_good: (r: DebugProtocol.Response) => void, - cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void - ): void { - const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); - if (args.context === "watch" || args.context === "hover") { - this.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { - response.body = { - variablesReference: 0, - result: res.result("value") - }; - cb_good(response); - }, msg => { - if (args.context === "hover") { - // suppress error for hover as the user may just play with the mouse - cb_good(response); - } else { - cb_bad(response, 7, msg.toString()); - } - }); - } else { - this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => { - if (typeof output === "undefined") - response.body = { - result: "", - variablesReference: 0 - }; - else - response.body = { - result: JSON.stringify(output), - variablesReference: 0 - }; - cb_good(response); - }, msg => { - cb_bad(response, 8, msg.toString()); - }); - } - } - public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { - this.inferiorEvaluateRequest(response, args, - (r: DebugProtocol.Response) => this.sendResponse(r), - (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) - ) - } - - public inferiorGotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments, - cb_good: (r: DebugProtocol.Response) => void, - cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void - ): void { - const path: string = this.isSSH ? this.sourceFileMap.toRemotePath(args.source.path) : args.source.path; - this.miDebugger.goto(path, args.line).then(done => { - response.body = { - targets: [{ - id: 1, - label: args.source.name, - column: args.column, - line: args.line - }] - }; - cb_good(response); - }, msg => { - cb_bad(response, 16, `Could not jump: ${msg}`); - }); - } - public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { - this.sendResponse(response); - } - protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void { + protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void { if (configMap === undefined) { - this.sourceFileMap = new SourceFileMap({ [fallbackGDB]: fallbackIDE }); + this.shared.sourceFileMap = new SourceFileMap({ [fallbackGDB]: fallbackIDE }); } else { - this.sourceFileMap = new SourceFileMap(configMap, fallbackGDB); + this.shared.sourceFileMap = new SourceFileMap(configMap, fallbackGDB); } } - public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { - this.inferiorGotoTargetsRequest( - response, args, - (r: DebugProtocol.Response) => this.sendResponse(r), - (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) - ); - } -} -function prettyStringArray(strings: any) { - if (typeof strings === "object") { - if (strings.length !== undefined) - return strings.join(", "); - else - return JSON.stringify(strings); - } else return strings; } + diff --git a/src/miinferior.ts b/src/miinferior.ts index 30aeb19..b65d75a 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -1,22 +1,29 @@ -import { MI2DebugSession } from './mibase'; +import { ExtendedVariable, MI2DebugSession, SharedState, VariableScope } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; +import { expandValue, isExpandable } from './backend/gdb_expansion'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { MIError } from './backend/backend'; import { setFlagsFromString } from 'v8'; +import { MI2 } from './backend/mi2/mi2'; +import { MINode } from './backend/mi_parse'; -export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { - parentSession: MI2DebugSession | DebugSession, +export interface InferiorAttachRequestArguments extends DebugProtocol.AttachRequestArguments { target: string } export class MI2InferiorSession extends DebugSession { - superiorSession: MI2DebugSession; sessionPid: string | undefined; - constructor(superiorSession: MI2DebugSession) { - super(false, true); + protected useVarObjects: boolean; + protected quit: boolean; + protected attached: boolean; + protected started: boolean; + protected crashed: boolean; + protected shared: SharedState; - this.superiorSession = superiorSession; + constructor(shared: SharedState, debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(debuggerLinesStartAt1, isServer); + this.shared = shared; } protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { @@ -33,69 +40,409 @@ export class MI2InferiorSession extends DebugSession { this.sendResponse(response); } - protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { - // Attached to server - const pargs = JSON.stringify(args); - - this.sessionPid = args.target; + public override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + this.sendResponse(response); } - protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { - this.superiorSession.disconnectRequest(response, args); - } + public override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + try { + if (this.useVarObjects) { + let name = args.name; + const parent = this.shared.variableHandles.get(args.variablesReference); + if (parent instanceof VariableScope) { + name = VariableScope.variableName(args.variablesReference, name); + } else if (parent instanceof VariableObject) { + name = `${parent.name}.${name}`; + } - protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { - this.superiorSession.setVariableRequest(response, args); + const res = await this.shared.miDebugger.varAssign(name, args.value); + response.body = { + value: res.result("value") + }; + } else { + await this.shared.miDebugger.changeVariable(args.name, args.value); + response.body = { + value: args.value + }; + } + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 11, `Could not continue: ${err}`); + } } - protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { - this.superiorSession.setFunctionBreakPointsRequest(response, args); - } + public inferiorThreadsRequest(response: DebugProtocol.ThreadsResponse, + sessionPid: string, + cb_good: (res: DebugProtocol.ThreadsResponse) => any, + cb_bad: (res: DebugProtocol.ThreadsResponse, codeOrMessage: number, err: string) => any + ): void { + if (!this.shared.miDebugger) { + cb_good(response); + return; + } + this.shared.miDebugger.getThreads().then(threads => { + response.body = { + threads: [] + }; + for (const thread of threads) { + const threadName = thread.name || thread.targetId || ""; + + let pid = this.shared.threadToPid.get(thread.id); - protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { - this.superiorSession.setBreakPointsRequest(response, args); + if (pid == sessionPid) { + response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); + } + } + cb_good(response); + }).catch((error: MIError) => { + if (error.message === 'Selected thread is running.') { + cb_good(response); + return; + } + cb_bad(response, 17, `Could not get threads: ${error}`); + }); } - protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.superiorSession.inferiorThreadsRequest(response, + public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { + this.inferiorThreadsRequest( + response, this.sessionPid, (r: DebugProtocol.ThreadsResponse) => this.sendResponse(r), (r: DebugProtocol.ThreadsResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) ) } - protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - this.superiorSession.inferiorStackTraceRequest(response, args, + // Supports 65535 threads. + protected threadAndLevelToFrameId(threadId: number, level: number) { + return level << 16 | threadId; + } + protected frameIdToThreadAndLevel(frameId: number) { + return [frameId & 0xffff, frameId >> 16]; + } + + public inferiorStackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments, + cb_good: (res: DebugProtocol.StackTraceResponse) => any, + cb_bad: (res: DebugProtocol.StackTraceResponse, codeOrMessage: number, err: string) => any) { + this.shared.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { + const ret: StackFrame[] = []; + stack.forEach(element => { + let source = undefined; + let path = element.file; + if (path) { + if (this.shared.isSSH) { + // convert ssh path to local path + path = this.shared.sourceFileMap.toLocalPath(path); + } else if (process.platform === "win32") { + if (path.startsWith("\\cygdrive\\") || path.startsWith("/cygdrive/")) { + path = path[10] + ":" + path.substring(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt + } + } + source = new Source(element.fileName, path); + } + + ret.push(new StackFrame( + this.threadAndLevelToFrameId(args.threadId, element.level), + element.function + (element.address ? "@" + element.address : ""), + source, + element.line, + 0)); + }); + response.body = { + stackFrames: ret + }; + cb_good(response); + }, err => { + cb_bad(response, 12, `Failed to get Stack Trace: ${err.toString()}`); + }); + } + + public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + this.inferiorStackTraceRequest(response, args, (r: DebugProtocol.StackTraceResponse) => this.sendResponse(r), (r: DebugProtocol.StackTraceResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) ) } - protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { - this.superiorSession.configurationDoneRequest(response, args); + + public inferiorScopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments, + cb_good: (r: DebugProtocol.Response) => void + ) { + const scopes = new Array(); + const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); + + const createScope = (scopeName: string, expensive: boolean): Scope => { + const key: string = scopeName + ":" + threadId + ":" + level; + let handle: number; + + if (this.shared.scopeHandlesReverse.hasOwnProperty(key)) { + handle = this.shared.scopeHandlesReverse[key]; + } else { + handle = this.shared.variableHandles.create(new VariableScope(scopeName, threadId, level)); + this.shared.scopeHandlesReverse[key] = handle; + } + + return new Scope(scopeName, handle, expensive); + }; + + scopes.push(createScope("Locals", false)); + scopes.push(createScope("Registers", false)); + + response.body = { + scopes: scopes + }; + cb_good(response); } - protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { - this.superiorSession.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) + public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + this.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) } - protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { - this.superiorSession.inferiorVariablesRequest(response, args, - (r: DebugProtocol.Response) => this.sendResponse(r), - (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) - ); + public async inferiorVariablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, + cb_good: (r: DebugProtocol.Response) => void, + cb_bad: (r: DebugProtocol.Response, n: number, m: string) => void + ) { + const variables: DebugProtocol.Variable[] = []; + const id: VariableScope | string | VariableObject | ExtendedVariable = this.shared.variableHandles.get(args.variablesReference); + + const createVariable = (arg: string | VariableObject, options?: any) => { + if (options) + return this.shared.variableHandles.create(new ExtendedVariable(typeof arg === 'string' ? arg : arg.name, options)); + else + return this.shared.variableHandles.create(arg); + }; + + const findOrCreateVariable = (varObj: VariableObject): number => { + let id: number; + if (this.shared.variableHandlesReverse.hasOwnProperty(varObj.name)) { + id = this.shared.variableHandlesReverse[varObj.name]; + } else { + id = createVariable(varObj); + this.shared.variableHandlesReverse[varObj.name] = id; + } + return varObj.isCompound() ? id : 0; + }; + + if (id instanceof VariableScope) { + try { + if (id.name === "Registers") { + const registers = await this.shared.miDebugger.getRegisters(); + for (const reg of registers) { + variables.push({ + name: reg.name, + value: reg.valueStr, + variablesReference: 0 + }); + } + } else { + const stack: Variable[] = await this.shared.miDebugger.getStackVariables(id.threadId, id.level); + for (const variable of stack) { + if (this.useVarObjects) { + try { + const varObjName = VariableScope.variableName(args.variablesReference, variable.name); + let varObj: VariableObject; + try { + const changes = await this.shared.miDebugger.varUpdate(varObjName); + const changelist = changes.result("changelist"); + changelist.forEach((change: any) => { + const name = MINode.valueOf(change, "name"); + const vId = this.shared.variableHandlesReverse[name]; + const v = this.shared.variableHandles.get(vId) as any; + v.applyChanges(change); + }); + const varId = this.shared.variableHandlesReverse[varObjName]; + varObj = this.shared.variableHandles.get(varId) as any; + } catch (err) { + if (err instanceof MIError && (err.message === "Variable object not found" || err.message.endsWith("does not exist"))) { + varObj = await this.shared.miDebugger.varCreate(id.threadId, id.level, variable.name, varObjName); + const varId = findOrCreateVariable(varObj); + varObj.exp = variable.name; + varObj.id = varId; + } else { + throw err; + } + } + variables.push(varObj.toProtocolVariable()); + } catch (err) { + variables.push({ + name: variable.name, + value: `<${err}>`, + variablesReference: 0 + }); + } + } else { + if (variable.valueStr !== undefined) { + let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw); + if (expanded) { + if (typeof expanded[0] === "string") + expanded = [ + { + name: "", + value: prettyStringArray(expanded), + variablesReference: 0 + } + ]; + variables.push(expanded[0]); + } + } else + variables.push({ + name: variable.name, + type: variable.type, + value: variable.type, + variablesReference: createVariable(variable.name) + }); + } + } + } + response.body = { + variables: variables + }; + cb_good(response); + } catch (err) { + cb_bad(response, 1, `Could not expand variable: ${err}`); + } + } else if (typeof id === "string") { + // Variable members + let variable; + try { + // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. + variable = await this.shared.miDebugger.evalExpression(JSON.stringify(id), 0, 0); + try { + let variableValue = variable.result("value"); + const pattern = /'([^']*)' /g; + variableValue = variableValue.replace(pattern, (_: any, char: string, count: string) => { + const repeatCount = parseInt(count, 10) + 1; + const repeatedArray = Array(repeatCount).fill(char); + return `{${repeatedArray.map(item => `'${item}'`).join(', ')}}`; + }); + let expanded = expandValue(createVariable, variableValue, id, variable); + if (!expanded) { + this.sendErrorResponse(response, 2, `Could not expand variable`); + } else { + if (typeof expanded[0] === "string") + expanded = [ + { + name: "", + value: prettyStringArray(expanded), + variablesReference: 0 + } + ]; + response.body = { + variables: expanded + }; + cb_good(response); + } + } catch (e) { + cb_bad(response, 2, `Could not expand variable: ${e}`); + } + } catch (err) { + cb_bad(response, 1, `Could not expand variable: ${err}`); + } + } else if (typeof id === "object") { + if (id instanceof VariableObject) { + // Variable members + let children: VariableObject[]; + try { + children = await this.shared.miDebugger.varListChildren(id.name); + const vars = children.map(child => { + const varId = findOrCreateVariable(child); + child.id = varId; + return child.toProtocolVariable(); + }); + + response.body = { + variables: vars + }; + cb_good(response); + } catch (err) { + cb_bad(response, 1, `Could not expand variable: ${err}`); + } + } else if (id instanceof ExtendedVariable) { + const varReq = id; + if (varReq.options.arg) { + const strArr: DebugProtocol.Variable[] = []; + let argsPart = true; + let arrIndex = 0; + const submit = () => { + response.body = { + variables: strArr + }; + cb_good(response); + }; + const addOne = async () => { + // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. + const variable = await this.shared.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0); + try { + const expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable); + if (!expanded) { + this.sendErrorResponse(response, 15, `Could not expand variable`); + } else { + if (typeof expanded === "string") { + if (expanded === "") { + if (argsPart) + argsPart = false; + else + return submit(); + } else if (expanded[0] !== '"') { + strArr.push({ + name: "[err]", + value: expanded, + variablesReference: 0 + }); + return submit(); + } + strArr.push({ + name: `[${(arrIndex++)}]`, + value: expanded, + variablesReference: 0 + }); + addOne(); + } else { + strArr.push({ + name: "[err]", + value: expanded, + variablesReference: 0 + }); + submit(); + } + } + } catch (e) { + cb_bad(response, 14, `Could not expand variable: ${e}`); + } + }; + addOne(); + } else + cb_bad(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); + } else { + response.body = { + variables: id + }; + cb_good(response); + } + } else { + response.body = { + variables: variables + }; + cb_good(response); + } } - protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { - this.superiorSession.miDebugger.interrupt(args.threadId).then(done => { + public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + return this.inferiorVariablesRequest(response, args, + (r: DebugProtocol.VariablesResponse) => this.sendResponse(r), + (r: DebugProtocol.VariablesResponse, n: number, m: string) => this.sendErrorResponse(r, n, m) + ) + } + + public override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { + this.shared.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 3, `Could not pause: ${msg}`); }); } - protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { - this.superiorSession.miDebugger.continue(true, args.threadId).then(done => { + public override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + this.shared.miDebugger.continue(true, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); } @@ -108,7 +455,7 @@ export class MI2InferiorSession extends DebugSession { } protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { - this.superiorSession.miDebugger.continue(false, args.threadId).then(done => { + this.shared.miDebugger.continue(false, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); } @@ -120,53 +467,129 @@ export class MI2InferiorSession extends DebugSession { }); } - protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { - this.superiorSession.miDebugger.step(true, args.threadId).then(done => { + public override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + this.shared.miDebugger.step(true, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`); }); } - protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { - this.superiorSession.miDebugger.step(false, args.threadId).then(done => { + public override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { + this.shared.miDebugger.step(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 4, `Could not step in: ${msg}`); }); } - protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { - this.superiorSession.miDebugger.stepOut(false, args.threadId).then(done => { + public override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { + this.shared.miDebugger.stepOut(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 5, `Could not step out: ${msg}`); }); } - protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { - this.superiorSession.miDebugger.next(false, args.threadId).then(done => { + public override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.shared.miDebugger.next(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { this.sendErrorResponse(response, 6, `Could not step over: ${msg}`); }); } - protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { - this.superiorSession.inferiorEvaluateRequest(response, args, + public inferiorEvaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments, + cb_good: (r: DebugProtocol.Response) => void, + cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void + ): void { + const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); + if (args.context === "watch" || args.context === "hover") { + this.shared.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { + response.body = { + variablesReference: 0, + result: res.result("value") + }; + cb_good(response); + }, msg => { + if (args.context === "hover") { + // suppress error for hover as the user may just play with the mouse + cb_good(response); + } else { + cb_bad(response, 7, msg.toString()); + } + }); + } else { + this.shared.miDebugger.sendUserInput(args.expression, threadId, level).then(output => { + if (typeof output === "undefined") + response.body = { + result: "", + variablesReference: 0 + }; + else + response.body = { + result: JSON.stringify(output), + variablesReference: 0 + }; + cb_good(response); + }, msg => { + cb_bad(response, 8, msg.toString()); + }); + } + } + + public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + this.inferiorEvaluateRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r), (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) - ); + ) + } + + public inferiorGotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments, + cb_good: (r: DebugProtocol.Response) => void, + cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void + ): void { + const path: string = this.shared.isSSH ? this.shared.sourceFileMap.toRemotePath(args.source.path) : args.source.path; + this.shared.miDebugger.goto(path, args.line).then(done => { + response.body = { + targets: [{ + id: 1, + label: args.source.name, + column: args.column, + line: args.line + }] + }; + cb_good(response); + }, msg => { + cb_bad(response, 16, `Could not jump: ${msg}`); + }); } - protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { - this.superiorSession.inferiorGotoTargetsRequest(response, args, + public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + this.inferiorGotoTargetsRequest( + response, args, (r: DebugProtocol.Response) => this.sendResponse(r), (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) ); } - protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { this.sendResponse(response); } } + +function prettyStringArray(strings: any) { + if (typeof strings === "object") { + if (strings.length !== undefined) + return strings.join(", "); + else + return JSON.stringify(strings); + } else return strings; +} + +export class MI2InferiorServer extends MI2InferiorSession { + protected override attachRequest(response: DebugProtocol.AttachResponse, args: InferiorAttachRequestArguments): void { + // Attached to server + this.sessionPid = args.target; + } +} \ No newline at end of file From ccc295c4a44d522866a72eb2f7d4443643c393d7 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 21:21:35 +0100 Subject: [PATCH 12/26] Cleaned up miinferior.ts --- src/miinferior.ts | 126 ++++++++++++---------------------------------- 1 file changed, 33 insertions(+), 93 deletions(-) diff --git a/src/miinferior.ts b/src/miinferior.ts index b65d75a..6f405f1 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -71,13 +71,9 @@ export class MI2InferiorSession extends DebugSession { } } - public inferiorThreadsRequest(response: DebugProtocol.ThreadsResponse, - sessionPid: string, - cb_good: (res: DebugProtocol.ThreadsResponse) => any, - cb_bad: (res: DebugProtocol.ThreadsResponse, codeOrMessage: number, err: string) => any - ): void { + public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { if (!this.shared.miDebugger) { - cb_good(response); + this.sendResponse(response); return; } this.shared.miDebugger.getThreads().then(threads => { @@ -89,29 +85,20 @@ export class MI2InferiorSession extends DebugSession { let pid = this.shared.threadToPid.get(thread.id); - if (pid == sessionPid) { + if (pid == this.sessionPid) { response.body.threads.push(new Thread(thread.id, `${thread.id}:${threadName}`)); } } - cb_good(response); + this.sendResponse(response); }).catch((error: MIError) => { if (error.message === 'Selected thread is running.') { - cb_good(response); + this.sendResponse(response); return; } - cb_bad(response, 17, `Could not get threads: ${error}`); + this.sendErrorResponse(response, 17, `Could not get threads: ${error}`); }); } - public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { - this.inferiorThreadsRequest( - response, - this.sessionPid, - (r: DebugProtocol.ThreadsResponse) => this.sendResponse(r), - (r: DebugProtocol.ThreadsResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) - ) - } - // Supports 65535 threads. protected threadAndLevelToFrameId(threadId: number, level: number) { return level << 16 | threadId; @@ -120,9 +107,7 @@ export class MI2InferiorSession extends DebugSession { return [frameId & 0xffff, frameId >> 16]; } - public inferiorStackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments, - cb_good: (res: DebugProtocol.StackTraceResponse) => any, - cb_bad: (res: DebugProtocol.StackTraceResponse, codeOrMessage: number, err: string) => any) { + public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { this.shared.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { const ret: StackFrame[] = []; stack.forEach(element => { @@ -150,23 +135,13 @@ export class MI2InferiorSession extends DebugSession { response.body = { stackFrames: ret }; - cb_good(response); + this.sendResponse(response); }, err => { - cb_bad(response, 12, `Failed to get Stack Trace: ${err.toString()}`); + this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`); }); } - public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - this.inferiorStackTraceRequest(response, args, - (r: DebugProtocol.StackTraceResponse) => this.sendResponse(r), - (r: DebugProtocol.StackTraceResponse, c: number, e: string) => this.sendErrorResponse(r, c, e) - ) - } - - - public inferiorScopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments, - cb_good: (r: DebugProtocol.Response) => void - ) { + public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { const scopes = new Array(); const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); @@ -190,17 +165,10 @@ export class MI2InferiorSession extends DebugSession { response.body = { scopes: scopes }; - cb_good(response); - } - - public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { - this.inferiorScopesRequest(response, args, (r: DebugProtocol.Response) => this.sendResponse(r)) + this.sendResponse(response); } - public async inferiorVariablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, - cb_good: (r: DebugProtocol.Response) => void, - cb_bad: (r: DebugProtocol.Response, n: number, m: string) => void - ) { + public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; const id: VariableScope | string | VariableObject | ExtendedVariable = this.shared.variableHandles.get(args.variablesReference); @@ -296,9 +264,9 @@ export class MI2InferiorSession extends DebugSession { response.body = { variables: variables }; - cb_good(response); + this.sendResponse(response); } catch (err) { - cb_bad(response, 1, `Could not expand variable: ${err}`); + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } } else if (typeof id === "string") { // Variable members @@ -329,13 +297,13 @@ export class MI2InferiorSession extends DebugSession { response.body = { variables: expanded }; - cb_good(response); + this.sendResponse(response); } } catch (e) { - cb_bad(response, 2, `Could not expand variable: ${e}`); + this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`); } } catch (err) { - cb_bad(response, 1, `Could not expand variable: ${err}`); + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } } else if (typeof id === "object") { if (id instanceof VariableObject) { @@ -352,9 +320,9 @@ export class MI2InferiorSession extends DebugSession { response.body = { variables: vars }; - cb_good(response); + this.sendResponse(response); } catch (err) { - cb_bad(response, 1, `Could not expand variable: ${err}`); + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } } else if (id instanceof ExtendedVariable) { const varReq = id; @@ -366,7 +334,7 @@ export class MI2InferiorSession extends DebugSession { response.body = { variables: strArr }; - cb_good(response); + this.sendResponse(response); }; const addOne = async () => { // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. @@ -406,33 +374,26 @@ export class MI2InferiorSession extends DebugSession { } } } catch (e) { - cb_bad(response, 14, `Could not expand variable: ${e}`); + this.sendErrorResponse(response, 14, `Could not expand variable: ${e}`); } }; addOne(); } else - cb_bad(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); + this.sendErrorResponse(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); } else { response.body = { variables: id }; - cb_good(response); + this.sendResponse(response); } } else { response.body = { variables: variables }; - cb_good(response); + this.sendResponse(response); } } - public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { - return this.inferiorVariablesRequest(response, args, - (r: DebugProtocol.VariablesResponse) => this.sendResponse(r), - (r: DebugProtocol.VariablesResponse, n: number, m: string) => this.sendErrorResponse(r, n, m) - ) - } - public override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { this.shared.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); @@ -499,10 +460,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public inferiorEvaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments, - cb_good: (r: DebugProtocol.Response) => void, - cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void - ): void { + public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); if (args.context === "watch" || args.context === "hover") { this.shared.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { @@ -510,13 +468,13 @@ export class MI2InferiorSession extends DebugSession { variablesReference: 0, result: res.result("value") }; - cb_good(response); + this.sendResponse(response); }, msg => { if (args.context === "hover") { // suppress error for hover as the user may just play with the mouse - cb_good(response); + this.sendResponse(response); } else { - cb_bad(response, 7, msg.toString()); + this.sendErrorResponse(response, 7, msg.toString()); } }); } else { @@ -531,24 +489,14 @@ export class MI2InferiorSession extends DebugSession { result: JSON.stringify(output), variablesReference: 0 }; - cb_good(response); + this.sendResponse(response); }, msg => { - cb_bad(response, 8, msg.toString()); + this.sendErrorResponse(response, 8, msg.toString()); }); } } - public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { - this.inferiorEvaluateRequest(response, args, - (r: DebugProtocol.Response) => this.sendResponse(r), - (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) - ) - } - - public inferiorGotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments, - cb_good: (r: DebugProtocol.Response) => void, - cb_bad: (r: DebugProtocol.Response, n: number, s: string) => void - ): void { + public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { const path: string = this.shared.isSSH ? this.shared.sourceFileMap.toRemotePath(args.source.path) : args.source.path; this.shared.miDebugger.goto(path, args.line).then(done => { response.body = { @@ -559,20 +507,12 @@ export class MI2InferiorSession extends DebugSession { line: args.line }] }; - cb_good(response); + this.sendResponse(response); }, msg => { - cb_bad(response, 16, `Could not jump: ${msg}`); + this.sendErrorResponse(response, 16, `Could not jump: ${msg}`); }); } - public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { - this.inferiorGotoTargetsRequest( - response, args, - (r: DebugProtocol.Response) => this.sendResponse(r), - (r: DebugProtocol.Response, n: number, s: string) => this.sendErrorResponse(r, n, s) - ); - } - public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { this.sendResponse(response); } From bc6a0b85e970457aa1b4efa9e1e4779f475da4e2 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 21:49:45 +0100 Subject: [PATCH 13/26] Removed excessive whitespace --- src/mibase.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index d0b4c33..f716db6 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -462,26 +462,6 @@ export class MI2DebugSession extends MI2InferiorSession { this.sendErrorResponse(response, 18, `Could not run/continue: ${err.toString()}`); }); } - - - - - - - - - - - - - - - - - - - - protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void { if (configMap === undefined) { @@ -490,7 +470,5 @@ export class MI2DebugSession extends MI2InferiorSession { this.shared.sourceFileMap = new SourceFileMap(configMap, fallbackGDB); } } - - } From 261a4b5c3eaa7d9981236ea83d63873782146a21 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 21:55:54 +0100 Subject: [PATCH 14/26] Improved exit handling --- src/mibase.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index f716db6..846ee72 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,6 +1,6 @@ import * as DebugAdapter from 'vscode-debugadapter'; import * as Net from 'net'; -import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles, ExitedEvent } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; import { MINode } from './backend/mi_parse'; @@ -306,26 +306,26 @@ export class MI2DebugSession extends MI2InferiorSession { protected threadGroupExitedEvent(info: MINode) { let pid = this.shared.threadGroupPids.get(info.record("id")); + let exit_code = info.record("exit-code"); if (pid == this.sessionPid) { // Session has no thread group anymore. Next started thread group will be debugged by this session this.sessionPid = undefined; + this.sendEvent(new ExitedEvent(exit_code)); } this.shared.threadGroupPids.delete(info.record("id")); } protected quitEvent(info?: MINode) { - if (this.shared.threadGroupPids.size == 0) { - this.quit = true; - this.sendEvent(new TerminatedEvent()); - - if (this.serverPath) - fs.unlink(this.serverPath, (err) => { - // eslint-disable-next-line no-console - console.error("Failed to unlink debug server"); - }); - } + this.quit = true; + this.sendEvent(new ExitedEvent(0)); + + if (this.serverPath) + fs.unlink(this.serverPath, (err) => { + // eslint-disable-next-line no-console + console.error("Failed to unlink debug server"); + }); } protected launchError(err: any) { From 10c496d564e3d0d185e141830a25726821c7b4d5 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 21:58:01 +0100 Subject: [PATCH 15/26] Added multiProcess guards --- src/mibase.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mibase.ts b/src/mibase.ts index 846ee72..710c233 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -264,6 +264,10 @@ export class MI2DebugSession extends MI2InferiorSession { } protected threadGroupStartedEvent(info: MINode) { + if (!this.shared.miDebugger.multiProcess) { + return; + } + let pid = info.record("pid"); if (typeof this.sessionPid === "undefined") { @@ -305,6 +309,10 @@ export class MI2DebugSession extends MI2InferiorSession { } protected threadGroupExitedEvent(info: MINode) { + if (!this.shared.miDebugger.multiProcess) { + return; + } + let pid = this.shared.threadGroupPids.get(info.record("id")); let exit_code = info.record("exit-code"); From 22e915bce6246b1582625b9a17f4853b28c828af Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:01:43 +0100 Subject: [PATCH 16/26] Moved constructor up and formatted fields --- src/mibase.ts | 8 ++++---- src/miinferior.ts | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 710c233..ce72839 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -44,14 +44,14 @@ export class SharedState { } export class MI2DebugSession extends MI2InferiorSession { - protected initialRunCommand: RunCommand; - protected commandServer: net.Server; - protected serverPath: string; - public constructor(shared: SharedState, debuggerLinesStartAt1?: boolean, isServer?: boolean) { super(shared, debuggerLinesStartAt1, isServer); } + protected initialRunCommand: RunCommand; + protected commandServer: net.Server; + protected serverPath: string; + protected initDebugger() { this.shared.miDebugger.on("launcherror", this.launchError.bind(this)); this.shared.miDebugger.on("quit", this.quitEvent.bind(this)); diff --git a/src/miinferior.ts b/src/miinferior.ts index 6f405f1..cf3823d 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -12,8 +12,12 @@ export interface InferiorAttachRequestArguments extends DebugProtocol.AttachRequ } export class MI2InferiorSession extends DebugSession { - sessionPid: string | undefined; + constructor(shared: SharedState, debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(debuggerLinesStartAt1, isServer); + this.shared = shared; + } + protected sessionPid: string | undefined; protected useVarObjects: boolean; protected quit: boolean; protected attached: boolean; @@ -21,11 +25,6 @@ export class MI2InferiorSession extends DebugSession { protected crashed: boolean; protected shared: SharedState; - constructor(shared: SharedState, debuggerLinesStartAt1?: boolean, isServer?: boolean) { - super(debuggerLinesStartAt1, isServer); - this.shared = shared; - } - protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { // Same capabilities as GDBDebugSession response.body.supportsGotoTargetsRequest = true; From 74d6e61698474636236546a5a0628ad39146ab2e Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:05:27 +0100 Subject: [PATCH 17/26] Fixed access specifiers --- src/mibase.ts | 8 ++++---- src/miinferior.ts | 30 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index ce72839..cf38cd7 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -342,7 +342,7 @@ export class MI2DebugSession extends MI2InferiorSession { this.quitEvent(); } - public override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { if (this.attached) this.shared.miDebugger.detach(); else @@ -352,7 +352,7 @@ export class MI2DebugSession extends MI2InferiorSession { this.sendResponse(response); } - public override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { + protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { const all: Thenable<[boolean, Breakpoint]>[] = []; args.breakpoints.forEach(brk => { all.push(this.shared.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition })); @@ -372,7 +372,7 @@ export class MI2DebugSession extends MI2InferiorSession { }); } - public override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { let path = args.source.path; if (this.shared.isSSH) { // convert local path to ssh path @@ -402,7 +402,7 @@ export class MI2DebugSession extends MI2InferiorSession { }); } - public override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { const promises: Thenable[] = []; let entryPoint: string | undefined = undefined; let runToStart: boolean = false; diff --git a/src/miinferior.ts b/src/miinferior.ts index cf3823d..ccb3737 100644 --- a/src/miinferior.ts +++ b/src/miinferior.ts @@ -39,11 +39,11 @@ export class MI2InferiorSession extends DebugSession { this.sendResponse(response); } - public override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { this.sendResponse(response); } - public override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { try { if (this.useVarObjects) { let name = args.name; @@ -70,7 +70,7 @@ export class MI2InferiorSession extends DebugSession { } } - public override threadsRequest(response: DebugProtocol.ThreadsResponse): void { + protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { if (!this.shared.miDebugger) { this.sendResponse(response); return; @@ -106,7 +106,7 @@ export class MI2InferiorSession extends DebugSession { return [frameId & 0xffff, frameId >> 16]; } - public override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { this.shared.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { const ret: StackFrame[] = []; stack.forEach(element => { @@ -140,7 +140,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { const scopes = new Array(); const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); @@ -167,7 +167,7 @@ export class MI2InferiorSession extends DebugSession { this.sendResponse(response); } - public override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; const id: VariableScope | string | VariableObject | ExtendedVariable = this.shared.variableHandles.get(args.variablesReference); @@ -393,7 +393,7 @@ export class MI2InferiorSession extends DebugSession { } } - public override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { + protected override pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { this.shared.miDebugger.interrupt(args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -401,7 +401,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { this.shared.miDebugger.continue(true, args.threadId).then(done => { if (!response.hasOwnProperty("body")) { response.body = Object(); @@ -427,7 +427,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { this.shared.miDebugger.step(true, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -435,7 +435,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { + protected override stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void { this.shared.miDebugger.step(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -443,7 +443,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { + protected override stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void { this.shared.miDebugger.stepOut(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -451,7 +451,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { this.shared.miDebugger.next(false, args.threadId).then(done => { this.sendResponse(response); }, msg => { @@ -459,7 +459,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); if (args.context === "watch" || args.context === "hover") { this.shared.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { @@ -495,7 +495,7 @@ export class MI2InferiorSession extends DebugSession { } } - public override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { const path: string = this.shared.isSSH ? this.shared.sourceFileMap.toRemotePath(args.source.path) : args.source.path; this.shared.miDebugger.goto(path, args.line).then(done => { response.body = { @@ -512,7 +512,7 @@ export class MI2InferiorSession extends DebugSession { }); } - public override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { this.sendResponse(response); } } From cc77c0ea06accc725bc194193c440862aa30efd5 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:07:12 +0100 Subject: [PATCH 18/26] Remove unused variables --- src/mibase.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index cf38cd7..886a7b6 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -142,8 +142,6 @@ export class MI2DebugSession extends MI2InferiorSession { } protected handleBreakpoint(info: MINode) { - let threadId = info.record("thread-id"); - let threadPid = this.shared.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); @@ -325,7 +323,7 @@ export class MI2DebugSession extends MI2InferiorSession { this.shared.threadGroupPids.delete(info.record("id")); } - protected quitEvent(info?: MINode) { + protected quitEvent() { this.quit = true; this.sendEvent(new ExitedEvent(0)); From d6f7ea42d59b125ca07ed3155823f1f539e0094d Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:07:41 +0100 Subject: [PATCH 19/26] Commit package.json changes --- package.json | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c0a6bdc..9f9f01c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "activationEvents": [ "onCommand:code-debug.examineMemoryLocation", "onCommand:code-debug.getFileNameNoExt", - "onCommand:code-debug.getFileBasenameNoExt" + "onCommand:code-debug.getFileBasenameNoExt", + "onDebug" ], "categories": [ "Debuggers" @@ -599,6 +600,59 @@ } ] }, + { + "type": "mi-inferior", + "program": "./out/src/miinferior.js", + "runtime": "node", + "label": "GDB", + "languages": [ + "c", + "cpp", + "d", + "objective-c", + "fortran", + "fortran-modern", + "fortran90", + "fortran_free-form", + "fortran_fixed-form", + "rust", + "pascal", + "objectpascal", + "ada", + "nim", + "arm", + "asm", + "vala", + "crystal", + "kotlin", + "zig", + "riscv" + ], + "variables": { + "FileBasenameNoExt": "code-debug.getFileBasenameNoExt", + "FileNameNoExt": "code-debug.getFileNameNoExt" + }, + "configurationAttributes": { + "attach": { + "required": [ + "port" + ], + "properties": { + "port": { + "type": "number", + "description": "" + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" + } + } + } + }, + "initialConfigurations": [ ], + "configurationSnippets": [ ] + }, { "type": "lldb-mi", "program": "./out/src/lldb.js", From f1c61157c4a0fcd55d968a8b2876372d99187248 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:08:54 +0100 Subject: [PATCH 20/26] Change type to mi-inferior --- src/mibase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mibase.ts b/src/mibase.ts index 886a7b6..6692d9b 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -285,7 +285,7 @@ export class MI2DebugSession extends MI2InferiorSession { this.sendRequest('startDebugging', { request: "attach", configuration: { - type: "gdb-inferior", + type: "mi-inferior", target: info.record("pid"), name: "Child session", cwd: "${workspaceRoot}", From 6194b23521e1ea17d8c2b9303d68df6192e1dcc6 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:09:49 +0100 Subject: [PATCH 21/26] Let OS pick port --- src/mibase.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 6692d9b..a9a8568 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -236,14 +236,6 @@ export class MI2DebugSession extends MI2InferiorSession { } private openInferiorDebugServer(superiorServer: MI2DebugSession) { - function randomIntFromInterval(min: number, max: number) { // min and max included - return Math.floor(Math.random() * (max - min + 1) + min); - } - - const port = 1337 + randomIntFromInterval(1, 1000); - - console.error(`waiting for debug protocol on port ${port}`); - const server = Net.createServer((socket) => { console.error('>> accepted connection from client'); socket.on('end', () => { @@ -254,7 +246,7 @@ export class MI2DebugSession extends MI2InferiorSession { session.start(socket, socket); this.shared.mi2Inferiors.push(session); - }).listen(port); + }).listen(); this.shared.inferiorServers.push(server); From 62c4073b548b7f1fe19cf4d6c426e831bbda7788 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:15:07 +0100 Subject: [PATCH 22/26] Improved startDebugging event of children --- src/mibase.ts | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index a9a8568..020fbfd 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -270,31 +270,22 @@ export class MI2DebugSession extends MI2InferiorSession { // This makes the UI all fancy with subprocesses and threads etc. if (this.shared.threadGroupPids.size > 1) { // Open a new port for the new DebugSession to attach to - const server = this.openInferiorDebugServer(this); - const serverAddress = (server.address() as Net.AddressInfo).port; - - // Necessary until vscode-debugadapter-node supports `startDebuggingRequest` - this.sendRequest('startDebugging', { - request: "attach", - configuration: { - type: "mi-inferior", - target: info.record("pid"), - name: "Child session", - cwd: "${workspaceRoot}", - debugServer: serverAddress - } - }, 1000, () => {}); - - // this.startDebuggingRequest({ - // request: "attach", - // configuration: { - // type: "gdb-inferior", - // target: info.record("pid"), - // name: "Child session", - // cwd: "${workspaceRoot}", - // debugServer: serverAddress - // } - // }, 1000, () => {}) + const server = this.openInferiorDebugServer(this).on("listening", () => { + const serverAddress = (server.address() as Net.AddressInfo).port; + const pid = info.record("pid"); + + // Necessary until vscode-debugadapter-node supports `startDebuggingRequest` + this.sendRequest('startDebugging', { + request: "attach", + configuration: { + type: "mi-inferior", + target: pid, + name: `Child (${pid})`, + cwd: "${workspaceRoot}", + debugServer: serverAddress + } + }, 1000, () => {}); + }) } } From 7ada83de2307a28507ea713bf9d3e006366ff876 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:20:42 +0100 Subject: [PATCH 23/26] Removed required attach attributes --- package.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/package.json b/package.json index 9f9f01c..87c8f56 100644 --- a/package.json +++ b/package.json @@ -632,24 +632,6 @@ "FileBasenameNoExt": "code-debug.getFileBasenameNoExt", "FileNameNoExt": "code-debug.getFileNameNoExt" }, - "configurationAttributes": { - "attach": { - "required": [ - "port" - ], - "properties": { - "port": { - "type": "number", - "description": "" - }, - "registerLimit": { - "type": "string", - "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", - "default": "" - } - } - } - }, "initialConfigurations": [ ], "configurationSnippets": [ ] }, From 4bcd7f7174b14fc5b2017202f3ab5e98ac894d63 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:39:14 +0100 Subject: [PATCH 24/26] Introduce sendEventToDebugSession and use it --- src/mibase.ts | 60 +++++++++++++++------------------------------------ 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 020fbfd..348e642 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,6 +1,6 @@ import * as DebugAdapter from 'vscode-debugadapter'; import * as Net from 'net'; -import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles, ExitedEvent } from 'vscode-debugadapter'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles, ExitedEvent, Event } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; import { MINode } from './backend/mi_parse'; @@ -141,19 +141,23 @@ export class MI2DebugSession extends MI2InferiorSession { this.sendEvent(new OutputEvent(msg, type)); } + protected sendEventToDebugSession(pid: string, event: Event) { + if (pid == this.sessionPid) { + this.sendEvent(event); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (pid == inferior.sessionPid) inferior.sendEvent(event) + }); + } + } + protected handleBreakpoint(info: MINode) { let threadPid = this.shared.threadToPid.get(parseInt(info.record("thread-id"), 10)); const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - if (threadPid == this.sessionPid) { - this.sendEvent(event); - } else { - this.shared.mi2Inferiors.forEach(inferior => { - if (threadPid == inferior.sessionPid) inferior.sendEvent(event) - }); - } + this.sendEventToDebugSession(threadPid, event); } protected handleBreak(info?: MINode) { @@ -162,13 +166,7 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") === "all" : true; - if (threadPid == this.sessionPid) { - this.sendEvent(event); - } else { - this.shared.mi2Inferiors.forEach(inferior => { - if (threadPid == inferior.sessionPid) inferior.sendEvent(event) - }); - } + this.sendEventToDebugSession(threadPid, event); } protected handlePause(info: MINode) { @@ -177,13 +175,7 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("user request", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - if (threadPid == this.sessionPid) { - this.sendEvent(event); - } else { - this.shared.mi2Inferiors.forEach(inferior => { - if (threadPid == inferior.sessionPid) inferior.sendEvent(event) - }); - } + this.sendEventToDebugSession(threadPid, event); } protected stopEvent(info: MINode) { @@ -195,13 +187,7 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("exception", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - if (threadPid == this.sessionPid) { - this.sendEvent(event); - } else { - this.shared.mi2Inferiors.forEach(inferior => { - if (threadPid == inferior.sessionPid) inferior.sendEvent(event) - }); - } + this.sendEventToDebugSession(threadPid, event); } } @@ -211,13 +197,7 @@ export class MI2DebugSession extends MI2InferiorSession { let threadPid = this.shared.threadGroupPids.get(info.record("group-id")); this.shared.threadToPid.set(threadId, threadPid); - if (threadPid == this.sessionPid) { - this.sendEvent(new ThreadEvent("started", threadId)); - } else { - this.shared.mi2Inferiors.forEach(inferior => { - if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("started", threadId)); - }); - } + this.sendEventToDebugSession(threadPid, new ThreadEvent("started", threadId)); } protected threadExitedEvent(info: MINode) { @@ -226,13 +206,7 @@ export class MI2DebugSession extends MI2InferiorSession { let threadPid = this.shared.threadGroupPids.get(info.record("group-id")); this.shared.threadToPid.delete(info.record("group-id")); - if (threadPid == this.sessionPid) { - this.sendEvent(new ThreadEvent("exited", threadId)); - } else { - this.shared.mi2Inferiors.forEach(inferior => { - if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("exited", threadId)); - }); - } + this.sendEventToDebugSession(threadPid, new ThreadEvent("exited", threadId)); } private openInferiorDebugServer(superiorServer: MI2DebugSession) { From 3a138041cc59eb2062c913c782a0f38c7a7822d5 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Sun, 23 Nov 2025 22:40:53 +0100 Subject: [PATCH 25/26] Add program exit logging statement --- src/backend/mi2/mi2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 7715dd7..50333a6 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -472,6 +472,7 @@ export class MI2 extends EventEmitter implements IBackend { this.emit("exited-normally", parsed); break; case "exited": // exit with error code != 0 + this.log("stderr", "Program exited with code " + parsed.record("exit-code")); this.emit("exited-normally", parsed); break; // case "exited-signalled": // consider handling that explicit possible From 7b26d45c2b8aa9d11388dc8e68eef6e8cd34ac40 Mon Sep 17 00:00:00 2001 From: Julian van Doorn Date: Mon, 24 Nov 2025 22:32:28 +0100 Subject: [PATCH 26/26] Improved subprocess exit handling --- src/mibase.ts | 81 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 348e642..fdb49cf 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -54,8 +54,6 @@ export class MI2DebugSession extends MI2InferiorSession { protected initDebugger() { this.shared.miDebugger.on("launcherror", this.launchError.bind(this)); - this.shared.miDebugger.on("quit", this.quitEvent.bind(this)); - this.shared.miDebugger.on("exited-normally", this.quitEvent.bind(this)); this.shared.miDebugger.on("stopped", this.stopEvent.bind(this)); this.shared.miDebugger.on("msg", this.handleMsg.bind(this)); this.shared.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this)); @@ -69,7 +67,7 @@ export class MI2DebugSession extends MI2InferiorSession { this.shared.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent()))); this.shared.miDebugger.on("thread-group-started", this.threadGroupStartedEvent.bind(this)); this.shared.miDebugger.on("thread-group-exited", this.threadGroupExitedEvent.bind(this)); - this.sendEvent(new InitializedEvent()); + try { this.commandServer = net.createServer(c => { c.on("data", data => { @@ -157,7 +155,13 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.sendEventToDebugSession(threadPid, event); + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } protected handleBreak(info?: MINode) { @@ -166,7 +170,13 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") === "all" : true; - this.sendEventToDebugSession(threadPid, event); + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } protected handlePause(info: MINode) { @@ -175,7 +185,13 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("user request", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.sendEventToDebugSession(threadPid, event); + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } protected stopEvent(info: MINode) { @@ -187,7 +203,13 @@ export class MI2DebugSession extends MI2InferiorSession { const event = new StoppedEvent("exception", parseInt(info.record("thread-id"))); (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") === "all"; - this.sendEventToDebugSession(threadPid, event); + if (threadPid == this.sessionPid) { + this.sendEvent(event); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(event) + }); + } } } @@ -197,7 +219,13 @@ export class MI2DebugSession extends MI2InferiorSession { let threadPid = this.shared.threadGroupPids.get(info.record("group-id")); this.shared.threadToPid.set(threadId, threadPid); - this.sendEventToDebugSession(threadPid, new ThreadEvent("started", threadId)); + if (threadPid == this.sessionPid) { + this.sendEvent(new ThreadEvent("started", threadId)); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("started", threadId)); + }); + } } protected threadExitedEvent(info: MINode) { @@ -206,7 +234,13 @@ export class MI2DebugSession extends MI2InferiorSession { let threadPid = this.shared.threadGroupPids.get(info.record("group-id")); this.shared.threadToPid.delete(info.record("group-id")); - this.sendEventToDebugSession(threadPid, new ThreadEvent("exited", threadId)); + if (threadPid == this.sessionPid) { + this.sendEvent(new ThreadEvent("exited", threadId)); + } else { + this.shared.mi2Inferiors.forEach(inferior => { + if (threadPid == inferior.sessionPid) inferior.sendEvent(new ThreadEvent("exited", threadId)); + }); + } } private openInferiorDebugServer(superiorServer: MI2DebugSession) { @@ -268,33 +302,38 @@ export class MI2DebugSession extends MI2InferiorSession { return; } - let pid = this.shared.threadGroupPids.get(info.record("id")); - let exit_code = info.record("exit-code"); + const threadGroupid = info.record("id"); + const pid = this.shared.threadGroupPids.get(threadGroupid); + const exit_code = info.record("exit-code"); - if (pid == this.sessionPid) { - // Session has no thread group anymore. Next started thread group will be debugged by this session - this.sessionPid = undefined; - this.sendEvent(new ExitedEvent(exit_code)); + // Only if the exit_code is defined, the process has exited. + // If exit_code is undefined it is still running (even if it has no threads). + // This happens, for instance, when ld-linux has finished dynamic loading. + if (typeof exit_code != "undefined") { + this.quitEvent(parseInt(pid, 10), parseInt(exit_code, 10)) } this.shared.threadGroupPids.delete(info.record("id")); } - protected quitEvent() { + protected quitEvent(pid: number, exit_code: number) { this.quit = true; - this.sendEvent(new ExitedEvent(0)); - if (this.serverPath) + this.sendEventToDebugSession(pid.toString(), new ExitedEvent(exit_code)); + this.sendEventToDebugSession(pid.toString(), new TerminatedEvent(false)); + + if (this.serverPath) { fs.unlink(this.serverPath, (err) => { - // eslint-disable-next-line no-console - console.error("Failed to unlink debug server"); + // eslint-disable-next-line no-console + console.error("Failed to unlink debug server"); }); + } } protected launchError(err: any) { this.handleMsg("stderr", "Could not start debugger process, does the program exist in filesystem?\n"); this.handleMsg("stderr", err.toString() + "\n"); - this.quitEvent(); + this.sendEvent(new TerminatedEvent(false)); } protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {