From 93757e17e425ef81ac33c70ea1b48bb71d9ee344 Mon Sep 17 00:00:00 2001 From: Luis G Date: Sun, 4 Mar 2018 19:45:56 -0300 Subject: [PATCH 01/19] Initial port to v2 API --- .vscode/launch.json | 3 +- src/debugAdapter/goDebug.ts | 64 +++++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index cb6ed47b7..107de41ec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,8 @@ "stopOnEntry": false, "sourceMaps": true, "outFiles": ["${workspaceRoot}/out/**/*.js"], - "preLaunchTask": "npm" + "preLaunchTask": "npm", + "trace": "verbose" }, { "name": "Launch as server", diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index f3318692a..7661711b1 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -60,6 +60,14 @@ interface DebuggerState { currentGoroutine: DebugGoroutine; } +interface ClearBreakpointOut { + breakpoint: DebugBreakpoint; +} + +interface CreateBreakpointOut { + breakpoint: DebugBreakpoint; +} + interface DebugBreakpoint { addr: number; continue: boolean; @@ -80,6 +88,10 @@ interface DebugThread { function?: DebugFunction; }; +interface StacktraceOut { + locations: DebugLocation[]; +} + interface DebugLocation { pc: number; file: string; @@ -96,6 +108,18 @@ interface DebugFunction { locals: DebugVariable[]; } +interface ListLocalVarsOut { + variables: DebugVariable[]; +} + +interface ListFunctionArgsOut { + args: DebugVariable[]; +} + +interface EvalOut { + variable: DebugVariable; +} + interface DebugVariable { name: string; addr: number; @@ -109,6 +133,10 @@ interface DebugVariable { unreadable: string; } +interface ListGoroutinesOut { + goroutines: DebugGoroutine[]; +} + interface DebugGoroutine { id: number; currentLoc: DebugLocation; @@ -285,7 +313,7 @@ class Delve { } else if (currentGOWorkspace) { dlvArgs = dlvArgs.concat([dirname.substr(currentGOWorkspace.length + 1)]); } - dlvArgs = dlvArgs.concat(['--headless=true', '--listen=' + host + ':' + port.toString()]); + dlvArgs = dlvArgs.concat(['--headless=true', '--api-version=2', '--listen=' + host + ':' + port.toString()]); if (launchArgs.showLog) { dlvArgs = dlvArgs.concat(['--log=' + launchArgs.showLog.toString()]); } @@ -536,7 +564,7 @@ class GoDebugSession extends DebugSession { let remoteFile = this.toDebuggerPath(file); Promise.all(this.breakpoints.get(file).map(existingBP => { verbose('Clearing: ' + existingBP.id); - return this.delve.callPromise('ClearBreakpoint', [existingBP.id]); + return this.delve.callPromise('ClearBreakpoint', [existingBP.id]); })).then(() => { verbose('All cleared'); return Promise.all(args.lines.map(line => { @@ -545,7 +573,7 @@ class GoDebugSession extends DebugSession { } else { verbose('Creating on: ' + file + ' (' + remoteFile + ') :' + line); } - return this.delve.callPromise('CreateBreakpoint', [{ file: remoteFile, line }]).then(null, err => { + return this.delve.callPromise('CreateBreakpoint', [{ file: remoteFile, line }]).then(null, err => { verbose('Error on CreateBreakpoint: ' + err.toString()); return null; }); @@ -573,7 +601,7 @@ class GoDebugSession extends DebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { verbose('ThreadsRequest'); - this.delve.call('ListGoroutines', [], (err, goroutines) => { + this.delve.call('ListGoroutines', [], (err, goroutinesList) => { if (this.debugState.exited) { // If the program exits very quickly, the initial threadsRequest will complete after it has exited. // A TerminatedEvent has already been sent. Ignore the err returned in this case. @@ -585,8 +613,8 @@ class GoDebugSession extends DebugSession { logError('Failed to get threads.'); return this.sendErrorResponse(response, 2003, 'Unable to display threads: "{e}"', { e: err.toString() }); } - verbose('goroutines', goroutines); - let threads = goroutines.map(goroutine => + verbose('goroutines', goroutinesList); + let threads = goroutinesList.goroutines.map(goroutine => new Thread( goroutine.id, goroutine.userCurrentLoc.function ? goroutine.userCurrentLoc.function.name : (goroutine.userCurrentLoc.file + '@' + goroutine.userCurrentLoc.line) @@ -599,14 +627,14 @@ class GoDebugSession extends DebugSession { } protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - verbose('StackTraceRequest'); - this.delve.call('StacktraceGoroutine', [{ id: args.threadId, depth: args.levels }], (err, locations) => { + verbose('Stacktrace'); + this.delve.call('StacktraceGoroutine', [{ id: args.threadId, depth: args.levels }], (err, stackTrace) => { if (err) { logError('Failed to produce stack trace!'); return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', { e: err.toString() }); } - verbose('locations', locations); - let stackFrames = locations.map((location, i) => + verbose('locations', stackTrace); + let stackFrames = stackTrace.locations.map((location, i) => new StackFrame( i, location.function ? location.function.name : '', @@ -626,19 +654,21 @@ class GoDebugSession extends DebugSession { protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { verbose('ScopesRequest'); - this.delve.call('ListLocalVars', [{ goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }], (err, locals) => { + let loadConfig = {followPointers: true, maxVariableRecurse: 1, maxStringLen: 100, maxArrayValues: 100, maxStructFields: -1}; + let listLocalVarsIn = [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: loadConfig}]; + this.delve.call('ListLocalVars', listLocalVarsIn, (err, locals) => { if (err) { logError('Failed to list local variables.'); return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', { e: err.toString() }); } verbose('locals', locals); - this.delve.call('ListFunctionArgs', [{ goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }], (err, args) => { + this.delve.call('ListFunctionArgs', [{ goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }], (err, argsList) => { if (err) { logError('Failed to list function args.'); return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', { e: err.toString() }); } verbose('functionArgs', args); - let vars = args.concat(locals); + let vars = argsList.args.concat(locals.variables); let scopes = new Array(); let localVariables = { @@ -759,14 +789,14 @@ class GoDebugSession extends DebugSession { verbose('TerminatedEvent'); } else { // [TODO] Can we avoid doing this? https://github.com/Microsoft/vscode/issues/40#issuecomment-161999881 - this.delve.call('ListGoroutines', [], (err, goroutines) => { + this.delve.call('ListGoroutines', [], (err, out) => { if (err) { logError('Failed to get threads.'); } // Assume we need to stop all the threads we saw before... let needsToBeStopped = new Set(); this.threads.forEach(id => needsToBeStopped.add(id)); - for (let goroutine of goroutines) { + for (let goroutine of out.goroutines) { // ...but delete from list of threads to stop if we still see it needsToBeStopped.delete(goroutine.id); if (!this.threads.has(goroutine.id)) { @@ -867,12 +897,12 @@ class GoDebugSession extends DebugSession { frame: args.frameId } }; - this.delve.call('EvalSymbol', [evalSymbolArgs], (err, variable) => { + this.delve.call('Eval', [evalSymbolArgs], (err, out) => { if (err) { logError('Failed to eval expression: ', JSON.stringify(evalSymbolArgs, null, ' ')); return this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', { e: err.toString() }); } - response.body = this.convertDebugVariableToProtocolVariable(variable, 0); + response.body = this.convertDebugVariableToProtocolVariable(out.variable, 0); this.sendResponse(response); verbose('EvaluateResponse'); }); From 08bf3fe4529c514cf465a0efb3a7af8700471aa9 Mon Sep 17 00:00:00 2001 From: Luis GG Date: Mon, 5 Mar 2018 12:44:58 -0300 Subject: [PATCH 02/19] Refactor default LoadConfig --- src/debugAdapter/goDebug.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 7661711b1..edd5a3300 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -434,6 +434,11 @@ class GoDebugSession extends DebugSession { logger.init(e => this.sendEvent(e), logPath, isServer); } + // Get default LoadConfig values according to delve API + protected getDefaultLoadConfig(): any { + return {followPointers: true, maxVariableRecurse: 1, maxStringLen: 64, maxArrayValues: 64, maxStructFields: -1}; + } + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { verbose('InitializeRequest'); // This debug adapter implements the configurationDoneRequest. @@ -654,8 +659,7 @@ class GoDebugSession extends DebugSession { protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { verbose('ScopesRequest'); - let loadConfig = {followPointers: true, maxVariableRecurse: 1, maxStringLen: 100, maxArrayValues: 100, maxStructFields: -1}; - let listLocalVarsIn = [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: loadConfig}]; + let listLocalVarsIn = [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.getDefaultLoadConfig()}]; this.delve.call('ListLocalVars', listLocalVarsIn, (err, locals) => { if (err) { logError('Failed to list local variables.'); From 97e40979ff609934c8479f271bc520035ef22b92 Mon Sep 17 00:00:00 2001 From: Luis GG Date: Mon, 5 Mar 2018 14:53:15 -0300 Subject: [PATCH 03/19] Migrate remaining calls --- src/debugAdapter/goDebug.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index edd5a3300..2132e7ce8 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -569,7 +569,7 @@ class GoDebugSession extends DebugSession { let remoteFile = this.toDebuggerPath(file); Promise.all(this.breakpoints.get(file).map(existingBP => { verbose('Clearing: ' + existingBP.id); - return this.delve.callPromise('ClearBreakpoint', [existingBP.id]); + return this.delve.callPromise('ClearBreakpoint', [{id: existingBP.id, name: file}]); })).then(() => { verbose('All cleared'); return Promise.all(args.lines.map(line => { @@ -578,7 +578,10 @@ class GoDebugSession extends DebugSession { } else { verbose('Creating on: ' + file + ' (' + remoteFile + ') :' + line); } - return this.delve.callPromise('CreateBreakpoint', [{ file: remoteFile, line }]).then(null, err => { + let breakpointIn = {}; + breakpointIn.file = remoteFile; + breakpointIn.line = line; + return this.delve.callPromise('CreateBreakpoint', [breakpointIn]).then(null, err => { verbose('Error on CreateBreakpoint: ' + err.toString()); return null; }); @@ -633,7 +636,7 @@ class GoDebugSession extends DebugSession { protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { verbose('Stacktrace'); - this.delve.call('StacktraceGoroutine', [{ id: args.threadId, depth: args.levels }], (err, stackTrace) => { + this.delve.call('Stacktrace', [{ id: args.threadId, depth: args.levels, full: false, cfg: this.getDefaultLoadConfig() }], (err, stackTrace) => { if (err) { logError('Failed to produce stack trace!'); return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', { e: err.toString() }); @@ -666,7 +669,7 @@ class GoDebugSession extends DebugSession { return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', { e: err.toString() }); } verbose('locals', locals); - this.delve.call('ListFunctionArgs', [{ goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }], (err, argsList) => { + this.delve.call('ListFunctionArgs', [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.getDefaultLoadConfig()}], (err, argsList) => { if (err) { logError('Failed to list function args.'); return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', { e: err.toString() }); @@ -899,7 +902,8 @@ class GoDebugSession extends DebugSession { scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId - } + }, + cfg: this.getDefaultLoadConfig() }; this.delve.call('Eval', [evalSymbolArgs], (err, out) => { if (err) { From 8af39a00c2e26349c7312ecbd5310d9df7b99981 Mon Sep 17 00:00:00 2001 From: Luis G Date: Mon, 23 Apr 2018 00:33:39 -0300 Subject: [PATCH 04/19] Fix CreateBreakpoint apicall --- src/debugAdapter/goDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 24f9e54ee..64f772482 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -582,7 +582,7 @@ class GoDebugSession extends DebugSession { let breakpointIn = {}; breakpointIn.file = remoteFile; breakpointIn.line = line; - return this.delve.callPromise('CreateBreakpoint', [breakpointIn]).then(null, err => { + return this.delve.callPromise('CreateBreakpoint', [{breakpoint: breakpointIn}]).then(null, err => { verbose('Error on CreateBreakpoint: ' + err.toString()); return null; }); From e38635317ddd93314cb1cf065b21bdb0cdd825e0 Mon Sep 17 00:00:00 2001 From: Luis G Date: Thu, 26 Apr 2018 00:33:21 -0300 Subject: [PATCH 05/19] Fix breakpoint apicalls. Unwrap command apicalls --- src/debugAdapter/goDebug.ts | 66 +++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 64f772482..f64bf6e2f 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -51,6 +51,10 @@ enum GoReflectKind { // These types should stay in sync with: // https://github.com/derekparker/delve/blob/master/service/api/types.go +interface CommandOut { + State: DebuggerState; +} + interface DebuggerState { exited: boolean; exitStatus: number; @@ -89,7 +93,7 @@ interface DebugThread { }; interface StacktraceOut { - locations: DebugLocation[]; + Locations: DebugLocation[]; } interface DebugLocation { @@ -109,11 +113,11 @@ interface DebugFunction { } interface ListLocalVarsOut { - variables: DebugVariable[]; + Variables: DebugVariable[]; } interface ListFunctionArgsOut { - args: DebugVariable[]; + Args: DebugVariable[]; } interface EvalOut { @@ -134,7 +138,7 @@ interface DebugVariable { } interface ListGoroutinesOut { - goroutines: DebugGoroutine[]; + Goroutines: DebugGoroutine[]; } interface DebugGoroutine { @@ -399,7 +403,7 @@ class Delve { close() { if (!this.debugProcess) { - this.call('Command', [{ name: 'halt' }], (err, state) => { + this.call('Command', [{ name: 'halt' }], (err, commandOut) => { if (err) return logError('Failed to halt.'); this.call('Restart', [], (err, state) => { if (err) return logError('Failed to restart.'); @@ -588,15 +592,19 @@ class GoDebugSession extends DebugSession { }); })); }).then(newBreakpoints => { - verbose('All set:' + JSON.stringify(newBreakpoints)); - let breakpoints = newBreakpoints.map((bp, i) => { + // Unwrap breakpoints from v2 apicall + let adaptedBreakpoints = newBreakpoints.map((bp, i) => { + return bp.Breakpoint; + }); + let breakpoints = adaptedBreakpoints.map((bp, i) => { if (bp) { return { verified: true, line: bp.line }; } else { return { verified: false, line: args.lines[i] }; } }); - this.breakpoints.set(file, newBreakpoints.filter(x => !!x)); + verbose('All set:' + JSON.stringify(adaptedBreakpoints)); + this.breakpoints.set(file, adaptedBreakpoints.filter(x => !!x)); return breakpoints; }).then(breakpoints => { response.body = { breakpoints }; @@ -622,8 +630,8 @@ class GoDebugSession extends DebugSession { logError('Failed to get threads.'); return this.sendErrorResponse(response, 2003, 'Unable to display threads: "{e}"', { e: err.toString() }); } - verbose('goroutines', goroutinesList); - let threads = goroutinesList.goroutines.map(goroutine => + verbose('goroutines', goroutinesList.Goroutines); + let threads = goroutinesList.Goroutines.map(goroutine => new Thread( goroutine.id, goroutine.userCurrentLoc.function ? goroutine.userCurrentLoc.function.name : (goroutine.userCurrentLoc.file + '@' + goroutine.userCurrentLoc.line) @@ -642,8 +650,8 @@ class GoDebugSession extends DebugSession { logError('Failed to produce stack trace!'); return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', { e: err.toString() }); } - verbose('locations', stackTrace); - let stackFrames = stackTrace.locations.map((location, i) => + verbose('locations', stackTrace.Locations); + let stackFrames = stackTrace.Locations.map((location, i) => new StackFrame( i, location.function ? location.function.name : '', @@ -669,14 +677,14 @@ class GoDebugSession extends DebugSession { logError('Failed to list local variables.'); return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', { e: err.toString() }); } - verbose('locals', locals); + verbose('locals', locals.Variables); this.delve.call('ListFunctionArgs', [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.getDefaultLoadConfig()}], (err, argsList) => { if (err) { logError('Failed to list function args.'); return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', { e: err.toString() }); } verbose('functionArgs', args); - let vars = argsList.args.concat(locals.variables); + let vars = argsList.Args.concat(locals.Variables); let scopes = new Array(); let localVariables = { @@ -804,7 +812,7 @@ class GoDebugSession extends DebugSession { // Assume we need to stop all the threads we saw before... let needsToBeStopped = new Set(); this.threads.forEach(id => needsToBeStopped.add(id)); - for (let goroutine of out.goroutines) { + for (let goroutine of out.Goroutines) { // ...but delete from list of threads to stop if we still see it needsToBeStopped.delete(goroutine.id); if (!this.threads.has(goroutine.id)) { @@ -829,12 +837,12 @@ class GoDebugSession extends DebugSession { protected continueRequest(response: DebugProtocol.ContinueResponse): void { verbose('ContinueRequest'); - this.delve.call('Command', [{ name: 'continue' }], (err, state) => { + this.delve.call('Command', [{ name: 'continue' }], (err, commandOut) => { if (err) { logError('Failed to continue.'); } - verbose('continue state', state); - this.debugState = state; + verbose('continue state', commandOut.State); + this.debugState = commandOut.State; this.handleReenterDebug('breakpoint'); }); this.sendResponse(response); @@ -843,12 +851,12 @@ class GoDebugSession extends DebugSession { protected nextRequest(response: DebugProtocol.NextResponse): void { verbose('NextRequest'); - this.delve.call('Command', [{ name: 'next' }], (err, state) => { + this.delve.call('Command', [{ name: 'next' }], (err, commandOut) => { if (err) { logError('Failed to next.'); } - verbose('next state', state); - this.debugState = state; + verbose('next state', commandOut.State); + this.debugState = commandOut.State; this.handleReenterDebug('step'); }); this.sendResponse(response); @@ -857,12 +865,12 @@ class GoDebugSession extends DebugSession { protected stepInRequest(response: DebugProtocol.StepInResponse): void { verbose('StepInRequest'); - this.delve.call('Command', [{ name: 'step' }], (err, state) => { + this.delve.call('Command', [{ name: 'step' }], (err, commandOut) => { if (err) { logError('Failed to step.'); } - verbose('stop state', state); - this.debugState = state; + verbose('stop state', commandOut.State); + this.debugState = commandOut.State; this.handleReenterDebug('step'); }); this.sendResponse(response); @@ -871,12 +879,12 @@ class GoDebugSession extends DebugSession { protected stepOutRequest(response: DebugProtocol.StepOutResponse): void { verbose('StepOutRequest'); - this.delve.call('Command', [{ name: 'stepOut' }], (err, state) => { + this.delve.call('Command', [{ name: 'stepOut' }], (err, commandOut) => { if (err) { logError('Failed to stepout.'); } - verbose('stepout state', state); - this.debugState = state; + verbose('stepout state', commandOut.State); + this.debugState = commandOut.State; this.handleReenterDebug('step'); }); this.sendResponse(response); @@ -885,12 +893,12 @@ class GoDebugSession extends DebugSession { protected pauseRequest(response: DebugProtocol.PauseResponse): void { verbose('PauseRequest'); - this.delve.call('Command', [{ name: 'halt' }], (err, state) => { + this.delve.call('Command', [{ name: 'halt' }], (err, commandOut) => { if (err) { logError('Failed to halt.'); return this.sendErrorResponse(response, 2010, 'Unable to halt execution: "{e}"', { e: err.toString() }); } - verbose('pause state', state); + verbose('pause state', commandOut.State); this.sendResponse(response); verbose('PauseResponse'); }); From 4fb5362faa571f0e18b9d63885efecda8b30aea2 Mon Sep 17 00:00:00 2001 From: Luis GG Date: Thu, 26 Apr 2018 10:27:36 -0300 Subject: [PATCH 06/19] Add delve output on eval error (console) --- src/debugAdapter/goDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index f64bf6e2f..8b871cde0 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -916,7 +916,7 @@ class GoDebugSession extends DebugSession { }; this.delve.call('Eval', [evalSymbolArgs], (err, out) => { if (err) { - logError('Failed to eval expression: ', JSON.stringify(evalSymbolArgs, null, ' ')); + logError('Failed to eval expression: ', JSON.stringify(evalSymbolArgs, null, ' '), '\n\rEval error:', err.toString()); return this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', { e: err.toString() }); } response.body = this.convertDebugVariableToProtocolVariable(out.variable, 0); From 0766407a7c656d16b4a1c7a02ab96429f347286c Mon Sep 17 00:00:00 2001 From: Luis GG Date: Thu, 26 Apr 2018 11:10:16 -0300 Subject: [PATCH 07/19] Fix EvaluateRequest apicall --- src/debugAdapter/goDebug.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 8b871cde0..1c304bc1f 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -121,7 +121,7 @@ interface ListFunctionArgsOut { } interface EvalOut { - variable: DebugVariable; + Variable: DebugVariable; } interface DebugVariable { @@ -441,7 +441,7 @@ class GoDebugSession extends DebugSession { // Get default LoadConfig values according to delve API protected getDefaultLoadConfig(): any { - return {followPointers: true, maxVariableRecurse: 1, maxStringLen: 64, maxArrayValues: 64, maxStructFields: -1}; + return {FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}; } protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { @@ -907,19 +907,19 @@ class GoDebugSession extends DebugSession { protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { verbose('EvaluateRequest'); let evalSymbolArgs = { - symbol: args.expression, - scope: { + Expr: args.expression, + Scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, - cfg: this.getDefaultLoadConfig() + Cfg: this.getDefaultLoadConfig() }; this.delve.call('Eval', [evalSymbolArgs], (err, out) => { if (err) { logError('Failed to eval expression: ', JSON.stringify(evalSymbolArgs, null, ' '), '\n\rEval error:', err.toString()); return this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', { e: err.toString() }); } - response.body = this.convertDebugVariableToProtocolVariable(out.variable, 0); + response.body = this.convertDebugVariableToProtocolVariable(out.Variable, 0); this.sendResponse(response); verbose('EvaluateResponse'); }); From 9a884b29cf830bc14ae2f5426826d63981a4a5cf Mon Sep 17 00:00:00 2001 From: Luis G Date: Thu, 26 Apr 2018 22:24:05 -0300 Subject: [PATCH 08/19] Fix SetBreakpoint, add LoadConfig interface --- src/debugAdapter/goDebug.ts | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 1c304bc1f..b33ad2a7f 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -79,11 +79,27 @@ interface DebugBreakpoint { functionName?: string; goroutine: boolean; id: number; + name: string; line: number; stacktrace: number; variables?: DebugVariable[]; + loadArgs?: LoadConfig; + loadLocals?: LoadConfig; } +interface LoadConfig { + // FollowPointers requests pointers to be automatically dereferenced. + followPointers: boolean; + // MaxVariableRecurse is how far to recurse when evaluating nested types. + maxVariableRecurse: number; + // MaxStringLen is the maximum number of bytes read from a string + maxStringLen: number; + // MaxArrayValues is the maximum number of elements read from an array, a slice or a map. + maxArrayValues: number; + // MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. + maxStructFields: number; +}; + interface DebugThread { file: string; id: number; @@ -439,9 +455,11 @@ class GoDebugSession extends DebugSession { logger.init(e => this.sendEvent(e), logPath, isServer); } - // Get default LoadConfig values according to delve API - protected getDefaultLoadConfig(): any { - return {FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}; + // Get default LoadConfig values according to delve API: + // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13 + // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423 + protected getDefaultLoadConfig(): LoadConfig { + return {followPointers: true, maxVariableRecurse: 1, maxStringLen: 64, maxArrayValues: 64, maxStructFields: -1}; } protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { @@ -572,9 +590,19 @@ class GoDebugSession extends DebugSession { this.breakpoints.set(file, []); } let remoteFile = this.toDebuggerPath(file); + /* TODO: Change this flow (applies for both v1 and v2 apicalls) + The current approach does the following, upon setting a breakpoint: + 1- Traverse all breakpoints and clear them via ClearBreakpoint + 2- Restore existing (active) breakpoints from DebugProtocol args and re-create them via CreateBreakpoint + 3- Send debug protocol response + + We might be able to optimize this so we only update modified breakpoints via AmendBreakpoint: + https://godoc.org/github.com/derekparker/delve/service/rpc2#RPCServer.AmendBreakpoint + */ Promise.all(this.breakpoints.get(file).map(existingBP => { verbose('Clearing: ' + existingBP.id); - return this.delve.callPromise('ClearBreakpoint', [{id: existingBP.id, name: file}]); + // Breakpoint objects also support names as IDs. We choose to use a numbered ID + return this.delve.callPromise('ClearBreakpoint', [{Id: existingBP.id}]); })).then(() => { verbose('All cleared'); return Promise.all(args.lines.map(line => { @@ -586,7 +614,9 @@ class GoDebugSession extends DebugSession { let breakpointIn = {}; breakpointIn.file = remoteFile; breakpointIn.line = line; - return this.delve.callPromise('CreateBreakpoint', [{breakpoint: breakpointIn}]).then(null, err => { + breakpointIn.loadArgs = this.getDefaultLoadConfig(); + breakpointIn.loadLocals = this.getDefaultLoadConfig(); + return this.delve.callPromise('CreateBreakpoint', [{Breakpoint: breakpointIn}]).then(null, err => { verbose('Error on CreateBreakpoint: ' + err.toString()); return null; }); From 9488c66ee0a6949637236ffe2da7e67061ad0c79 Mon Sep 17 00:00:00 2001 From: Luis G Date: Thu, 26 Apr 2018 23:19:06 -0300 Subject: [PATCH 09/19] Migrate Restart apicall --- src/debugAdapter/goDebug.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index b33ad2a7f..7da37b3e9 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -170,6 +170,15 @@ interface DebuggerCommand { goroutineID?: number; } +interface RestartOut { + DiscardedBreakpoints: DiscardedBreakpoint[]; +} + +interface DiscardedBreakpoint { + breakpoint: DebugBreakpoint; + reason: string; +} + // This interface should always match the schema found in `package.json`. interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { program: string; @@ -421,8 +430,8 @@ class Delve { if (!this.debugProcess) { this.call('Command', [{ name: 'halt' }], (err, commandOut) => { if (err) return logError('Failed to halt.'); - this.call('Restart', [], (err, state) => { - if (err) return logError('Failed to restart.'); + this.call('Restart', [{position: '', resetArgs: false, newArgs: []}], (err, out) => { + if (err) return logError('Failed to restart'); }); }); } else { From 3ac0e2321e1594137bb2bfa531e9f2a224b1d20c Mon Sep 17 00:00:00 2001 From: Luis G Date: Fri, 27 Apr 2018 00:45:05 -0300 Subject: [PATCH 10/19] Add delve LoadConfig in launch configuration --- package.json | 25 +++++++++++++++++++++++ src/debugAdapter/goDebug.ts | 40 +++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 6c0254057..41fff72af 100644 --- a/package.json +++ b/package.json @@ -417,6 +417,31 @@ "type": "string", "description": "Output path for the binary of delve", "default": "debug" + }, + "followPointers": { + "type": "boolean", + "description": "FollowPointers requests pointers to be automatically dereferenced", + "default": true + }, + "maxVariableRecurse": { + "type": "number", + "description": "MaxVariableRecurse is how far to recurse when evaluating nested types", + "default": 1 + }, + "maxStringLen": { + "type": "number", + "description": "MaxStringLen is the maximum number of bytes read from a string", + "default": 64 + }, + "maxArrayValues": { + "type": "number", + "description": "MaxArrayValues is the maximum number of elements read from an array, a slice or a map", + "default": 64 + }, + "maxStructFields": { + "type": "number", + "description": "MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields", + "default": -1 } } } diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 7da37b3e9..0e1b1f8c9 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -198,6 +198,12 @@ interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { envFile?: string; backend?: string; output?: string; + /** Delve LoadConfig parameters **/ + followPointers: boolean; + maxVariableRecurse: number; + maxStringLen: number; + maxArrayValues: number; + maxStructFields: number; } process.on('uncaughtException', (err: any) => { @@ -238,6 +244,7 @@ class Delve { program: string; remotePath: string; debugProcess: ChildProcess; + loadConfig: LoadConfig; connection: Promise; onstdout: (str: string) => void; onstderr: (str: string) => void; @@ -252,6 +259,10 @@ class Delve { let isProgramDirectory = false; let launchArgsEnv = launchArgs.env || {}; this.connection = new Promise((resolve, reject) => { + let argsGetter = function (val: any, def: any) { + return ((val === null) || (val === undefined)) ? def : val; + }; + // Validations on the program if (!program) { return reject('The program attribute is missing in the debug configuration in launch.json'); @@ -362,6 +373,16 @@ class Delve { if (launchArgs.args) { dlvArgs = dlvArgs.concat(['--', ...launchArgs.args]); } + // Get default LoadConfig values according to delve API: + // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13 + // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423 + this.loadConfig = { + followPointers: argsGetter(launchArgs.followPointers, true), + maxVariableRecurse: argsGetter(launchArgs.maxVariableRecurse, 1), + maxStringLen: argsGetter(launchArgs.maxStringLen, 64), + maxArrayValues: argsGetter(launchArgs.maxArrayValues, 64), + maxStructFields: argsGetter(launchArgs.maxStructFields, -1) + }; verbose(`Running: ${dlv} ${dlvArgs.join(' ')}`); @@ -464,13 +485,6 @@ class GoDebugSession extends DebugSession { logger.init(e => this.sendEvent(e), logPath, isServer); } - // Get default LoadConfig values according to delve API: - // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13 - // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423 - protected getDefaultLoadConfig(): LoadConfig { - return {followPointers: true, maxVariableRecurse: 1, maxStringLen: 64, maxArrayValues: 64, maxStructFields: -1}; - } - protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { verbose('InitializeRequest'); // This debug adapter implements the configurationDoneRequest. @@ -623,8 +637,8 @@ class GoDebugSession extends DebugSession { let breakpointIn = {}; breakpointIn.file = remoteFile; breakpointIn.line = line; - breakpointIn.loadArgs = this.getDefaultLoadConfig(); - breakpointIn.loadLocals = this.getDefaultLoadConfig(); + breakpointIn.loadArgs = this.delve.loadConfig; + breakpointIn.loadLocals = this.delve.loadConfig; return this.delve.callPromise('CreateBreakpoint', [{Breakpoint: breakpointIn}]).then(null, err => { verbose('Error on CreateBreakpoint: ' + err.toString()); return null; @@ -684,7 +698,7 @@ class GoDebugSession extends DebugSession { protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { verbose('Stacktrace'); - this.delve.call('Stacktrace', [{ id: args.threadId, depth: args.levels, full: false, cfg: this.getDefaultLoadConfig() }], (err, stackTrace) => { + this.delve.call('Stacktrace', [{ id: args.threadId, depth: args.levels, full: false, cfg: this.delve.loadConfig }], (err, stackTrace) => { if (err) { logError('Failed to produce stack trace!'); return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', { e: err.toString() }); @@ -710,14 +724,14 @@ class GoDebugSession extends DebugSession { protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { verbose('ScopesRequest'); - let listLocalVarsIn = [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.getDefaultLoadConfig()}]; + let listLocalVarsIn = [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.delve.loadConfig}]; this.delve.call('ListLocalVars', listLocalVarsIn, (err, locals) => { if (err) { logError('Failed to list local variables.'); return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', { e: err.toString() }); } verbose('locals', locals.Variables); - this.delve.call('ListFunctionArgs', [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.getDefaultLoadConfig()}], (err, argsList) => { + this.delve.call('ListFunctionArgs', [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.delve.loadConfig}], (err, argsList) => { if (err) { logError('Failed to list function args.'); return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', { e: err.toString() }); @@ -951,7 +965,7 @@ class GoDebugSession extends DebugSession { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, - Cfg: this.getDefaultLoadConfig() + Cfg: this.delve.loadConfig }; this.delve.call('Eval', [evalSymbolArgs], (err, out) => { if (err) { From 37bd6edbe54154dabf358e5f146c9c6a704a89ce Mon Sep 17 00:00:00 2001 From: Luis GG Date: Sun, 6 May 2018 00:36:56 -0300 Subject: [PATCH 11/19] Update launch.json --- .vscode/launch.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 68c1b42ef..a37ef247f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,8 +11,7 @@ "stopOnEntry": false, "sourceMaps": true, "outFiles": ["${workspaceRoot}/out/**/*.js"], - "preLaunchTask": "npm", - "trace": "verbose" + "preLaunchTask": "npm" }, { "name": "Launch as server", From c8137c27ca200ba4d7b9dd541a4d66c45702b1b3 Mon Sep 17 00:00:00 2001 From: Luis G Date: Sun, 6 May 2018 23:52:19 -0300 Subject: [PATCH 12/19] Remove TODO comment --- src/debugAdapter/goDebug.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 0e1b1f8c9..0a35e1353 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -613,15 +613,7 @@ class GoDebugSession extends DebugSession { this.breakpoints.set(file, []); } let remoteFile = this.toDebuggerPath(file); - /* TODO: Change this flow (applies for both v1 and v2 apicalls) - The current approach does the following, upon setting a breakpoint: - 1- Traverse all breakpoints and clear them via ClearBreakpoint - 2- Restore existing (active) breakpoints from DebugProtocol args and re-create them via CreateBreakpoint - 3- Send debug protocol response - - We might be able to optimize this so we only update modified breakpoints via AmendBreakpoint: - https://godoc.org/github.com/derekparker/delve/service/rpc2#RPCServer.AmendBreakpoint - */ + Promise.all(this.breakpoints.get(file).map(existingBP => { verbose('Clearing: ' + existingBP.id); // Breakpoint objects also support names as IDs. We choose to use a numbered ID From 6dfbfecc9002405f1b0dfe393188eaa8c512ef91 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Mon, 14 May 2018 15:30:59 -0700 Subject: [PATCH 13/19] Enable setting new breakpoints after a failed one --- src/debugAdapter/goDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 0a35e1353..d5f95592e 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -639,7 +639,7 @@ class GoDebugSession extends DebugSession { }).then(newBreakpoints => { // Unwrap breakpoints from v2 apicall let adaptedBreakpoints = newBreakpoints.map((bp, i) => { - return bp.Breakpoint; + return bp ? bp.Breakpoint : null; }); let breakpoints = adaptedBreakpoints.map((bp, i) => { if (bp) { From 17cd579b761c12aa9169ddce56b3ab033fad23f7 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Mon, 14 May 2018 17:12:48 -0700 Subject: [PATCH 14/19] Option to use v1 api of delve --- package.json | 5 ++ src/debugAdapter/goDebug.ts | 135 ++++++++++++++++++++++-------------- 2 files changed, 87 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 08942e8b7..ee57df8c1 100644 --- a/package.json +++ b/package.json @@ -442,6 +442,11 @@ "type": "number", "description": "MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields", "default": -1 + }, + "useApiV1": { + "type": "boolean", + "description": "If true, the v1 of delve apis will be used, else v2 will be used", + "default": true } } } diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index d5f95592e..7be5d042a 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -170,13 +170,13 @@ interface DebuggerCommand { goroutineID?: number; } -interface RestartOut { +interface RestartOut { DiscardedBreakpoints: DiscardedBreakpoint[]; } interface DiscardedBreakpoint { breakpoint: DebugBreakpoint; - reason: string; + reason: string; } // This interface should always match the schema found in `package.json`. @@ -204,6 +204,8 @@ interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { maxStringLen: number; maxArrayValues: number; maxStructFields: number; + /** Delve Version */ + useApiV1: boolean; } process.on('uncaughtException', (err: any) => { @@ -250,10 +252,12 @@ class Delve { onstderr: (str: string) => void; onclose: (code: number) => void; noDebug: boolean; + isApiV1: boolean; constructor(remotePath: string, port: number, host: string, program: string, launchArgs: LaunchRequestArguments) { this.program = normalizePath(program); this.remotePath = remotePath; + this.isApiV1 = launchArgs.useApiV1; let mode = launchArgs.mode; let dlvCwd = dirname(program); let isProgramDirectory = false; @@ -351,7 +355,11 @@ class Delve { } else if (currentGOWorkspace) { dlvArgs = dlvArgs.concat([dirname.substr(currentGOWorkspace.length + 1)]); } - dlvArgs = dlvArgs.concat(['--headless=true', '--api-version=2', '--listen=' + host + ':' + port.toString()]); + dlvArgs = dlvArgs.concat(['--headless=true', '--listen=' + host + ':' + port.toString()]); + if (!this.isApiV1) { + dlvArgs.push('--api-version=2'); + } + if (launchArgs.showLog) { dlvArgs = dlvArgs.concat(['--log=' + launchArgs.showLog.toString()]); } @@ -381,7 +389,7 @@ class Delve { maxVariableRecurse: argsGetter(launchArgs.maxVariableRecurse, 1), maxStringLen: argsGetter(launchArgs.maxStringLen, 64), maxArrayValues: argsGetter(launchArgs.maxArrayValues, 64), - maxStructFields: argsGetter(launchArgs.maxStructFields, -1) + maxStructFields: argsGetter(launchArgs.maxStructFields, -1) }; verbose(`Running: ${dlv} ${dlvArgs.join(' ')}`); @@ -449,9 +457,9 @@ class Delve { close() { if (!this.debugProcess) { - this.call('Command', [{ name: 'halt' }], (err, commandOut) => { + this.call('Command', [{ name: 'halt' }], (err, out) => { if (err) return logError('Failed to halt.'); - this.call('Restart', [{position: '', resetArgs: false, newArgs: []}], (err, out) => { + this.call('Restart', this.isApiV1 ? [] : [{ position: '', resetArgs: false, newArgs: [] }], (err, out) => { if (err) return logError('Failed to restart'); }); }); @@ -616,8 +624,7 @@ class GoDebugSession extends DebugSession { Promise.all(this.breakpoints.get(file).map(existingBP => { verbose('Clearing: ' + existingBP.id); - // Breakpoint objects also support names as IDs. We choose to use a numbered ID - return this.delve.callPromise('ClearBreakpoint', [{Id: existingBP.id}]); + return this.delve.callPromise('ClearBreakpoint', [this.delve.isApiV1 ? existingBP.id : { Id: existingBP.id }]); })).then(() => { verbose('All cleared'); return Promise.all(args.lines.map(line => { @@ -631,25 +638,27 @@ class GoDebugSession extends DebugSession { breakpointIn.line = line; breakpointIn.loadArgs = this.delve.loadConfig; breakpointIn.loadLocals = this.delve.loadConfig; - return this.delve.callPromise('CreateBreakpoint', [{Breakpoint: breakpointIn}]).then(null, err => { + return this.delve.callPromise('CreateBreakpoint', [this.delve.isApiV1 ? breakpointIn : { Breakpoint: breakpointIn }]).then(null, err => { verbose('Error on CreateBreakpoint: ' + err.toString()); return null; }); })); }).then(newBreakpoints => { - // Unwrap breakpoints from v2 apicall - let adaptedBreakpoints = newBreakpoints.map((bp, i) => { - return bp ? bp.Breakpoint : null; - }); - let breakpoints = adaptedBreakpoints.map((bp, i) => { + if (!this.delve.isApiV1) { + // Unwrap breakpoints from v2 apicall + newBreakpoints = newBreakpoints.map((bp, i) => { + return bp ? bp.Breakpoint : null; + }); + } + verbose('All set:' + JSON.stringify(newBreakpoints)); + let breakpoints = newBreakpoints.map((bp, i) => { if (bp) { return { verified: true, line: bp.line }; } else { return { verified: false, line: args.lines[i] }; } }); - verbose('All set:' + JSON.stringify(adaptedBreakpoints)); - this.breakpoints.set(file, adaptedBreakpoints.filter(x => !!x)); + this.breakpoints.set(file, newBreakpoints.filter(x => !!x)); return breakpoints; }).then(breakpoints => { response.body = { breakpoints }; @@ -663,7 +672,7 @@ class GoDebugSession extends DebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { verbose('ThreadsRequest'); - this.delve.call('ListGoroutines', [], (err, goroutinesList) => { + this.delve.call('ListGoroutines', [], (err, out) => { if (this.debugState.exited) { // If the program exits very quickly, the initial threadsRequest will complete after it has exited. // A TerminatedEvent has already been sent. Ignore the err returned in this case. @@ -675,8 +684,9 @@ class GoDebugSession extends DebugSession { logError('Failed to get threads.'); return this.sendErrorResponse(response, 2003, 'Unable to display threads: "{e}"', { e: err.toString() }); } - verbose('goroutines', goroutinesList.Goroutines); - let threads = goroutinesList.Goroutines.map(goroutine => + const goroutines = this.delve.isApiV1 ? out : (out).Goroutines; + verbose('goroutines', goroutines); + let threads = goroutines.map(goroutine => new Thread( goroutine.id, goroutine.userCurrentLoc.function ? goroutine.userCurrentLoc.function.name : (goroutine.userCurrentLoc.file + '@' + goroutine.userCurrentLoc.line) @@ -689,14 +699,19 @@ class GoDebugSession extends DebugSession { } protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - verbose('Stacktrace'); - this.delve.call('Stacktrace', [{ id: args.threadId, depth: args.levels, full: false, cfg: this.delve.loadConfig }], (err, stackTrace) => { + verbose('StackTraceRequest'); + let stackTraceIn = { id: args.threadId, depth: args.levels }; + if (!this.delve.isApiV1) { + Object.assign(stackTraceIn, { full: false, cfg: this.delve.loadConfig }); + } + this.delve.call(this.delve.isApiV1 ? 'StacktraceGoroutine' : 'Stacktrace', [stackTraceIn], (err, out) => { if (err) { logError('Failed to produce stack trace!'); return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', { e: err.toString() }); } - verbose('locations', stackTrace.Locations); - let stackFrames = stackTrace.Locations.map((location, i) => + const locations = this.delve.isApiV1 ? out : (out).Locations; + verbose('locations', locations); + let stackFrames = locations.map((location, i) => new StackFrame( i, location.function ? location.function.name : '', @@ -716,20 +731,23 @@ class GoDebugSession extends DebugSession { protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { verbose('ScopesRequest'); - let listLocalVarsIn = [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.delve.loadConfig}]; - this.delve.call('ListLocalVars', listLocalVarsIn, (err, locals) => { + const listLocalVarsIn = { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }; + this.delve.call('ListLocalVars', this.delve.isApiV1 ? [listLocalVarsIn] : [{ scope: listLocalVarsIn, cfg: this.delve.loadConfig }], (err, out) => { if (err) { logError('Failed to list local variables.'); return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', { e: err.toString() }); } - verbose('locals', locals.Variables); - this.delve.call('ListFunctionArgs', [{scope: { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }, cfg: this.delve.loadConfig}], (err, argsList) => { + const locals = this.delve.isApiV1 ? out : (out).Variables; + verbose('locals', locals); + let listLocalFunctionArgsIn = { goroutineID: this.debugState.currentGoroutine.id, frame: args.frameId }; + this.delve.call('ListFunctionArgs', this.delve.isApiV1 ? [listLocalFunctionArgsIn] : [{ scope: listLocalFunctionArgsIn, cfg: this.delve.loadConfig }], (err, outArgs) => { if (err) { logError('Failed to list function args.'); return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', { e: err.toString() }); } + const args = this.delve.isApiV1 ? outArgs : (outArgs).Args; verbose('functionArgs', args); - let vars = argsList.Args.concat(locals.Variables); + let vars = args.concat(locals); let scopes = new Array(); let localVariables = { @@ -850,14 +868,15 @@ class GoDebugSession extends DebugSession { verbose('TerminatedEvent'); } else { // [TODO] Can we avoid doing this? https://github.com/Microsoft/vscode/issues/40#issuecomment-161999881 - this.delve.call('ListGoroutines', [], (err, out) => { + this.delve.call('ListGoroutines', [], (err, out) => { if (err) { logError('Failed to get threads.'); } + const goroutines = this.delve.isApiV1 ? out : (out).Goroutines; // Assume we need to stop all the threads we saw before... let needsToBeStopped = new Set(); this.threads.forEach(id => needsToBeStopped.add(id)); - for (let goroutine of out.Goroutines) { + for (let goroutine of goroutines) { // ...but delete from list of threads to stop if we still see it needsToBeStopped.delete(goroutine.id); if (!this.threads.has(goroutine.id)) { @@ -882,12 +901,13 @@ class GoDebugSession extends DebugSession { protected continueRequest(response: DebugProtocol.ContinueResponse): void { verbose('ContinueRequest'); - this.delve.call('Command', [{ name: 'continue' }], (err, commandOut) => { + this.delve.call('Command', [{ name: 'continue' }], (err, out) => { if (err) { logError('Failed to continue.'); } - verbose('continue state', commandOut.State); - this.debugState = commandOut.State; + const state = this.delve.isApiV1 ? out : (out).State; + verbose('continue state', state); + this.debugState = state; this.handleReenterDebug('breakpoint'); }); this.sendResponse(response); @@ -896,12 +916,13 @@ class GoDebugSession extends DebugSession { protected nextRequest(response: DebugProtocol.NextResponse): void { verbose('NextRequest'); - this.delve.call('Command', [{ name: 'next' }], (err, commandOut) => { + this.delve.call('Command', [{ name: 'next' }], (err, out) => { if (err) { logError('Failed to next.'); } - verbose('next state', commandOut.State); - this.debugState = commandOut.State; + const state = this.delve.isApiV1 ? out : (out).State; + verbose('next state', state); + this.debugState = state; this.handleReenterDebug('step'); }); this.sendResponse(response); @@ -910,12 +931,13 @@ class GoDebugSession extends DebugSession { protected stepInRequest(response: DebugProtocol.StepInResponse): void { verbose('StepInRequest'); - this.delve.call('Command', [{ name: 'step' }], (err, commandOut) => { + this.delve.call('Command', [{ name: 'step' }], (err, out) => { if (err) { logError('Failed to step.'); } - verbose('stop state', commandOut.State); - this.debugState = commandOut.State; + const state = this.delve.isApiV1 ? out : (out).State; + verbose('stop state', state); + this.debugState = state; this.handleReenterDebug('step'); }); this.sendResponse(response); @@ -924,12 +946,13 @@ class GoDebugSession extends DebugSession { protected stepOutRequest(response: DebugProtocol.StepOutResponse): void { verbose('StepOutRequest'); - this.delve.call('Command', [{ name: 'stepOut' }], (err, commandOut) => { + this.delve.call('Command', [{ name: 'stepOut' }], (err, out) => { if (err) { logError('Failed to stepout.'); } - verbose('stepout state', commandOut.State); - this.debugState = commandOut.State; + const state = this.delve.isApiV1 ? out : (out).State; + verbose('stepout state', state); + this.debugState = state; this.handleReenterDebug('step'); }); this.sendResponse(response); @@ -938,12 +961,13 @@ class GoDebugSession extends DebugSession { protected pauseRequest(response: DebugProtocol.PauseResponse): void { verbose('PauseRequest'); - this.delve.call('Command', [{ name: 'halt' }], (err, commandOut) => { + this.delve.call('Command', [{ name: 'halt' }], (err, out) => { if (err) { logError('Failed to halt.'); return this.sendErrorResponse(response, 2010, 'Unable to halt execution: "{e}"', { e: err.toString() }); } - verbose('pause state', commandOut.State); + const state = this.delve.isApiV1 ? out : (out).State; + verbose('pause state', state); this.sendResponse(response); verbose('PauseResponse'); }); @@ -951,20 +975,25 @@ class GoDebugSession extends DebugSession { protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { verbose('EvaluateRequest'); - let evalSymbolArgs = { - Expr: args.expression, - Scope: { - goroutineID: this.debugState.currentGoroutine.id, - frame: args.frameId - }, - Cfg: this.delve.loadConfig + const scope = { + goroutineID: this.debugState.currentGoroutine.id, + frame: args.frameId }; - this.delve.call('Eval', [evalSymbolArgs], (err, out) => { + let evalSymbolArgs = this.delve.isApiV1 ? { + symbol: args.expression, + scope + } : { + Expr: args.expression, + Scope: scope, + Cfg: this.delve.loadConfig + }; + this.delve.call('Eval', [evalSymbolArgs], (err, out) => { if (err) { logError('Failed to eval expression: ', JSON.stringify(evalSymbolArgs, null, ' '), '\n\rEval error:', err.toString()); return this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', { e: err.toString() }); } - response.body = this.convertDebugVariableToProtocolVariable(out.Variable, 0); + const variable = this.delve.isApiV1 ? out : (out).Variable; + response.body = this.convertDebugVariableToProtocolVariable(variable, 0); this.sendResponse(response); verbose('EvaluateResponse'); }); From f11ea6e34f2319e8a70811c8e550adee3f532c05 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Mon, 14 May 2018 17:22:28 -0700 Subject: [PATCH 15/19] Collapse loadConfig to single configuration --- package.json | 63 ++++++++++++++++++++++--------------- src/debugAdapter/goDebug.ts | 20 +++++------- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index ee57df8c1..94320e1ea 100644 --- a/package.json +++ b/package.json @@ -418,30 +418,43 @@ "description": "Output path for the binary of delve", "default": "debug" }, - "followPointers": { - "type": "boolean", - "description": "FollowPointers requests pointers to be automatically dereferenced", - "default": true - }, - "maxVariableRecurse": { - "type": "number", - "description": "MaxVariableRecurse is how far to recurse when evaluating nested types", - "default": 1 - }, - "maxStringLen": { - "type": "number", - "description": "MaxStringLen is the maximum number of bytes read from a string", - "default": 64 - }, - "maxArrayValues": { - "type": "number", - "description": "MaxArrayValues is the maximum number of elements read from an array, a slice or a map", - "default": 64 - }, - "maxStructFields": { - "type": "number", - "description": "MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields", - "default": -1 + "dlvLoadConfig": { + "type": "object", + "properties": { + "followPointers": { + "type": "boolean", + "description": "FollowPointers requests pointers to be automatically dereferenced", + "default": true + }, + "maxVariableRecurse": { + "type": "number", + "description": "MaxVariableRecurse is how far to recurse when evaluating nested types", + "default": 1 + }, + "maxStringLen": { + "type": "number", + "description": "MaxStringLen is the maximum number of bytes read from a string", + "default": 64 + }, + "maxArrayValues": { + "type": "number", + "description": "MaxArrayValues is the maximum number of elements read from an array, a slice or a map", + "default": 64 + }, + "maxStructFields": { + "type": "number", + "description": "MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields", + "default": -1 + } + }, + "description": "LoadConfig describes to delve, how to load values from target's memory", + "default": { + "followPointers": true, + "maxVariableRecurse": 1, + "maxStringLen": 64, + "maxArrayValues": 64, + "maxStructFields": -1 + } }, "useApiV1": { "type": "boolean", @@ -1034,4 +1047,4 @@ ] } } -} +} \ No newline at end of file diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 7be5d042a..1a1e81b55 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -199,11 +199,7 @@ interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { backend?: string; output?: string; /** Delve LoadConfig parameters **/ - followPointers: boolean; - maxVariableRecurse: number; - maxStringLen: number; - maxArrayValues: number; - maxStructFields: number; + dlvLoadConfig?: LoadConfig; /** Delve Version */ useApiV1: boolean; } @@ -384,13 +380,13 @@ class Delve { // Get default LoadConfig values according to delve API: // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13 // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423 - this.loadConfig = { - followPointers: argsGetter(launchArgs.followPointers, true), - maxVariableRecurse: argsGetter(launchArgs.maxVariableRecurse, 1), - maxStringLen: argsGetter(launchArgs.maxStringLen, 64), - maxArrayValues: argsGetter(launchArgs.maxArrayValues, 64), - maxStructFields: argsGetter(launchArgs.maxStructFields, -1) - }; + this.loadConfig = launchArgs.dlvLoadConfig || { + followPointers: true, + maxVariableRecurse: 1, + maxStringLen: 64, + maxArrayValues: 64, + maxStructFields: -1 + } verbose(`Running: ${dlv} ${dlvArgs.join(' ')}`); From fc88177e5dfd2390e443e11e64582fb55e449728 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Mon, 14 May 2018 17:32:31 -0700 Subject: [PATCH 16/19] Fix linting errors --- src/debugAdapter/goDebug.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 1a1e81b55..0d88dc639 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -259,10 +259,6 @@ class Delve { let isProgramDirectory = false; let launchArgsEnv = launchArgs.env || {}; this.connection = new Promise((resolve, reject) => { - let argsGetter = function (val: any, def: any) { - return ((val === null) || (val === undefined)) ? def : val; - }; - // Validations on the program if (!program) { return reject('The program attribute is missing in the debug configuration in launch.json'); @@ -386,7 +382,7 @@ class Delve { maxStringLen: 64, maxArrayValues: 64, maxStructFields: -1 - } + }; verbose(`Running: ${dlv} ${dlvArgs.join(' ')}`); From bcb89e42cc9a929a7421e854957b8f223b7aecb5 Mon Sep 17 00:00:00 2001 From: Luis G Date: Tue, 15 May 2018 01:33:57 -0300 Subject: [PATCH 17/19] Check remote debug server API version --- src/debugAdapter/goDebug.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 0d88dc639..bcba3a3ea 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -72,6 +72,11 @@ interface CreateBreakpointOut { breakpoint: DebugBreakpoint; } +interface GetVersionOut { + DelveVersion: string; + APIVersion: number; +} + interface DebugBreakpoint { addr: number; continue: boolean; @@ -552,6 +557,20 @@ class GoDebugSession extends DebugSession { }; this.delve.connection.then(() => { + this.delve.call('GetVersion', [], (err, out) => { + if (err) { + logError(err); + return this.sendErrorResponse(response, 2001, 'Failed to get remote server version: "{e}"', { e: err.toString() }); + } + let clientVersion = this.delve.isApiV1 ? 1 : 2; + if (out.APIVersion !== clientVersion) { + logError(`Failed to get version: The remote server is running on delve v${out.APIVersion} API and the client is running v${clientVersion} API`); + return this.sendErrorResponse(response, + 3000, + 'Failed to get version: The remote server is running on delve v{cli} API and the client is running v{ser} API', + { ser: out.APIVersion.toString(), cli: clientVersion }); + } + }); if (!this.delve.noDebug) { this.sendEvent(new InitializedEvent()); verbose('InitializeEvent'); From cea6535b025a395e9f69574af32e25838a3a6100 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Tue, 15 May 2018 20:28:07 -0700 Subject: [PATCH 18/19] Use v1 by default --- src/debugAdapter/goDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index bcba3a3ea..a618bbd29 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -258,7 +258,7 @@ class Delve { constructor(remotePath: string, port: number, host: string, program: string, launchArgs: LaunchRequestArguments) { this.program = normalizePath(program); this.remotePath = remotePath; - this.isApiV1 = launchArgs.useApiV1; + this.isApiV1 = typeof launchArgs.useApiV1 === 'boolean' ? launchArgs.useApiV1 : true; let mode = launchArgs.mode; let dlvCwd = dirname(program); let isProgramDirectory = false; From 4c9a77d8ce394777ca680c4ee7e164c9e655d1c9 Mon Sep 17 00:00:00 2001 From: Luis G Date: Sun, 27 May 2018 23:51:32 -0300 Subject: [PATCH 19/19] Move loadConfig init before remote mode return --- src/debugAdapter/goDebug.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index a618bbd29..608f0263e 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -331,6 +331,17 @@ class Delve { this.noDebug = false; let serverRunning = false; + // Get default LoadConfig values according to delve API: + // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13 + // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423 + this.loadConfig = launchArgs.dlvLoadConfig || { + followPointers: true, + maxVariableRecurse: 1, + maxStringLen: 64, + maxArrayValues: 64, + maxStructFields: -1 + }; + if (mode === 'remote') { this.debugProcess = null; serverRunning = true; // assume server is running when in remote mode @@ -378,16 +389,6 @@ class Delve { if (launchArgs.args) { dlvArgs = dlvArgs.concat(['--', ...launchArgs.args]); } - // Get default LoadConfig values according to delve API: - // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc1/server.go#L13 - // https://github.com/derekparker/delve/blob/c5c41f635244a22d93771def1c31cf1e0e9a2e63/service/rpc2/server.go#L423 - this.loadConfig = launchArgs.dlvLoadConfig || { - followPointers: true, - maxVariableRecurse: 1, - maxStringLen: 64, - maxArrayValues: 64, - maxStructFields: -1 - }; verbose(`Running: ${dlv} ${dlvArgs.join(' ')}`);