Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5ccec73
Add multi-process support
LeszekSwirski Feb 21, 2018
92c61ff
Improvements to multi-processing
JulianvDoorn Nov 21, 2025
96a1909
POC for multiple debug sessions
JulianvDoorn Nov 22, 2025
8fd02d8
Fixed passing of events. Now it properly shows two stack traces in su…
JulianvDoorn Nov 22, 2025
d81d63d
Reuse threadRequest code between superior and inferior
JulianvDoorn Nov 22, 2025
99d76f1
Stability improvements
JulianvDoorn Nov 23, 2025
4df215d
Remove dependency on vscode-debugadapter-node changes
JulianvDoorn Nov 23, 2025
1807c8d
Fixed improved thread-specific commands
JulianvDoorn Nov 23, 2025
a67e92e
More improvements
JulianvDoorn Nov 23, 2025
4f08c34
Removed logging
JulianvDoorn Nov 23, 2025
4198fde
Large refactoring
JulianvDoorn Nov 23, 2025
ccc295c
Cleaned up miinferior.ts
JulianvDoorn Nov 23, 2025
bc6a0b8
Removed excessive whitespace
JulianvDoorn Nov 23, 2025
261a4b5
Improved exit handling
JulianvDoorn Nov 23, 2025
10c496d
Added multiProcess guards
JulianvDoorn Nov 23, 2025
22e915b
Moved constructor up and formatted fields
JulianvDoorn Nov 23, 2025
74d6e61
Fixed access specifiers
JulianvDoorn Nov 23, 2025
cc77c0e
Remove unused variables
JulianvDoorn Nov 23, 2025
d6f7ea4
Commit package.json changes
JulianvDoorn Nov 23, 2025
f1c6115
Change type to mi-inferior
JulianvDoorn Nov 23, 2025
6194b23
Let OS pick port
JulianvDoorn Nov 23, 2025
62c4073
Improved startDebugging event of children
JulianvDoorn Nov 23, 2025
7ada83d
Removed required attach attributes
JulianvDoorn Nov 23, 2025
4bcd7f7
Introduce sendEventToDebugSession and use it
JulianvDoorn Nov 23, 2025
3a13804
Add program exit logging statement
JulianvDoorn Nov 23, 2025
7b26d45
Improved subprocess exit handling
JulianvDoorn Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"activationEvents": [
"onCommand:code-debug.examineMemoryLocation",
"onCommand:code-debug.getFileNameNoExt",
"onCommand:code-debug.getFileBasenameNoExt"
"onCommand:code-debug.getFileBasenameNoExt",
"onDebug"
],
"categories": [
"Debuggers"
Expand Down Expand Up @@ -204,6 +205,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",
Expand Down Expand Up @@ -594,6 +600,41 @@
}
]
},
{
"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"
},
"initialConfigurations": [ ],
"configurationSnippets": [ ]
},
{
"type": "lldb-mi",
"program": "./out/src/lldb.js",
Expand Down
4 changes: 2 additions & 2 deletions src/backend/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export interface IBackend {
start(runToStart: boolean): Thenable<boolean>;
stop(): void;
detach(): void;
interrupt(): Thenable<boolean>;
continue(): Thenable<boolean>;
interrupt(threadId?: number): Thenable<boolean>;
continue(reverse?: boolean, threadId?: number): Thenable<boolean>;
next(): Thenable<boolean>;
Copy link
Author

@JulianvDoorn JulianvDoorn Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface does not correspond with the actual implementation

step(): Thenable<boolean>;
stepOut(): Thenable<boolean>;
Expand Down
41 changes: 28 additions & 13 deletions src/backend/mi2/mi2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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));
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -546,51 +560,51 @@ export class MI2 extends EventEmitter implements IBackend {
this.sendRaw("-target-detach");
}

interrupt(): Thenable<boolean> {
interrupt(threadId?: number): Thenable<boolean> {
if (trace)
this.log("stderr", "interrupt");
this.log("stderr", "interrupt" + (threadId ? " --thread " + threadId : ""));
return new Promise((resolve, reject) => {
this.sendCommand("exec-interrupt").then((info) => {
this.sendCommand("exec-interrupt" + (threadId ? " --thread " + threadId : "")).then((info) => {
resolve(info.resultRecords.resultClass === "done");
}, reject);
});
}

continue(reverse: boolean = false): Thenable<boolean> {
continue(reverse: boolean = false, threadId?: number): Thenable<boolean> {
if (trace)
this.log("stderr", "continue");
this.log("stderr", "continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : ""));
return new Promise((resolve, reject) => {
this.sendCommand("exec-continue" + (reverse ? " --reverse" : "")).then((info) => {
this.sendCommand("exec-continue" + (reverse ? " --reverse" : "") + (threadId ? " --thread " + threadId : "")).then((info) => {
resolve(info.resultRecords.resultClass === "running");
}, reject);
});
}

next(reverse: boolean = false): Thenable<boolean> {
next(reverse: boolean = false, threadId?: number): Thenable<boolean> {
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 " + threadId : "")).then((info) => {
resolve(info.resultRecords.resultClass === "running");
}, reject);
});
}

step(reverse: boolean = false): Thenable<boolean> {
step(reverse: boolean = false, threadId?: number): Thenable<boolean> {
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 " + threadId : "")).then((info) => {
resolve(info.resultRecords.resultClass === "running");
}, reject);
});
}

stepOut(reverse: boolean = false): Thenable<boolean> {
stepOut(reverse: boolean = false, threadId?: number): Thenable<boolean> {
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 " + threadId : "")).then((info) => {
resolve(info.resultRecords.resultClass === "running");
}, reject);
});
Expand Down Expand Up @@ -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[];
Expand Down
56 changes: 32 additions & 24 deletions src/gdb.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -19,6 +19,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum
valuesFormatting: ValuesFormattingMode;
frameFilters: boolean;
printCalls: boolean;
multiProcess: boolean;
showDevDebugOutput: boolean;
registerLimit: string;
}
Expand All @@ -39,11 +40,16 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum
valuesFormatting: ValuesFormattingMode;
frameFilters: boolean;
printCalls: boolean;
multiProcess: boolean;
showDevDebugOutput: boolean;
registerLimit: string;
}

class GDBDebugSession extends MI2DebugSession {
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;
Expand All @@ -63,21 +69,22 @@ 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.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;
Expand All @@ -89,15 +96,15 @@ 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()}`);
Expand All @@ -111,19 +118,20 @@ 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.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;
Expand All @@ -135,22 +143,22 @@ 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()}`);
Expand All @@ -163,7 +171,7 @@ 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]) + "\"");
});
}
}
Expand Down
Loading