From 00aafd4f1d837957a852f5f1cf9c65e88fa96316 Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Wed, 21 Feb 2018 16:36:34 +0000 Subject: [PATCH] Add multi-process support --- package.json | 5 ++++ src/backend/backend.ts | 2 +- src/backend/mi2/mi2.ts | 26 +++++++++++++++----- src/gdb.ts | 4 ++++ src/mibase.ts | 54 ++++++++++++++++++++++++++++++++---------- 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index d84b98ed..1ea81b00 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,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 0f35d063..f580e9cb 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -56,7 +56,7 @@ export interface IBackend { start(): Thenable; stop(); detach(); - 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 25dc3905..51854a7e 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -197,9 +197,18 @@ export class MI2 extends EventEmitter implements IBackend { cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); if (this.prettyPrint) cmds.push(this.sendCommand("enable-pretty-printing")); + 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 (let cmd of this.extraCommands) { cmds.push(this.sendCommand(cmd)); - } return cmds; } @@ -357,7 +366,7 @@ export class MI2 extends EventEmitter implements IBackend { else if (reason == "exited-normally") this.emit("exited-normally", parsed); else if (reason == "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); } else { this.log("console", "Not implemented stop reason (assuming exception): " + reason); @@ -370,6 +379,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); } } } @@ -431,21 +444,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); }); @@ -794,6 +807,7 @@ export class MI2 extends EventEmitter implements IBackend { } prettyPrint: boolean = true; + multiProcess: boolean = false; printCalls: boolean; debugOutput: boolean; public procEnv: any; diff --git a/src/gdb.ts b/src/gdb.ts index c588801c..04621535 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -17,6 +17,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum ssh: SSHArguments; valuesFormatting: ValuesFormattingMode; printCalls: boolean; + multiProcess: boolean; showDevDebugOutput: boolean; } @@ -33,6 +34,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum ssh: SSHArguments; valuesFormatting: ValuesFormattingMode; printCalls: boolean; + multiProcess: boolean; showDevDebugOutput: boolean; } @@ -62,6 +64,7 @@ class GDBDebugSession extends MI2DebugSession { this.debugReady = false; this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.multiProcess = !!args.multiProcess; this.miDebugger.debugOutput = !!args.showDevDebugOutput; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) @@ -130,6 +133,7 @@ class GDBDebugSession extends MI2DebugSession { this.debugReady = false; this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.multiProcess = !!args.multiProcess; this.miDebugger.debugOutput = !!args.showDevDebugOutput; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) diff --git a/src/mibase.ts b/src/mibase.ts index 354f630d..988a533b 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -38,6 +38,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); @@ -56,6 +58,8 @@ export class MI2DebugSession extends DebugSession { 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.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 => { @@ -140,21 +144,39 @@ 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 quitEvent() { - this.quit = true; - this.sendEvent(new TerminatedEvent()); + protected threadGroupStartedEvent(info: MINode) { + this.threadGroupPids.set(info.record("id"), info.record("pid")); + } - if (this.serverPath) - fs.unlink(this.serverPath, (err) => { - console.error("Failed to unlink debug server"); - }); + protected threadGroupExitedEvent(info: MINode) { + this.threadGroupPids.delete(info.record("id")); + } + + protected quitEvent(info?: MINode) { + if (this.threadGroupPids.size == 0) { + this.quit = true; + this.sendEvent(new TerminatedEvent()); + if (this.serverPath) + fs.unlink(this.serverPath, (err) => { + console.error("Failed to unlink debug server"); + }); + } } protected launchError(err: any) { @@ -274,8 +296,14 @@ export class MI2DebugSession extends DebugSession { threads: [] }; for (const thread of threads) { - let threadName = thread.name || thread.targetId || ""; - response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName)); + let threadName = thread.name || thread.targetId || ""; + if (this.threadGroupPids.size > 1) { + let pid = this.threadToPid.get(thread.id); + threadName = `(${pid}) ${thread.id}:${threadName}`; + } else { + threadName = `${thread.id}:${threadName}`; + } + response.body.threads.push(new Thread(thread.id, threadName)); } this.sendResponse(response); }); @@ -561,7 +589,7 @@ export class MI2DebugSession extends DebugSession { } } - protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void { this.miDebugger.interrupt().then(done => { this.sendResponse(response); }, msg => { @@ -571,6 +599,7 @@ export class MI2DebugSession extends DebugSession { protected 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}`); @@ -579,6 +608,7 @@ export class MI2DebugSession extends DebugSession { protected 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}`);