From ab0a3bb4be3b94168fa5602b761c31dffdd2397b Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Thu, 1 Feb 2018 10:53:26 +0000 Subject: [PATCH 1/4] Support multi-threading --- src/backend/backend.ts | 14 +++-- src/backend/gdb_expansion.ts | 19 ++++-- src/backend/mi2/mi2.ts | 119 ++++++++++++++++++++++------------- src/backend/mi2/mi2mago.ts | 2 +- src/backend/mi_parse.ts | 4 +- src/mago.ts | 2 +- src/mibase.ts | 103 ++++++++++++++++++++---------- 7 files changed, 173 insertions(+), 90 deletions(-) diff --git a/src/backend/backend.ts b/src/backend/backend.ts index 1a04c7bc..668551e8 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -11,6 +11,11 @@ export interface Breakpoint { countCondition?: string; } +export interface Thread { + id: number; + name: string; +} + export interface Stack { level: number; address: string; @@ -58,9 +63,10 @@ export interface IBackend { addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>; removeBreakPoint(breakpoint: Breakpoint): Thenable; clearBreakPoints(): Thenable; - getStack(maxLevels: number): Thenable; + getThreads(): Thenable; + getStack(maxLevels: number, thread: number): Thenable; getStackVariables(thread: number, frame: number): Thenable; - evalExpression(name: string): Thenable; + evalExpression(name: string, thread: number, frame: number): Thenable; isReady(): boolean; changeVariable(name: string, rawValue: string): Thenable; examineMemory(from: number, to: number): Thenable; @@ -114,12 +120,8 @@ export class VariableObject { evaluateName: this.name, value: (this.value === void 0) ? "" : this.value, type: this.type, - // kind: this.displayhint, variablesReference: this.id }; - if (this.displayhint) { - res.kind = this.displayhint; - } return res; } } diff --git a/src/backend/gdb_expansion.ts b/src/backend/gdb_expansion.ts index 8528dffa..a5288a4d 100644 --- a/src/backend/gdb_expansion.ts +++ b/src/backend/gdb_expansion.ts @@ -5,6 +5,7 @@ const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/; const errorRegex = /^\<.+?\>/; const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/; const referenceRegex = /^0x[0-9a-fA-F]+/; +const cppReferenceRegex = /^@0x[0-9a-fA-F]+/; const nullpointerRegex = /^0x0+\b/; const charRegex = /^(\d+) ['"]/; const numberRegex = /^\d+(\.\d+)?/; @@ -168,6 +169,10 @@ export function expandValue(variableCreate: Function, value: string, root: strin primitive = "*" + match[0]; value = value.substr(match[0].length).trim(); } + else if (match = cppReferenceRegex.exec(value)) { + primitive = match[0]; + value = value.substr(match[0].length).trim(); + } else if (match = charRegex.exec(value)) { primitive = match[1]; value = value.substr(match[0].length - 1); @@ -222,19 +227,21 @@ export function expandValue(variableCreate: Function, value: string, root: strin ref = variableCreate(val); val = "Object"; } - if (typeof val == "string" && val.startsWith("*0x")) { - if (extra && MINode.valueOf(extra, "arg") == "1") - { + else if (typeof val == "string" && val.startsWith("*0x")) { + if (extra && MINode.valueOf(extra, "arg") == "1") { ref = variableCreate(getNamespace("*(" + name), { arg: true }); val = ""; } - else - { + else { ref = variableCreate(getNamespace("*" + name)); val = "Object@" + val; } } - if (typeof val == "string" && val.startsWith("<...>")) { + else if (typeof val == "string" && val.startsWith("@0x")) { + ref = variableCreate(getNamespace("*&" + name.substr)); + val = "Ref" + val; + } + else if (typeof val == "string" && val.startsWith("<...>")) { ref = variableCreate(getNamespace(name)); val = "..."; } diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 944aeb85..61397b81 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -1,4 +1,4 @@ -import { Breakpoint, IBackend, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend" +import { Breakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend" import * as ChildProcess from "child_process" import { EventEmitter } from "events" import { parseMI, MINode } from '../mi_parse'; @@ -365,6 +365,12 @@ export class MI2 extends EventEmitter implements IBackend { } } else this.log("log", JSON.stringify(parsed)); + } else if (record.type == "notify") { + if (record.asyncClass == "thread-created") { + this.emit("thread-created", parsed); + } else if (record.asyncClass == "thread-exited") { + this.emit("thread-exited", parsed); + } } } }); @@ -584,39 +590,55 @@ export class MI2 extends EventEmitter implements IBackend { }); } - getStack(maxLevels: number): Thenable { - if (trace) - this.log("stderr", "getStack"); - return new Promise((resolve, reject) => { - let command = "stack-list-frames"; - if (maxLevels) { - command += " 0 " + maxLevels; - } - this.sendCommand(command).then((result) => { - let stack = result.result("stack"); - let ret: Stack[] = []; - stack.forEach(element => { - let level = MINode.valueOf(element, "@frame.level"); - let addr = MINode.valueOf(element, "@frame.addr"); - let func = MINode.valueOf(element, "@frame.func"); - let filename = MINode.valueOf(element, "@frame.file"); - let file = MINode.valueOf(element, "@frame.fullname"); - let line = 0; - let lnstr = MINode.valueOf(element, "@frame.line"); - if (lnstr) - line = parseInt(lnstr); - let from = parseInt(MINode.valueOf(element, "@frame.from")); - ret.push({ - address: addr, - fileName: filename, - file: file, - function: func || from, - level: level, - line: line - }); - }); - resolve(ret); - }, reject); + async getThreads(): Promise { + if (trace) this.log("stderr", "getThreads"); + + let command = "thread-info"; + let result = await this.sendCommand(command); + let threads = result.result("threads"); + let ret: Thread[] = []; + return threads.map(element => { + let id = parseInt(MINode.valueOf(element, "id")); + let name = MINode.valueOf(element, "name") + ""; + return { + id, + name + }; + }); + } + + async getStack(maxLevels: number, thread: number): Promise { + if (trace) this.log("stderr", "getStack"); + + let command = "stack-list-frames"; + if (thread != 0) { + command += ` --thread ${thread}`; + } + if (maxLevels) { + command += " 0 " + maxLevels; + } + let result = await this.sendCommand(command); + let stack = result.result("stack"); + let ret: Stack[] = []; + return stack.map(element => { + let level = MINode.valueOf(element, "@frame.level"); + let addr = MINode.valueOf(element, "@frame.addr"); + let func = MINode.valueOf(element, "@frame.func"); + let filename = MINode.valueOf(element, "@frame.file"); + let file = MINode.valueOf(element, "@frame.fullname"); + let line = 0; + let lnstr = MINode.valueOf(element, "@frame.line"); + if (lnstr) + line = parseInt(lnstr); + let from = parseInt(MINode.valueOf(element, "@frame.from")); + return { + address: addr, + fileName: filename, + file: file, + function: func || from, + level: level, + line: line + }; }); } @@ -651,14 +673,17 @@ export class MI2 extends EventEmitter implements IBackend { }); } - evalExpression(name: string): Thenable { + async evalExpression(name: string, thread: number, frame: number): Promise { if (trace) this.log("stderr", "evalExpression"); - return new Promise((resolve, reject) => { - this.sendCommand("data-evaluate-expression " + name).then((result) => { - resolve(result); - }, reject); - }); + + let command = "data-evaluate-expression "; + if (thread != 0) { + command += `--thread ${thread} --frame ${frame} `; + } + command += name; + + return await this.sendCommand(command); } async varCreate(expression: string, name: string = "-"): Promise { @@ -704,13 +729,12 @@ export class MI2 extends EventEmitter implements IBackend { this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n")); } - sendUserInput(command: string): Thenable { + sendUserInput(command: string, threadId: number = 0, frameLevel: number = 0): Thenable { if (command.startsWith("-")) { return this.sendCommand(command.substr(1)); } else { - this.sendRaw(command); - return Promise.resolve(undefined); + return this.sendCliCommand(command, threadId, frameLevel); } } @@ -723,6 +747,15 @@ export class MI2 extends EventEmitter implements IBackend { this.process.stdin.write(raw + "\n"); } + async sendCliCommand(command: string, threadId: number = 0, frameLevel: number = 0) { + let mi_command = "interpreter-exec "; + if (threadId != 0) { + mi_command += `--thread ${threadId} --frame ${frameLevel} `; + } + mi_command += `console "${command.replace(/[\\"']/g, '\\$&')}"`; + await this.sendCommand(mi_command); + } + sendCommand(command: string, suppressFailure: boolean = false): Thenable { let sel = this.currentToken++; return new Promise((resolve, reject) => { diff --git a/src/backend/mi2/mi2mago.ts b/src/backend/mi2/mi2mago.ts index 9e304cc4..c9aad349 100644 --- a/src/backend/mi2/mi2mago.ts +++ b/src/backend/mi2/mi2mago.ts @@ -3,7 +3,7 @@ import { Stack } from "../backend" import { MINode } from "../mi_parse" export class MI2_Mago extends MI2_LLDB { - getStack(maxLevels: number): Thenable { + getStack(maxLevels: number, thread: number): Promise { return new Promise((resolve, reject) => { let command = "stack-list-frames"; this.sendCommand(command).then((result) => { diff --git a/src/backend/mi_parse.ts b/src/backend/mi_parse.ts index 594ddf38..91163156 100644 --- a/src/backend/mi_parse.ts +++ b/src/backend/mi_parse.ts @@ -205,8 +205,10 @@ export function parseMI(output: string): MINode { let oldContent = output; let canBeValueList = output[0] == '['; output = output.substr(1); - if (output[0] == '}' || output[0] == ']') + if (output[0] == '}' || output[0] == ']') { + output = output.substr(1); // ] or } return []; + } if (canBeValueList) { let value = parseValue(); if (value) { // is value list diff --git a/src/mago.ts b/src/mago.ts index 2aca54cb..c796276a 100644 --- a/src/mago.ts +++ b/src/mago.ts @@ -32,7 +32,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum class MagoDebugSession extends MI2DebugSession { public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { - super(debuggerLinesStartAt1, isServer, 0); + super(debuggerLinesStartAt1, isServer); } protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { diff --git a/src/mibase.ts b/src/mibase.ts index a961a508..68387ded 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,4 +1,5 @@ -import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import * as DebugAdapter from 'vscode-debugadapter'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; import { MINode } from './backend/mi_parse'; @@ -19,7 +20,7 @@ class ExtendedVariable { } const STACK_HANDLES_START = 1000; -const VAR_HANDLES_START = 2000; +const VAR_HANDLES_START = 512 * 256 + 1000; export class MI2DebugSession extends DebugSession { protected variableHandles = new Handles(VAR_HANDLES_START); @@ -35,12 +36,10 @@ export class MI2DebugSession extends DebugSession { protected crashed: boolean; protected debugReady: boolean; protected miDebugger: MI2; - protected threadID: number = 1; protected commandServer: net.Server; - public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false, threadID: number = 1) { + public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { super(debuggerLinesStartAt1, isServer); - this.threadID = threadID; } protected initDebugger() { @@ -53,6 +52,8 @@ export class MI2DebugSession extends DebugSession { this.miDebugger.on("step-end", this.handleBreak.bind(this)); this.miDebugger.on("step-out-end", 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.sendEvent(new InitializedEvent()); try { this.commandServer = net.createServer(c => { @@ -109,22 +110,39 @@ export class MI2DebugSession extends DebugSession { } protected handleBreakpoint(info: MINode) { - this.sendEvent(new StoppedEvent("breakpoint", this.threadID)); + let event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); } protected handleBreak(info: MINode) { - this.sendEvent(new StoppedEvent("step", this.threadID)); + let event = new StoppedEvent("step", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); } protected handlePause(info: MINode) { - this.sendEvent(new StoppedEvent("user request", this.threadID)); + let event = new StoppedEvent("user request", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); } protected stopEvent(info: MINode) { if (!this.started) this.crashed = true; - if (!this.quit) - this.sendEvent(new StoppedEvent("exception", this.threadID)); + if (!this.quit) { + let event = new StoppedEvent("exception", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); + } + } + + protected threadCreatedEvent(info: MINode) { + this.sendEvent(new ThreadEvent("started", info.record("id"))); + } + + protected threadExitedEvent(info: MINode) { + this.sendEvent(new ThreadEvent("exited", info.record("id"))); } protected quitEvent() { @@ -211,15 +229,14 @@ export class MI2DebugSession extends DebugSession { path = relative(this.trimCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/")); path = resolve(this.switchCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/")); } - let all = []; - args.breakpoints.forEach(brk => { - all.push(this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition })); + let all = args.breakpoints.map(brk => { + return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition }); }); Promise.all(all).then(brkpoints => { let finalBrks = []; brkpoints.forEach(brkp => { if (brkp[0]) - finalBrks.push({ line: brkp[1].line }); + finalBrks.push(new DebugAdapter.Breakpoint(true, brkp[1].line)); }); response.body = { breakpoints: finalBrks @@ -239,18 +256,31 @@ export class MI2DebugSession extends DebugSession { } protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { - response.body = { - threads: [ - new Thread(this.threadID, "Thread 1") - ] - }; - this.sendResponse(response); + this.miDebugger.getThreads().then( + threads => { + response.body = { + threads: [] + }; + for (const thread of threads) { + response.body.threads.push(new Thread(thread.id, thread.id + ":" + thread.name)); + } + this.sendResponse(response); + }); + } + + // Supports 256 threads. + protected threadAndLevelToFrameId(threadId: number, level: number) { + return level << 8 | threadId; + } + protected frameIdToThreadAndLevel(frameId: number) { + return [frameId & 0xff, frameId >> 8]; } protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - this.miDebugger.getStack(args.levels).then(stack => { + this.miDebugger.getStack(args.levels, args.threadId).then(stack => { let ret: StackFrame[] = []; stack.forEach(element => { + let source = null; let file = element.file; if (file) { if (this.isSSH) { @@ -262,10 +292,15 @@ export class MI2DebugSession extends DebugSession { file = file[10] + ":" + file.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt } } - ret.push(new StackFrame(element.level, element.function + "@" + element.address, new Source(element.fileName, file), element.line, 0)); + source = new Source(element.fileName, file); } - else - ret.push(new StackFrame(element.level, element.function + "@" + element.address, null, element.line, 0)); + + ret.push(new StackFrame( + this.threadAndLevelToFrameId(args.threadId, element.level), + element.function + "@" + element.address, + source, + element.line, + 0)); }); response.body = { stackFrames: ret @@ -331,11 +366,12 @@ export class MI2DebugSession extends DebugSession { if (typeof id == "number") { let stack: Variable[]; try { - stack = await this.miDebugger.getStackVariables(this.threadID, id); + let [threadId, level] = this.frameIdToThreadAndLevel(id); + stack = await this.miDebugger.getStackVariables(threadId, level); for (const variable of stack) { if (this.useVarObjects) { try { - let varObjName = `var_${variable.name}`; + let varObjName = `var_${id}_${variable.name}`; let varObj: VariableObject; try { const changes = await this.miDebugger.varUpdate(varObjName); @@ -406,7 +442,8 @@ export class MI2DebugSession extends DebugSession { // Variable members let variable; try { - variable = await this.miDebugger.evalExpression(JSON.stringify(id)); + // TODO: this evals on an (effectively) unknown thread for multithreaded programs. + variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0); try { let expanded = expandValue(createVariable, variable.result("value"), id, variable); if (!expanded) { @@ -469,7 +506,8 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); }; let addOne = async () => { - const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`)); + // TODO: this evals on an (effectively) unknown thread for multithreaded programs. + const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0); try { let expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable); if (!expanded) { @@ -589,8 +627,9 @@ export class MI2DebugSession extends DebugSession { } protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { - if (args.context == "watch" || args.context == "hover") - this.miDebugger.evalExpression(args.expression).then((res) => { + let [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") @@ -599,8 +638,8 @@ export class MI2DebugSession extends DebugSession { }, msg => { this.sendErrorResponse(response, 7, msg.toString()); }); - else { - this.miDebugger.sendUserInput(args.expression).then(output => { + } else { + this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => { if (typeof output == "undefined") response.body = { result: "", From 596674fb594c0d02bca7a5341631a5fb741a3a42 Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Fri, 2 Feb 2018 11:41:39 +0000 Subject: [PATCH 2/4] Add a test --- test/mi_parse.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/mi_parse.test.ts b/test/mi_parse.test.ts index bf290b5b..e7587db0 100644 --- a/test/mi_parse.test.ts +++ b/test/mi_parse.test.ts @@ -178,4 +178,14 @@ suite("MI Parse", () => { let result = parsed.result('register-names'); assert.deepEqual(result, ["r0", "pc", "", "xpsr", "", "control"]); }); -}); \ No newline at end of file + test("empty array values", () => { + let parsed = parseMI(`15^done,foo={x=[],y="y"}`); + assert.deepEqual(parsed.result('foo.x'), []); + assert.equal(parsed.result('foo.y'), "y"); + }); + test("empty object values", () => { + let parsed = parseMI(`15^done,foo={x={},y="y"}`); + assert.deepEqual(parsed.result('foo.x'), {}); + assert.equal(parsed.result('foo.y'), "y"); + }); +}); From a7b89c3569f8e3be4df877511900f6943bc799d9 Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Wed, 21 Feb 2018 11:40:38 +0000 Subject: [PATCH 3/4] Fix crash in threadsRequest with no miDebugger, and undefined thread names --- src/backend/backend.ts | 3 ++- src/backend/mi2/mi2.ts | 15 ++++++++++----- src/mibase.ts | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/backend/backend.ts b/src/backend/backend.ts index 668551e8..2b124bcf 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -13,7 +13,8 @@ export interface Breakpoint { export interface Thread { id: number; - name: string; + targetId: string; + name?: string; } export interface Stack { diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 61397b81..9049bcf8 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -598,12 +598,17 @@ export class MI2 extends EventEmitter implements IBackend { let threads = result.result("threads"); let ret: Thread[] = []; return threads.map(element => { - let id = parseInt(MINode.valueOf(element, "id")); - let name = MINode.valueOf(element, "name") + ""; - return { - id, - name + let ret : Thread = { + id: parseInt(MINode.valueOf(element, "id")), + targetId: MINode.valueOf(element, "target-id") }; + + let name = MINode.valueOf(element, "name"); + if (name) { + ret.name = name; + } + + return ret; }); } diff --git a/src/mibase.ts b/src/mibase.ts index 68387ded..70a1c73c 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -235,6 +235,8 @@ export class MI2DebugSession extends DebugSession { Promise.all(all).then(brkpoints => { let finalBrks = []; brkpoints.forEach(brkp => { + // TODO: Currently all breakpoints returned are marked as verified, + // which leads to verified breakpoints on a broken lldb. if (brkp[0]) finalBrks.push(new DebugAdapter.Breakpoint(true, brkp[1].line)); }); @@ -256,13 +258,24 @@ export class MI2DebugSession extends DebugSession { } protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + if (!this.miDebugger) { + this.sendResponse(response); + } this.miDebugger.getThreads().then( threads => { response.body = { threads: [] }; for (const thread of threads) { - response.body.threads.push(new Thread(thread.id, thread.id + ":" + thread.name)); + let threadName = thread.name; + // TODO: Thread names are undefined on LLDB + if (threadName === undefined) { + threadName = thread.targetId; + } + if (threadName === undefined) { + threadName = ""; + } + response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName)); } this.sendResponse(response); }); From 8af4b28453f42729499f044e3b2b3fead7939a4f Mon Sep 17 00:00:00 2001 From: Jan Jurzitza Date: Wed, 21 Feb 2018 18:14:18 +0100 Subject: [PATCH 4/4] Return if miDebugger is not set in threadsRequest Not sure why it only happens for this function but this fixes the error in the console. --- src/mibase.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mibase.ts b/src/mibase.ts index 70a1c73c..cc728fae 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -260,6 +260,7 @@ export class MI2DebugSession extends DebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { if (!this.miDebugger) { this.sendResponse(response); + return; } this.miDebugger.getThreads().then( threads => {